diff --git a/.gitignore b/.gitignore index dc7245b41e..f91a803df9 100644 --- a/.gitignore +++ b/.gitignore @@ -102,3 +102,21 @@ test/dummyfile/data # inbox inbox/ + + +nginx/*.cer +nginx/keys/*.crt +nginx/keys/*.csr +nginx/keys/*.key +nginx/*-metadata.xml + +backup/ + +# local config files +# regular file is nginx/ams/weko-frontend/app.config.ts +nginx/ams/weko-frontend/.app.config.ts.prod +nginx/ams/weko-frontend/.app.config.ts.dev +# regular file is nginx/ams/weko-frontend/nuxt.config.ts +nginx/ams/weko-frontend/.nuxt.config.ts.prod +nginx/ams/weko-frontend/.nuxt.config.ts.dev + diff --git a/docker-compose.yml b/docker-compose.yml index 919c868852..10dcd4478f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -152,7 +152,7 @@ services: - elasticsearch - rabbitmq - worker - - inbox + # - inbox logging: driver: "json-file" options: @@ -311,11 +311,14 @@ services: pgpool: restart: "always" - image: bitnami/pgpool + image: pgpool/pgpool:4.2.2 environment: - - PGPOOL_BACKEND_NODES=0:postgresql:5432 - - PGPOOL_SR_CHECK_USER=invenio - - PGPOOL_SR_CHECK_PASSWORD=dbpass123 + - PGPOOL_PARAMS_BACKEND_HOSTNAME0=postgresql + - PGPOOL_PARAMS_BACKEND_PORT0=5432 + - PGPOOL_PARAMS_BACKEND_WEIGHT0=1 + - PGPOOL_PARAMS_SR_CHECK_USER=invenio + - PGPOOL_PARAMS_SR_CHECK_PASSWORD=dbpass123 + - PGPOOL_PARAMS_PORT=5432 - PGPOOL_ENABLE_LDAP=no - PGPOOL_POSTGRES_USERNAME=postgres - PGPOOL_POSTGRES_PASSWORD=dbpass123 @@ -373,11 +376,15 @@ services: # - letsencrypt_html:/var/www/html links: - web - - inbox + # - inbox deploy: resources: limits: memory: 300m + environment: + - NODE_TLS_REJECT_UNAUTHORIZED=0 + extra_hosts: + - "ms2db.ir.rcos.nii.ac.jp:host-gateway" # certbot: # image: certbot/certbot:latest @@ -386,46 +393,46 @@ services: # - letsencrypt_html:/var/www/html # command: ["--version"] - flower: - restart: "always" - image: mher/flower:0.9.5 - command: --broker=amqp://guest:guest@rabbitmq:5672// --broker_api=http://guest:guest@rabbitmq:5671/api/ - ports: - - "5501:5555" - links: - - rabbitmq + # flower: + # restart: "always" + # image: mher/flower:0.9.5 + # command: --broker=amqp://guest:guest@rabbitmq:5672// --broker_api=http://guest:guest@rabbitmq:5671/api/ + # ports: + # - "5501:5555" + # links: + # - rabbitmq - inbox: - restart: "always" - build: - context: ./inbox - dockerfile: Dockerfile - ports: - - "8080:8080" - environment: - - ENABLE_PUSH_NOTIFICATIONS=True - - ICON=/static/images/weko-logo-256.png - - MONGO_DB_URI=mongodb://inbox:ibpass123@mongo:27017 - - MONGO_DB_NAME=inbox - - ON_RECEIVE_NOTIFICATION_WEBHOOK_URL= - - ALLOWED_ADMIN_ORIGINS=["*"] - - ALLOWED_ORIGINS=["*"] - - SUBSCRIBER=mailto:wekosoftware@nii.ac.jp - - VAPID_PUBLIC_KEY= - - VAPID_PRIVATE_KEY= - links: - - mongo + # inbox: + # restart: "always" + # build: + # context: ./inbox + # dockerfile: Dockerfile + # ports: + # - "8080:8080" + # environment: + # - ENABLE_PUSH_NOTIFICATIONS=True + # - ICON=/static/images/weko-logo-256.png + # - MONGO_DB_URI=mongodb://inbox:ibpass123@mongo:27017 + # - MONGO_DB_NAME=inbox + # - ON_RECEIVE_NOTIFICATION_WEBHOOK_URL= + # - ALLOWED_ADMIN_ORIGINS=["*"] + # - ALLOWED_ORIGINS=["*"] + # - SUBSCRIBER=mailto:wekosoftware@nii.ac.jp + # - VAPID_PUBLIC_KEY= + # - VAPID_PRIVATE_KEY= + # links: + # - mongo - mongo: - restart: "always" - image: mongo:7.0.14 - environment: - - MONGO_INITDB_ROOT_USERNAME=inbox - - MONGO_INITDB_ROOT_PASSWORD=ibpass123 - ports: - - "27017:27017" - volumes: - - mongo_data:/data/db + # mongo: + # restart: "always" + # image: mongo:7.0.14 + # environment: + # - MONGO_INITDB_ROOT_USERNAME=inbox + # - MONGO_INITDB_ROOT_PASSWORD=ibpass123 + # ports: + # - "27017:27017" + # volumes: + # - mongo_data:/data/db # kibana: # build: @@ -449,7 +456,6 @@ volumes: conf_data: pgsql-data: es-data: - mongo_data: + # mongo_data: # letsencrypt_etc: # letsencrypt_html: - diff --git a/docker-compose2.yml b/docker-compose2.yml index baf22a20a4..055827d3d4 100644 --- a/docker-compose2.yml +++ b/docker-compose2.yml @@ -46,7 +46,7 @@ services: - INVENIO_WEB_HOST=127.0.0.1 - INVENIO_WEB_INSTANCE=invenio - INVENIO_WEB_VENV=invenio - - INVENIO_WEB_HOST_NAME=weko3.example.org + - INVENIO_WEB_HOST_NAME=ms2db.ir.rcos.nii.ac.jp - INVENIO_WEB_PROTOCOL=https - INVENIO_USER_EMAIL=wekosoftware@nii.ac.jp - INVENIO_USER_PASS=uspass123 @@ -69,8 +69,8 @@ services: - INVENIO_ROLE_CONTRIBUTOR=Contributor - INVENIO_ROLE_COMMUNITY=Community Administrator # production or development - - FLASK_ENV=development - # - FLASK_ENV=production + # - FLASK_ENV=development + - FLASK_ENV=production # - FLASK_DEBUG=True - SEARCH_INDEX_PREFIX=tenant1 #- INVENIO_DB_POOL_CLASS=QueuePool @@ -186,7 +186,7 @@ services: - INVENIO_WEB_HOST=127.0.0.1 - INVENIO_WEB_INSTANCE=invenio - INVENIO_WEB_VENV=invenio - - INVENIO_WEB_HOST_NAME=weko3.example.org + - INVENIO_WEB_HOST_NAME=ms2db.ir.rcos.nii.ac.jp - INVENIO_WEB_PROTOCOL=https - INVENIO_USER_EMAIL=wekosoftware@nii.ac.jp - INVENIO_USER_PASS=uspass123 @@ -311,11 +311,14 @@ services: pgpool: restart: "always" - image: bitnami/pgpool + image: pgpool/pgpool:4.2.2 environment: - - PGPOOL_BACKEND_NODES=0:postgresql:5432 - - PGPOOL_SR_CHECK_USER=invenio - - PGPOOL_SR_CHECK_PASSWORD=dbpass123 + - PGPOOL_PARAMS_BACKEND_HOSTNAME0=postgresql + - PGPOOL_PARAMS_BACKEND_PORT0=5432 + - PGPOOL_PARAMS_BACKEND_WEIGHT0=1 + - PGPOOL_PARAMS_SR_CHECK_USER=invenio + - PGPOOL_PARAMS_SR_CHECK_PASSWORD=dbpass123 + - PGPOOL_PARAMS_PORT=5432 - PGPOOL_ENABLE_LDAP=no - PGPOOL_POSTGRES_USERNAME=postgres - PGPOOL_POSTGRES_PASSWORD=dbpass123 @@ -378,6 +381,10 @@ services: resources: limits: memory: 300m + environment: + - NODE_TLS_REJECT_UNAUTHORIZED=0 + extra_hosts: + - ms2db.ir.rcos.nii.ac.jp:127.0.0.1 # certbot: # image: certbot/certbot:latest @@ -452,4 +459,3 @@ volumes: mongo_data: # letsencrypt_etc: # letsencrypt_html: - diff --git a/install.sh b/install.sh index c1ddea64ed..e59d57a9f0 100644 --- a/install.sh +++ b/install.sh @@ -1,25 +1,25 @@ #!/bin/bash find . | grep -E "(__pycache__|\.tox|\.eggs|\.pyc|\.pyo$)" | xargs rm -rf -docker-compose -f docker-compose2.yml down -v -DOCKER_BUILDKIT=1 COMPOSE_DOCKER_CLI_BUILD=1 docker-compose -f docker-compose2.yml build --no-cache --force-rm +docker compose -f docker-compose2.yml down -v +DOCKER_BUILDKIT=1 COMPOSE_DOCKER_CLI_BUILD=1 docker compose -f docker-compose2.yml build --no-cache --force-rm # Initialize resources -docker-compose -f docker-compose2.yml run --rm web ./scripts/populate-instance.sh -docker cp scripts/demo/item_type.sql $(docker-compose -f docker-compose2.yml ps -q postgresql):/tmp/item_type.sql -docker-compose -f docker-compose2.yml exec postgresql psql -U invenio -d invenio -f /tmp/item_type.sql -docker cp scripts/demo/indextree.sql $(docker-compose -f docker-compose2.yml ps -q postgresql):/tmp/indextree.sql -docker-compose -f docker-compose2.yml exec postgresql psql -U invenio -d invenio -f /tmp/indextree.sql -docker-compose -f docker-compose2.yml run --rm web invenio workflow init action_status,Action -docker cp scripts/demo/defaultworkflow.sql $(docker-compose -f docker-compose2.yml ps -q postgresql):/tmp/defaultworkflow.sql -docker-compose -f docker-compose2.yml exec postgresql psql -U invenio -d invenio -f /tmp/defaultworkflow.sql -docker cp scripts/demo/doi_identifier.sql $(docker-compose -f docker-compose2.yml ps -q postgresql):/tmp/doi_identifier.sql -docker-compose -f docker-compose2.yml exec postgresql psql -U invenio -d invenio -f /tmp/doi_identifier.sql -docker cp postgresql/ddl/W-OA-user_activity_log.sql $(docker-compose -f docker-compose2.yml ps -q postgresql):/tmp/W-OA-user_activity_log.sql -docker-compose -f docker-compose2.yml exec postgresql psql -U invenio -d invenio -f /tmp/W-OA-user_activity_log.sql +docker compose -f docker-compose2.yml run --rm web ./scripts/populate-instance.sh +docker cp scripts/demo/item_type.sql $(docker compose -f docker-compose2.yml ps -q postgresql):/tmp/item_type.sql +docker compose -f docker-compose2.yml exec postgresql psql -U invenio -d invenio -f /tmp/item_type.sql +docker cp scripts/demo/indextree.sql $(docker compose -f docker-compose2.yml ps -q postgresql):/tmp/indextree.sql +docker compose -f docker-compose2.yml exec postgresql psql -U invenio -d invenio -f /tmp/indextree.sql +docker compose -f docker-compose2.yml run --rm web invenio workflow init action_status,Action +docker cp scripts/demo/defaultworkflow.sql $(docker dompose -f docker-compose2.yml ps -q postgresql):/tmp/defaultworkflow.sql +docker compose -f docker-compose2.yml exec postgresql psql -U invenio -d invenio -f /tmp/defaultworkflow.sql +docker cp scripts/demo/doi_identifier.sql $(docker compose -f docker-compose2.yml ps -q postgresql):/tmp/doi_identifier.sql +docker compose -f docker-compose2.yml exec postgresql psql -U invenio -d invenio -f /tmp/doi_identifier.sql +docker cp postgresql/ddl/W-OA-user_activity_log.sql $(docker compose -f docker-compose2.yml ps -q postgresql):/tmp/W-OA-user_activity_log.sql +docker compose -f docker-compose2.yml exec postgresql psql -U invenio -d invenio -f /tmp/W-OA-user_activity_log.sql -docker-compose -f docker-compose2.yml run --rm web invenio assets build -docker-compose -f docker-compose2.yml run --rm web invenio collect -v +docker compose -f docker-compose2.yml run --rm web invenio assets build +docker compose -f docker-compose2.yml run --rm web invenio collect -v # Start services -docker-compose -f docker-compose2.yml up -d +docker compose -f docker-compose2.yml up -d diff --git a/modules/invenio-accounts/invenio_accounts/forms.py b/modules/invenio-accounts/invenio_accounts/forms.py index bb6217d3f5..ba8b12d377 100644 --- a/modules/invenio-accounts/invenio_accounts/forms.py +++ b/modules/invenio-accounts/invenio_accounts/forms.py @@ -65,5 +65,15 @@ def __init__(self, *args, **kwargs): """ super(LoginForm, self).__init__(*args, **kwargs) self.remember.data = False + + def validate(self): + """Validate the form after trimming email and password fields.""" + if self.email.data: + self.email.data = self.email.data.strip() + self.email.data = self.email.data.strip('\u200b') # remove zero-width spaces + if self.password.data: + self.password.data = self.password.data.strip() + self.password.data = self.password.data.strip('\u200b') # remove zero-width spaces + return super(LoginForm, self).validate() return LoginForm diff --git a/modules/invenio-oauth2server/babel.ini b/modules/invenio-oauth2server/babel.ini index e021ca1959..f43121a12a 100644 --- a/modules/invenio-oauth2server/babel.ini +++ b/modules/invenio-oauth2server/babel.ini @@ -15,7 +15,7 @@ encoding = utf-8 [jinja2: **/templates/**.html] encoding = utf-8 -extensions = jinja2.ext.autoescape, jinja2.ext.with_ +extensions = webassets.ext.jinja2.AssetsExtension # Extraction from JavaScript files diff --git a/modules/invenio-oauth2server/invenio_oauth2server/templates/invenio_oauth2server/authorize.html b/modules/invenio-oauth2server/invenio_oauth2server/templates/invenio_oauth2server/authorize.html index 921dc581b7..5d0eb131a7 100644 --- a/modules/invenio-oauth2server/invenio_oauth2server/templates/invenio_oauth2server/authorize.html +++ b/modules/invenio-oauth2server/invenio_oauth2server/templates/invenio_oauth2server/authorize.html @@ -1,10 +1,10 @@ {# -*- coding: utf-8 -*- - This file is part of Invenio. - Copyright (C) 2015-2018 CERN. +This file is part of Invenio. +Copyright (C) 2015-2018 CERN. - Invenio is free software; you can redistribute it and/or modify it - under the terms of the MIT License; see LICENSE file for more details. +Invenio is free software; you can redistribute it and/or modify it +under the terms of the MIT License; see LICENSE file for more details. #} {%- extends config.OAUTH2SERVER_COVER_TEMPLATE %} @@ -13,54 +13,64 @@ {% block page_body %}
-
-
-{{ helpers.panel_start(_('Authorize application'), icon='fa fa-shield fa-fw') }} -

{{ _('Authorize application') }}

-

{{ _("Application '%(client_name)s' by '%(client_user)s' wants permission to access your '%(current_user)s' account.", - client_name=client.name, client_user=client.user.nickname or client.user.email, current_user=current_user.nickname or current_user.email) }}

-
-
-
-
+
+
+ {{ helpers.panel_start(_('Authorize application'), icon='fa fa-shield fa-fw') }} +

{{ _('Authorize application') }}

+

{{ _("Application '%(client_name)s' by '%(client_user)s' wants permission to access your + '%(current_user)s' account.", + client_name=client.name, client_user=client.user.nickname or client.user.email, + current_user=current_user.nickname or current_user.email) }}

+
+
+
+
-

{{ _('Review permissions') }}

- {%- for group in scopes|groupby('group') %} - {%- if loop.first %}{% endif %} - - - - - {%- if loop.last %}
{{group.grouper}}
    {% for scope in group.list %}
  • {{scope.help_text}}
  • {% endfor %}
{% endif %} - {%- else %} -

No permissions granted.

- {%- endfor %} -
-
-
-
-
- {{ _('Application') }} -

{{client.name}}

- {%- if client.description %}

{{client.description}}

{% endif %} - {%- if client.website %}

{{ _('Visit application website') }}

{% endif %} -
-

- {{ client.get_users }} {{ 'user' if client.get_users == 1 else 'users' }} -

+

{{ _('Review permissions') }}

+ {%- for group in scopes|groupby('group') %} + {%- if loop.first %} + {% endif %} + + + + + {%- if loop.last %} + +
{{group.grouper}} +
    {% for scope in group.list %}
  • {{scope.help_text}}
  • {% endfor %}
+
{% endif %} + {%- else %} +

{{ _("No permissions granted.") }}

+ {%- endfor %} +
+
+
+
+
+ {{ _('Application') }} +

{{client.name}}

+ {%- if client.description %}

{{client.description}}

{% endif %} + {%- if client.website %}

{{ _("Visit application website") }}

{% + endif %} +
+

+ {{ client.get_users }} {{ 'user' if client.get_users == 1 else 'users' }} +

+
+
+
+
+
+
+
+ + +
+
+ {{ helpers.panel_end() }} +
-
-
-
-
-
-
- - -
-
-{{ helpers.panel_end() }} -
-
{% endblock %} diff --git a/modules/invenio-oauth2server/invenio_oauth2server/templates/invenio_oauth2server/errors.html b/modules/invenio-oauth2server/invenio_oauth2server/templates/invenio_oauth2server/errors.html index 13cea2a2d6..7452474103 100644 --- a/modules/invenio-oauth2server/invenio_oauth2server/templates/invenio_oauth2server/errors.html +++ b/modules/invenio-oauth2server/invenio_oauth2server/templates/invenio_oauth2server/errors.html @@ -1,10 +1,10 @@ {# -*- coding: utf-8 -*- - This file is part of Invenio. - Copyright (C) 2015-2018 CERN. +This file is part of Invenio. +Copyright (C) 2015-2018 CERN. - Invenio is free software; you can redistribute it and/or modify it - under the terms of the MIT License; see LICENSE file for more details. +Invenio is free software; you can redistribute it and/or modify it +under the terms of the MIT License; see LICENSE file for more details. #} {%- extends config.OAUTH2SERVER_COVER_TEMPLATE %} @@ -14,24 +14,36 @@ {%- block page_body %}
- {{ helpers.panel_start( + {{ helpers.panel_start( _('Invalid authorization request'), icon='fa fa-warning fa-fw' - ) }} - - {{ _('Invalid authorization request') }} - - {{ _('The service that redirected your here made an invalid authorization request (error code: %(x_error)s).', - x_error=error.error) }} - -
-
-
- - {{ _('Get me out of here!') }} - + ) }} + +

+ {{ _('You cannot access the service because there is an error in the authentication request.') }} + {% if error.error == unsupported_response_type %} + ({{ _('unsupported_response_type') }}) + {% elif error.error == 'invalid_request' %} + {% if error.description is string and 'client_id' in error.description %} + ({{ _('invalid_client_id') }}) + {% else %} + ({{ _('invalid_request') }}) + {% endif %} + {% elif error.error == 'invalid_scope' %} + ({{ _('invalid_scope')}}) + {% elif error.error == 'access_denied' %} + ({{ _('access_denied') }}) + {% endif %} +

+ +
+
+ + {{ helpers.panel_end() }}
- {{ helpers.panel_end() }}
-
-{%- endblock %} + {%- endblock %} diff --git a/modules/invenio-oauth2server/invenio_oauth2server/translations/de/LC_MESSAGES/messages.mo b/modules/invenio-oauth2server/invenio_oauth2server/translations/de/LC_MESSAGES/messages.mo index a8a88c920e..105341f3fc 100644 Binary files a/modules/invenio-oauth2server/invenio_oauth2server/translations/de/LC_MESSAGES/messages.mo and b/modules/invenio-oauth2server/invenio_oauth2server/translations/de/LC_MESSAGES/messages.mo differ diff --git a/modules/invenio-oauth2server/invenio_oauth2server/translations/en/LC_MESSAGES/messages.mo b/modules/invenio-oauth2server/invenio_oauth2server/translations/en/LC_MESSAGES/messages.mo index 7993bcacba..1b177fba6f 100644 Binary files a/modules/invenio-oauth2server/invenio_oauth2server/translations/en/LC_MESSAGES/messages.mo and b/modules/invenio-oauth2server/invenio_oauth2server/translations/en/LC_MESSAGES/messages.mo differ diff --git a/modules/invenio-oauth2server/invenio_oauth2server/translations/en/LC_MESSAGES/messages.po b/modules/invenio-oauth2server/invenio_oauth2server/translations/en/LC_MESSAGES/messages.po index 89915abdb9..8f1f0ef602 100644 --- a/modules/invenio-oauth2server/invenio_oauth2server/translations/en/LC_MESSAGES/messages.po +++ b/modules/invenio-oauth2server/invenio_oauth2server/translations/en/LC_MESSAGES/messages.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: invenio-oauth2server 1.0.0\n" "Report-Msgid-Bugs-To: info@inveniosoftware.org\n" -"POT-Creation-Date: 2025-05-07 17:40+0900\n" +"POT-Creation-Date: 2025-06-26 13:44+0900\n" "PO-Revision-Date: 2025-05-07 18:18+0900\n" "Last-Translator: FULL NAME \n" "Language: en\n" @@ -17,7 +17,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.5.1\n" +"Generated-By: Babel 2.9.1\n" #: invenio_oauth2server/admin.py:60 msgid "OAuth Applications" @@ -112,8 +112,8 @@ msgstr "" #: invenio_oauth2server/templates/invenio_oauth2server/authorize.html:18 #: invenio_oauth2server/templates/invenio_oauth2server/authorize.html:19 -#: invenio_oauth2server/templates/invenio_oauth2server/authorize.html:58 -#: invenio_oauth2server/views/server.py:69 +#: invenio_oauth2server/templates/invenio_oauth2server/authorize.html:67 +#: invenio_oauth2server/views/server.py:70 msgid "Authorize application" msgstr "" @@ -121,41 +121,64 @@ msgstr "" #, python-format msgid "" "Application '%(client_name)s' by '%(client_user)s' wants permission to " -"access your '%(current_user)s' account." +"access your\n" +" '%(current_user)s' account." msgstr "" -#: invenio_oauth2server/templates/invenio_oauth2server/authorize.html:27 +#: invenio_oauth2server/templates/invenio_oauth2server/authorize.html:29 #: invenio_oauth2server/templates/invenio_oauth2server/settings/token_permission_view.html:29 msgid "Review permissions" msgstr "" #: invenio_oauth2server/templates/invenio_oauth2server/authorize.html:43 +msgid "No permissions granted." +msgstr "" + +#: invenio_oauth2server/templates/invenio_oauth2server/authorize.html:50 #: invenio_oauth2server/templates/invenio_oauth2server/settings/token_permission_view.html:43 msgid "Application" msgstr "" -#: invenio_oauth2server/templates/invenio_oauth2server/authorize.html:46 +#: invenio_oauth2server/templates/invenio_oauth2server/authorize.html:53 #: invenio_oauth2server/templates/invenio_oauth2server/settings/token_permission_view.html:46 msgid "Visit application website" msgstr "" -#: invenio_oauth2server/templates/invenio_oauth2server/authorize.html:59 +#: invenio_oauth2server/templates/invenio_oauth2server/authorize.html:69 msgid "Reject" msgstr "" #: invenio_oauth2server/templates/invenio_oauth2server/errors.html:18 -#: invenio_oauth2server/templates/invenio_oauth2server/errors.html:22 msgid "Invalid authorization request" msgstr "" -#: invenio_oauth2server/templates/invenio_oauth2server/errors.html:24 -#, python-format +#: invenio_oauth2server/templates/invenio_oauth2server/errors.html:23 msgid "" -"The service that redirected your here made an invalid authorization " -"request (error code: %(x_error)s)." +"You cannot access the service because there is an error in the " +"authentication request." msgstr "" -#: invenio_oauth2server/templates/invenio_oauth2server/errors.html:31 +#: invenio_oauth2server/templates/invenio_oauth2server/errors.html:25 +msgid "unsupported_response_type" +msgstr "error: This response type is not supported." + +#: invenio_oauth2server/templates/invenio_oauth2server/errors.html:28 +msgid "invalid_client_id" +msgstr "error: The client ID is incorrect." + +#: invenio_oauth2server/templates/invenio_oauth2server/errors.html:30 +msgid "invalid_request" +msgstr "error: The request is invalid." + +#: invenio_oauth2server/templates/invenio_oauth2server/errors.html:33 +msgid "invalid_scope" +msgstr "error: The scope is incorrect." + +#: invenio_oauth2server/templates/invenio_oauth2server/errors.html:35 +msgid "access_denied" +msgstr "error: Access has been denied." + +#: invenio_oauth2server/templates/invenio_oauth2server/errors.html:43 msgid "Get me out of here!" msgstr "" diff --git a/modules/invenio-oauth2server/invenio_oauth2server/translations/ja/LC_MESSAGES/messages.mo b/modules/invenio-oauth2server/invenio_oauth2server/translations/ja/LC_MESSAGES/messages.mo index d60d4802e7..a213779f40 100644 Binary files a/modules/invenio-oauth2server/invenio_oauth2server/translations/ja/LC_MESSAGES/messages.mo and b/modules/invenio-oauth2server/invenio_oauth2server/translations/ja/LC_MESSAGES/messages.mo differ diff --git a/modules/invenio-oauth2server/invenio_oauth2server/translations/ja/LC_MESSAGES/messages.po b/modules/invenio-oauth2server/invenio_oauth2server/translations/ja/LC_MESSAGES/messages.po index e199cd0dcc..6193754711 100644 --- a/modules/invenio-oauth2server/invenio_oauth2server/translations/ja/LC_MESSAGES/messages.po +++ b/modules/invenio-oauth2server/invenio_oauth2server/translations/ja/LC_MESSAGES/messages.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: invenio-oauth2server 1.0.0\n" "Report-Msgid-Bugs-To: info@inveniosoftware.org\n" -"POT-Creation-Date: 2025-05-07 17:40+0900\n" +"POT-Creation-Date: 2025-06-26 13:44+0900\n" "PO-Revision-Date: 2025-05-07 18:18+0900\n" "Last-Translator: FULL NAME \n" "Language: ja\n" @@ -17,7 +17,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.5.1\n" +"Generated-By: Babel 2.9.1\n" #: invenio_oauth2server/admin.py:60 msgid "OAuth Applications" @@ -112,50 +112,72 @@ msgstr "" #: invenio_oauth2server/templates/invenio_oauth2server/authorize.html:18 #: invenio_oauth2server/templates/invenio_oauth2server/authorize.html:19 -#: invenio_oauth2server/templates/invenio_oauth2server/authorize.html:58 -#: invenio_oauth2server/views/server.py:69 +#: invenio_oauth2server/templates/invenio_oauth2server/authorize.html:67 +#: invenio_oauth2server/views/server.py:70 msgid "Authorize application" -msgstr "" +msgstr "アプリの使用を承諾する" #: invenio_oauth2server/templates/invenio_oauth2server/authorize.html:20 -#, python-format msgid "" "Application '%(client_name)s' by '%(client_user)s' wants permission to " -"access your '%(current_user)s' account." -msgstr "" +"access your\n" +" '%(current_user)s' account." +msgstr "「%(client_user)s」による「%(client_name)s」アプリケーションがあなたのアカウントへのアクセス許可を求めています。" -#: invenio_oauth2server/templates/invenio_oauth2server/authorize.html:27 +#: invenio_oauth2server/templates/invenio_oauth2server/authorize.html:29 #: invenio_oauth2server/templates/invenio_oauth2server/settings/token_permission_view.html:29 msgid "Review permissions" -msgstr "" +msgstr "権限を確認" #: invenio_oauth2server/templates/invenio_oauth2server/authorize.html:43 +msgid "No permissions granted." +msgstr "権限が付与されていません。" + +#: invenio_oauth2server/templates/invenio_oauth2server/authorize.html:50 #: invenio_oauth2server/templates/invenio_oauth2server/settings/token_permission_view.html:43 msgid "Application" -msgstr "" +msgstr "アプリケーション" -#: invenio_oauth2server/templates/invenio_oauth2server/authorize.html:46 +#: invenio_oauth2server/templates/invenio_oauth2server/authorize.html:53 #: invenio_oauth2server/templates/invenio_oauth2server/settings/token_permission_view.html:46 msgid "Visit application website" -msgstr "" +msgstr "アプリケーションのウェブサイト閲覧" -#: invenio_oauth2server/templates/invenio_oauth2server/authorize.html:59 +#: invenio_oauth2server/templates/invenio_oauth2server/authorize.html:69 msgid "Reject" -msgstr "" +msgstr "拒否" #: invenio_oauth2server/templates/invenio_oauth2server/errors.html:18 -#: invenio_oauth2server/templates/invenio_oauth2server/errors.html:22 msgid "Invalid authorization request" -msgstr "" +msgstr "無効な認証リクエスト" -#: invenio_oauth2server/templates/invenio_oauth2server/errors.html:24 -#, python-format +#: invenio_oauth2server/templates/invenio_oauth2server/errors.html:23 msgid "" -"The service that redirected your here made an invalid authorization " -"request (error code: %(x_error)s)." -msgstr "" +"You cannot access the service because there is an error in the " +"authentication request." +msgstr "認証リクエストに誤りがあるため、アクセスできません。" + +#: invenio_oauth2server/templates/invenio_oauth2server/errors.html:25 +msgid "unsupported_response_type" +msgstr "エラー: このレスポンスタイプはサポートされていません。" + +#: invenio_oauth2server/templates/invenio_oauth2server/errors.html:28 +msgid "invalid_client_id" +msgstr "エラー: クライアントIDに誤りがあります。" + +#: invenio_oauth2server/templates/invenio_oauth2server/errors.html:30 +msgid "invalid_request" +msgstr "エラー: 無効なリクエストです。" + +#: invenio_oauth2server/templates/invenio_oauth2server/errors.html:33 +msgid "invalid_scope" +msgstr "エラー: スコープに誤りがあります。" + +#: invenio_oauth2server/templates/invenio_oauth2server/errors.html:35 +msgid "access_denied" +msgstr "エラー: アクセスが拒否されました。" -#: invenio_oauth2server/templates/invenio_oauth2server/errors.html:31 +#: invenio_oauth2server/templates/invenio_oauth2server/errors.html:43 msgid "Get me out of here!" msgstr "" diff --git a/modules/invenio-oauth2server/invenio_oauth2server/translations/messages.pot b/modules/invenio-oauth2server/invenio_oauth2server/translations/messages.pot index f59dfdd923..73cb210b50 100644 --- a/modules/invenio-oauth2server/invenio_oauth2server/translations/messages.pot +++ b/modules/invenio-oauth2server/invenio_oauth2server/translations/messages.pot @@ -9,14 +9,14 @@ msgid "" msgstr "" "Project-Id-Version: invenio-oauth2server 1.0.0\n" "Report-Msgid-Bugs-To: info@inveniosoftware.org\n" -"POT-Creation-Date: 2025-05-07 17:40+0900\n" +"POT-Creation-Date: 2025-06-26 13:44+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.5.1\n" +"Generated-By: Babel 2.9.1\n" #: invenio_oauth2server/admin.py:60 msgid "OAuth Applications" @@ -111,8 +111,8 @@ msgstr "" #: invenio_oauth2server/templates/invenio_oauth2server/authorize.html:18 #: invenio_oauth2server/templates/invenio_oauth2server/authorize.html:19 -#: invenio_oauth2server/templates/invenio_oauth2server/authorize.html:58 -#: invenio_oauth2server/views/server.py:69 +#: invenio_oauth2server/templates/invenio_oauth2server/authorize.html:67 +#: invenio_oauth2server/views/server.py:70 msgid "Authorize application" msgstr "" @@ -120,41 +120,64 @@ msgstr "" #, python-format msgid "" "Application '%(client_name)s' by '%(client_user)s' wants permission to " -"access your '%(current_user)s' account." +"access your\n" +" '%(current_user)s' account." msgstr "" -#: invenio_oauth2server/templates/invenio_oauth2server/authorize.html:27 +#: invenio_oauth2server/templates/invenio_oauth2server/authorize.html:29 #: invenio_oauth2server/templates/invenio_oauth2server/settings/token_permission_view.html:29 msgid "Review permissions" msgstr "" #: invenio_oauth2server/templates/invenio_oauth2server/authorize.html:43 +msgid "No permissions granted." +msgstr "" + +#: invenio_oauth2server/templates/invenio_oauth2server/authorize.html:50 #: invenio_oauth2server/templates/invenio_oauth2server/settings/token_permission_view.html:43 msgid "Application" msgstr "" -#: invenio_oauth2server/templates/invenio_oauth2server/authorize.html:46 +#: invenio_oauth2server/templates/invenio_oauth2server/authorize.html:53 #: invenio_oauth2server/templates/invenio_oauth2server/settings/token_permission_view.html:46 msgid "Visit application website" msgstr "" -#: invenio_oauth2server/templates/invenio_oauth2server/authorize.html:59 +#: invenio_oauth2server/templates/invenio_oauth2server/authorize.html:69 msgid "Reject" msgstr "" #: invenio_oauth2server/templates/invenio_oauth2server/errors.html:18 -#: invenio_oauth2server/templates/invenio_oauth2server/errors.html:22 msgid "Invalid authorization request" msgstr "" -#: invenio_oauth2server/templates/invenio_oauth2server/errors.html:24 -#, python-format +#: invenio_oauth2server/templates/invenio_oauth2server/errors.html:23 msgid "" -"The service that redirected your here made an invalid authorization " -"request (error code: %(x_error)s)." +"You cannot access the service because there is an error in the " +"authentication request." +msgstr "" + +#: invenio_oauth2server/templates/invenio_oauth2server/errors.html:25 +msgid "unsupported_response_type" +msgstr "" + +#: invenio_oauth2server/templates/invenio_oauth2server/errors.html:28 +msgid "invalid_client_id" +msgstr "" + +#: invenio_oauth2server/templates/invenio_oauth2server/errors.html:30 +msgid "invalid_request" +msgstr "" + +#: invenio_oauth2server/templates/invenio_oauth2server/errors.html:33 +msgid "invalid_scope" +msgstr "" + +#: invenio_oauth2server/templates/invenio_oauth2server/errors.html:35 +msgid "access_denied" msgstr "" -#: invenio_oauth2server/templates/invenio_oauth2server/errors.html:31 +#: invenio_oauth2server/templates/invenio_oauth2server/errors.html:43 msgid "Get me out of here!" msgstr "" diff --git a/modules/invenio-oauth2server/invenio_oauth2server/views/server.py b/modules/invenio-oauth2server/invenio_oauth2server/views/server.py index 5e59143bb1..9d40ec41b1 100644 --- a/modules/invenio-oauth2server/invenio_oauth2server/views/server.py +++ b/modules/invenio-oauth2server/invenio_oauth2server/views/server.py @@ -13,12 +13,14 @@ from functools import wraps from flask import Blueprint, _request_ctx_stack, abort, current_app, jsonify, \ - redirect, render_template, request, session + redirect, render_template, request, make_response from flask_babelex import lazy_gettext as _ from flask_breadcrumbs import register_breadcrumb from flask_login import login_required from flask_principal import Identity, identity_changed -from oauthlib.oauth2.rfc6749.errors import InvalidClientError, OAuth2Error +from oauthlib.oauth2.rfc6749.errors import InvalidClientError, OAuth2Error, \ + AccessDeniedError, raise_from_error + from invenio_db import db from ..models import Client @@ -41,7 +43,7 @@ def login_oauth2_user(valid, oauth): oauth.user.login_via_oauth2 = True _request_ctx_stack.top.user = oauth.user identity_changed.send(current_app._get_current_object(), - identity=Identity(oauth.user.id)) + identity=Identity(oauth.user.id)) return valid, oauth @@ -81,17 +83,20 @@ def authorize(*args, **kwargs): abort(404) scopes = current_oauth2server.scopes + scopes_list = [scopes[x] for x in kwargs.get('scopes', [])] + if not scopes_list: + return redirect('/oauth/errors?error=invalid_scope') + ctx = dict( client=client, oauth_request=kwargs.get('request'), - scopes=[scopes[x] for x in kwargs.get('scopes', [])], + scopes=scopes_list ) return render_template('invenio_oauth2server/authorize.html', **ctx) confirm = request.form.get('confirm', 'no') return confirm == 'yes' - @blueprint.route('/token', methods=['POST', ]) @oauth2.token_handler def access_token(): @@ -114,20 +119,29 @@ def access_token(): # Return None or a dictionary. Dictionary will be merged with token # returned to the client requesting the access token. # Response is in application/json + return None @blueprint.route('/errors') def errors(): """Error view in case of invalid oauth requests.""" - from oauthlib.oauth2.rfc6749.errors import raise_from_error + status_code = 200 try: error = None - raise_from_error(request.values.get('error'), params=dict()) + error_code = request.values.get('error') + description = request.values.get('error_description') + params = {} + if description: + params['error_description'] = description + + raise_from_error(error_code, params=params) except OAuth2Error as raised: error = raised - return render_template('invenio_oauth2server/errors.html', error=error) - + if not isinstance(error, AccessDeniedError): + status_code = 400 + response = make_response(render_template('invenio_oauth2server/errors.html', error=error), status_code) + return response @blueprint.route('/ping', methods=['GET', 'POST']) @oauth2.require_oauth() @@ -163,10 +177,11 @@ def invalid(): @blueprint.teardown_request def dbsession_clean(exception): + """Clean up the database session after each request.""" current_app.logger.debug("invenio_oauth2server dbsession_clean: {}".format(exception)) if exception is None: try: db.session.commit() except: db.session.rollback() - db.session.remove() \ No newline at end of file + db.session.remove() diff --git a/modules/invenio-oauth2server/tests/conftest.py b/modules/invenio-oauth2server/tests/conftest.py index 05e8fd0860..b7f35001ae 100644 --- a/modules/invenio-oauth2server/tests/conftest.py +++ b/modules/invenio-oauth2server/tests/conftest.py @@ -314,10 +314,20 @@ def provider_fixture(app): 'oauth2test.authorized', _external=True ), _default_scopes='email') + c5 = Client(client_id='no-scopes', + client_secret='no-scopes', + name='no-scopes', + description='', + is_confidential=False, + user=user1, + _redirect_uris=url_for( + 'oauth2test.authorized', _external=True + )) db.session.add(c1) db.session.add(c2) db.session.add(c3) db.session.add(c4) + db.session.add(c5) personal_token = Token.create_personal('test-personal', user1.id, scopes=[], diff --git a/modules/invenio-oauth2server/tests/test_provider.py b/modules/invenio-oauth2server/tests/test_provider.py index ab4963ffab..117d524668 100644 --- a/modules/invenio-oauth2server/tests/test_provider.py +++ b/modules/invenio-oauth2server/tests/test_provider.py @@ -136,7 +136,7 @@ def test_invalid_authorize_requests(provider_fixture): assert error_url in next_url r = client.get(next_url, query_string=data) - assert 'invalid_request' in str(r.data) + assert 'an error in the authentication request' in str(r.data) # Invalid redirect uri r = client.get(url_for( @@ -151,6 +151,27 @@ def test_invalid_authorize_requests(provider_fixture): assert data['error'] == 'invalid_request' assert error_url in next_url + for client_id in ['no-scopes']: + response_type = 'code' + error_url = url_for('invenio_oauth2server.errors', + _external=True) + # Missing scope + r = client.get( + url_for('invenio_oauth2server.authorize'), + data={ + 'redirect_uri': redirect_uri, + 'response_type': response_type, + 'client_id': client_id, + }) + next_url, data = parse_redirect(r.location) + assert r.status_code == 302 + assert data['error'] == 'invalid_scope' + assert url_for('invenio_oauth2server.errors') in next_url + + r = client.get( + url_for('invenio_oauth2server.errors')) + assert r.status_code == 400 + def test_refresh_flow(provider_fixture): app = provider_fixture diff --git a/modules/invenio-records-rest/invenio_records_rest/serializers/json.py b/modules/invenio-records-rest/invenio_records_rest/serializers/json.py index 272c23d197..6984f34dbe 100644 --- a/modules/invenio-records-rest/invenio_records_rest/serializers/json.py +++ b/modules/invenio-records-rest/invenio_records_rest/serializers/json.py @@ -10,7 +10,7 @@ from __future__ import absolute_import, print_function -from flask import json, request +from flask import json, request,current_app from weko_records.api import ItemTypes from .base import PreprocessorMixin, SerializerMixinInterface @@ -109,20 +109,26 @@ def del_hide_sub_metadata(keys, metadata): if '_source' in hit and '_item_metadata' in hit['_source'] and hit['_source']['_item_metadata']: if 'control_number' in hit['_source']['_item_metadata']: control_number=hit['_source']['_item_metadata']['control_number'] - record = WekoRecord.get_record_by_pid(control_number) - is_admin = False - is_owner = False - roles = get_user_roles() - if roles[0]: - is_admin = True - if check_created_id(record): - is_owner = True - is_public = check_publish_status(record) - if check_created_id(record): - is_owner = True - is_public = check_publish_status(record) - if not is_public and not is_admin and not is_owner: + try: + record = WekoRecord.get_record_by_pid(control_number) + is_admin = False + is_owner = False + roles = get_user_roles() + if roles[0]: + is_admin = True + if check_created_id(record): + is_owner = True + is_public = check_publish_status(record) + if check_created_id(record): + is_owner = True + is_public = check_publish_status(record) + if not is_public and not is_admin and not is_owner: + hit['_source']['_item_metadata']={} + except Exception as e: + current_app.logger.error(f"Error in serialize_search: {e}") hit['_source']['_item_metadata']={} + + return json.dumps(dict( hits=dict( diff --git a/modules/weko-accounts/tests/test_api.py b/modules/weko-accounts/tests/test_api.py index 1566c47ab6..83e75166fe 100644 --- a/modules/weko-accounts/tests/test_api.py +++ b/modules/weko-accounts/tests/test_api.py @@ -33,7 +33,7 @@ def test_init(self): "shib_eppn":"test_eppn" } shibuser = ShibUser(attr) - assert shibuser.shib_attr == attr + assert shibuser.shib_attr == {"shib_eppn": "test_eppn"} assert shibuser.user == None assert shibuser.shib_user == None assert shibuser.is_member_of == [] @@ -46,7 +46,7 @@ def test_init(self): "https://example.com/gr/yyy"] } shibuser = ShibUser(attr) - assert shibuser.shib_attr == attr + assert shibuser.shib_attr == {"shib_eppn": "test_eppn"} assert shibuser.user == None assert shibuser.shib_user == None assert shibuser.is_member_of == ["https://example.com/gr/xxx", "https://example.com/gr/yyy"] @@ -58,7 +58,7 @@ def test_init(self): "shib_is_member_of": "https://example.com/gr/xxx" } shibuser = ShibUser(attr) - assert shibuser.shib_attr == attr + assert shibuser.shib_attr == {"shib_eppn": "test_eppn"} assert shibuser.user == None assert shibuser.shib_user == None assert shibuser.is_member_of == ["https://example.com/gr/xxx"] @@ -70,19 +70,55 @@ def test_init(self): "shib_is_member_of": "https://example.com/gr/xxx;https://example.com/gr/yyy" } shibuser = ShibUser(attr) - assert shibuser.shib_attr == attr + assert shibuser.shib_attr == {"shib_eppn": "test_eppn"} assert shibuser.user == None assert shibuser.shib_user == None assert shibuser.is_member_of == ["https://example.com/gr/xxx", "https://example.com/gr/yyy"] assert shibuser.organizations == [] + # get is_member_of, type is str and is empty + attr = { + "shib_eppn": "test_eppn", + "shib_is_member_of": "" + } + shibuser = ShibUser(attr) + assert shibuser.shib_attr == {"shib_eppn": "test_eppn"} + assert shibuser.user == None + assert shibuser.shib_user == None + assert shibuser.is_member_of == [] + assert shibuser.organizations == [] + + # get is_member_of, type is neither list nor str + attr = { + "shib_eppn": "test_eppn", + "shib_is_member_of": 123 + } + shibuser = ShibUser(attr) + assert shibuser.shib_attr == {"shib_eppn": "test_eppn"} + assert shibuser.user == None + assert shibuser.shib_user == None + assert shibuser.is_member_of == [] + assert shibuser.organizations == [] + + # get is_member_of, type is none + attr = { + "shib_eppn": "test_eppn", + "shib_is_member_of": None + } + shibuser = ShibUser(attr) + assert shibuser.shib_attr == {"shib_eppn": "test_eppn"} + assert shibuser.user == None + assert shibuser.shib_user == None + assert shibuser.is_member_of == [] + assert shibuser.organizations == [] + # get organizations and type is list attr = { "shib_eppn":"test_eppn", "shib_organization": ["Abcdef University", "Test Organization"] } shibuser = ShibUser(attr) - assert shibuser.shib_attr == attr + assert shibuser.shib_attr == {"shib_eppn": "test_eppn"} assert shibuser.user == None assert shibuser.shib_user == None assert shibuser.is_member_of == [] @@ -94,7 +130,7 @@ def test_init(self): "shib_organization": "Abcdef University" } shibuser = ShibUser(attr) - assert shibuser.shib_attr == attr + assert shibuser.shib_attr == {"shib_eppn": "test_eppn"} assert shibuser.user == None assert shibuser.shib_user == None assert shibuser.is_member_of == [] @@ -106,12 +142,47 @@ def test_init(self): "shib_organization": "Abcdef University;Test Organization" } shibuser = ShibUser(attr) - assert shibuser.shib_attr == attr + assert shibuser.shib_attr == {"shib_eppn": "test_eppn"} assert shibuser.user == None assert shibuser.shib_user == None assert shibuser.is_member_of == [] assert shibuser.organizations == ["Abcdef University", "Test Organization"] + # get organizations, type is str and is empty + attr = { + "shib_eppn": "test_eppn", + "shib_organization": "" + } + shibuser = ShibUser(attr) + assert shibuser.shib_attr == {"shib_eppn": "test_eppn"} + assert shibuser.user == None + assert shibuser.shib_user == None + assert shibuser.is_member_of == [] + assert shibuser.organizations == [] + + # get organizations, type is neither list nor str + attr = { + "shib_eppn": "test_eppn", + "shib_organization": 123 + } + shibuser = ShibUser(attr) + assert shibuser.shib_attr == {"shib_eppn": "test_eppn"} + assert shibuser.user == None + assert shibuser.shib_user == None + assert shibuser.is_member_of == [] + assert shibuser.organizations == [] + + # get organizations, type is none + attr = { + "shib_eppn": "test_eppn", + "shib_organization": None + } + shibuser = ShibUser(attr) + assert shibuser.shib_attr == {"shib_eppn": "test_eppn"} + assert shibuser.user == None + assert shibuser.shib_user == None + assert shibuser.is_member_of == [] + assert shibuser.organizations == [] # def _set_weko_user_role(self, roles): # .tox/c1/bin/pytest --cov=weko_accounts tests/test_api.py::TestShibUser::test_set_weko_user_role -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-accounts/.tox/c1/tmp diff --git a/modules/weko-accounts/tests/test_views.py b/modules/weko-accounts/tests/test_views.py index 3ba78bc5a7..3c20381c2e 100644 --- a/modules/weko-accounts/tests/test_views.py +++ b/modules/weko-accounts/tests/test_views.py @@ -1,15 +1,13 @@ - from unittest.mock import MagicMock import pytest import json import redis -from invenio_accounts.models import Role +from invenio_accounts.models import Role, User from flask import url_for,request,make_response,current_app,Flask from flask_login.utils import login_user,logout_user from flask_menu import current_menu from flask_security import url_for_security from mock import patch -from invenio_accounts.models import User from weko_accounts.api import ShibUser from weko_accounts.models import ShibbolethUser from weko_accounts.views import ( @@ -18,7 +16,8 @@ _redirect_method, find_user_by_email, shib_sp_login, - _adjust_shib_admin_DB + _adjust_shib_admin_DB, + generate_ams_login_url ) from weko_admin.models import AdminSettings @@ -26,6 +25,11 @@ def set_session(client,data): with client.session_transaction() as session: for k, v in data.items(): session[k] = v + +def del_session(client,key): + with client.session_transaction() as session: + del session[key] + #def _has_admin_access(): # .tox/c1/bin/pytest --cov=weko_accounts tests/test_views.py::test_has_admin_access -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workflow/.tox/c1/tmp #def test_has_admin_access(request_context,users): @@ -35,12 +39,12 @@ def set_session(client,data): # logout_user() # login_user(users[4]["obj"]) # result = _has_admin_access() -# assert result == False +# assert result is False #def init_menu(): # .tox/c1/bin/pytest --cov=weko_accounts tests/test_views.py::test_init_menu -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workflow/.tox/c1/tmp def test_init_menu(request_context): init_menu() - assert current_menu.submenu("setting.admin").active == True + assert current_menu.submenu("setting.admin").active is True assert current_menu.submenu("settings.admin").url == "/admin/" assert current_menu.submenu("settings.admin").text == ' Administration' @@ -96,6 +100,59 @@ def test_redirect_method(app,mocker): _redirect_method(True) mock_render.assert_called_with("http://TEST_SERVER.localdomain/secure/login.py?next="+url) + url = 'ams' + with app.test_request_context(url): + current_app.config['WEKO_ACCOUNTS_SHIB_AMS_LOGIN_URL'] = '{}ams/login' + # Login is blocked. + mock_render = mocker.patch('weko_accounts.views.redirect',return_value=make_response()) + ams_error = 'Login is blocked.' + _redirect_method(True, ams_error) + mock_render.assert_called_with(\ + 'http://TEST_SERVER.localdomain/ams/login?error=Login+is+blocked.') + # There is no user information. + mock_render = mocker.patch('weko_accounts.views.redirect',return_value=make_response()) + ams_error = 'There is no user information.' + _redirect_method(True, ams_error) + mock_render.assert_called_with( + 'http://TEST_SERVER.localdomain/ams/login?error=There+is+no+user+information.') + # server error + mock_render = mocker.patch('weko_accounts.views.redirect',return_value=make_response()) + ams_error = 'Server error has occurred. Please contact server administrator.' + _redirect_method(True, ams_error) + mock_render.assert_called_with(\ + 'http://TEST_SERVER.localdomain/ams/login?'\ + 'error=Server+error+has+occurred.+Please+contact+server+administrator.') + # other error + mock_render = mocker.patch('weko_accounts.views.redirect',return_value=make_response()) + ams_error = 'Error Message' + _redirect_method(True, ams_error) + mock_render.assert_called_with(\ + 'http://TEST_SERVER.localdomain/ams/login?error=Error+Message') + +# .tox/c1/bin/pytest --cov=weko_accounts tests/test_views.py::test_generate_ams_login_url -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workflow/.tox/c1/tmp +def test_generate_ams_login_url(app): + with app.test_request_context(): + current_app.config['WEKO_ACCOUNTS_SHIB_AMS_LOGIN_URL'] = '{}ams/login' + # Missing Shib-Session-ID! + ams_error = 'Missing Shib-Session-ID!' + url = generate_ams_login_url(ams_error) + assert url == '/ams/login?error=Missing+Shib-Session-ID%21' + + # Missing SHIB_ATTRs! + ams_error = 'Missing SHIB_ATTRs!' + url = generate_ams_login_url(ams_error) + assert url == '/ams/login?error=Missing+SHIB_ATTRs%21' + + # Login is blocked. + ams_error = 'Login is blocked.' + url = generate_ams_login_url(ams_error) + assert url == '/ams/login?error=Login+is+blocked.' + + # Server error has occurred. Please contact server administrator. + ams_error = 'Server error has occurred. Please contact server administrator.' + url = generate_ams_login_url(ams_error) + assert url == '/ams/login?error=Server+error+has+occurred.+Please+contact+server+administrator.' + #def index(): # .tox/c1/bin/pytest --cov=weko_accounts tests/test_views.py::test_index -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workflow/.tox/c1/tmp def test_index(client,mocker): @@ -109,53 +166,101 @@ def test_shib_auto_login(client,redis_connect,mocker): url = url_for("weko_accounts.shib_auto_login") set_session(client,{"shib_session_id":None}) # not exist shib_session_id - mock_redirect_ = mocker.patch("weko_accounts.views._redirect_method",return_value=make_response()) + mock_redirect_ = mocker.patch("weko_accounts.views._redirect_method", + return_value=make_response()) client.get(url) mock_redirect_.assert_called_once() + # not exist shib_session_id(AMS) + mock_redirect_ = mocker.patch("weko_accounts.views._redirect_method", + return_value=make_response()) + client.get(url+"?next=ams") + mock_redirect_.assert_called_once() + called_args, called_kwargs = mock_redirect_.call_args + assert called_args[0] is True + assert "Missing Shib-Session-ID!" in called_kwargs.get("ams_error", "") - mocker.patch("weko_accounts.views.RedisConnection.connection",return_value=redis_connect) # not exist cache - mock_redirect_ = mocker.patch("weko_accounts.views._redirect_method",return_value=make_response()) + mocker.patch("weko_accounts.views.RedisConnection.connection", + return_value=redis_connect) + mock_redirect_ = mocker.patch("weko_accounts.views._redirect_method", + return_value=make_response()) client.get(url+"?Shib-Session-ID=2222") mock_redirect_.assert_called_once() + # not exist cache(AMS) + mocker.patch("weko_accounts.views.RedisConnection.connection", + return_value=redis_connect) + mock_redirect_ = mocker.patch("weko_accounts.views._redirect_method", + return_value=make_response()) + client.get(url+"?next=ams&Shib-Session-ID=2222") + mock_redirect_.assert_called_once() + called_args, called_kwargs = mock_redirect_.call_args + assert called_args[0] is True + assert "Missing SHIB_CACHE_PREFIX!" in called_kwargs.get("ams_error", "") - - redis_connect.put("Shib-Session-1111",bytes("","utf-8")) # not cache_val - mock_redirect_ = mocker.patch("weko_accounts.views._redirect_method",return_value=make_response()) + redis_connect.put("Shib-Session-1111",bytes("","utf-8")) + mock_redirect_ = mocker.patch("weko_accounts.views._redirect_method", + return_value=make_response()) client.get(url+"?Shib-Session-ID=1111") mock_redirect_.assert_called_once() - assert redis_connect.redis.exists("Shib-Session-1111") == False + assert redis_connect.redis.exists("Shib-Session-1111") is False + + # not cache_val(AMS) + redis_connect.put("Shib-Session-1111",bytes("","utf-8")) + mock_redirect_ = mocker.patch("weko_accounts.views._redirect_method", + return_value=make_response()) + client.get(url+"?next=ams&Shib-Session-ID=1111") + mock_redirect_.assert_called_once() + called_args, called_kwargs = mock_redirect_.call_args + assert called_args[0] is True + assert "Missing SHIB_ATTR!" in called_kwargs.get("ams_error", "") + # is_auto_bind is false, check_in is error mock_get_relation = mocker.patch("weko_accounts.views.ShibUser.get_relation_info") mock_new_relation = mocker.patch("weko_accounts.views.ShibUser.new_relation_info") mock_shib_login = mocker.patch("weko_accounts.views.ShibUser.shib_user_login") redis_connect.put("Shib-Session-1111",bytes('{"shib_eppn":"test_eppn"}',"utf-8")) - # is_auto_bind is false, check_in is error - mock_redirect_ = mocker.patch("weko_accounts.views._redirect_method",return_value=make_response()) + mock_redirect_ = mocker.patch("weko_accounts.views._redirect_method", + return_value=make_response()) with patch("weko_accounts.views.ShibUser.check_in",return_value="test_error"): client.get(url+"?Shib-Session-ID=1111") mock_get_relation.assert_called_once() mock_redirect_.assert_called_once() - assert redis_connect.redis.exists("Shib-Session-1111") == False + assert redis_connect.redis.exists("Shib-Session-1111") is False + + # is_auto_bind is false, check_in is error(AMS) + mock_get_relation = mocker.patch("weko_accounts.views.ShibUser.get_relation_info") + mock_new_relation = mocker.patch("weko_accounts.views.ShibUser.new_relation_info") + mock_shib_login = mocker.patch("weko_accounts.views.ShibUser.shib_user_login") redis_connect.put("Shib-Session-1111",bytes('{"shib_eppn":"test_eppn"}',"utf-8")) + mock_redirect_ = mocker.patch("weko_accounts.views._redirect_method", + return_value=make_response()) + with patch("weko_accounts.views.ShibUser.check_in",return_value="test_error"): + client.get(url+"?next=ams&Shib-Session-ID=1111") + mock_get_relation.assert_called_once() + mock_redirect_.assert_called_once() + called_args, called_kwargs = mock_redirect_.call_args + assert called_args[0] is True + assert "test_error" in called_kwargs.get("ams_error", "") + redis_connect.put("Shib-Session-1111",bytes('{"shib_eppn":"test_eppn"}',"utf-8")) set_session(client,{"shib_session_id":"1111"}) with patch("weko_accounts.views.ShibUser.check_in",return_value=None): # is_auto_bind is true,shib_user is None shibuser = ShibUser({}) shibuser.user = User(id=1) with patch("weko_accounts.views.ShibUser",return_value=shibuser): - mock_redirect = mocker.patch("weko_accounts.views.redirect",return_value=make_response()) + mock_redirect = mocker.patch("weko_accounts.views.redirect", + return_value=make_response()) client.get(url) mock_new_relation.assert_called_once() mock_shib_login.assert_not_called() mock_redirect.assert_called_with("/") - assert redis_connect.redis.exists("Shib-Session-1111") == False + assert redis_connect.redis.exists("Shib-Session-1111") is False # is_auto_bind is true,shib_user exis redis_connect.put("Shib-Session-1111",bytes('{"shib_eppn":"test_eppn"}',"utf-8")) @@ -165,19 +270,50 @@ def test_shib_auto_login(client,redis_connect,mocker): shibuser.shib_user = "test_user" shibuser.user = User(id=1) with patch("weko_accounts.views.ShibUser",return_value=shibuser): - mock_redirect = mocker.patch("weko_accounts.views.redirect",return_value=make_response()) + mock_redirect = mocker.patch("weko_accounts.views.redirect", + return_value=make_response()) client.get(url+'?next=/next_page') mock_redirect.assert_called_with("/next_page") mock_shib_login.assert_called_once() - assert redis_connect.redis.exists("Shib-Session-1111") == False + assert redis_connect.redis.exists("Shib-Session-1111") is False + + # is_auto_bind is true,shib_user exis(AMS) + mock_shib_login.reset_mock() + redis_connect.put("Shib-Session-1111",bytes('{"shib_eppn":"test_eppn"}',"utf-8")) + set_session(client,{"shib_session_id":"1111","next":"/next_page"}) + + shibuser = ShibUser({}) + shibuser.shib_user = "test_user" + shibuser.user = User(id=1) + with patch("weko_accounts.views.ShibUser",return_value=shibuser): + mock_redirect = mocker.patch("weko_accounts.views.redirect", + return_value=make_response()) + client.get(url+'?next=ams') + called_args, _ = mock_redirect.call_args + mock_redirect.assert_called_with("/?next=ams") + mock_shib_login.assert_called_once() + assert redis_connect.redis.exists("Shib-Session-1111") is False + # raise BaseException with patch("weko_accounts.views.RedisConnection",side_effect=BaseException("test_error")): res = client.get(url+"?Shib-Session-ID=1111") assert res.status_code == 400 + + mock_redirect_ = mocker.patch("weko_accounts.views._redirect_method", + return_value=make_response()) + with patch("weko_accounts.views.RedisConnection",side_effect=BaseException("test_error")): + res = client.get(url+"?next=ams&Shib-Session-ID=1111") + mock_redirect_.assert_called_once() + called_args, called_kwargs = mock_redirect_.call_args + assert called_args[0] is True + assert "Server error has occurred. Please contact server " \ + "administrator." in called_kwargs.get("ams_error", "") + #def confirm_user(): # .tox/c1/bin/pytest --cov=weko_accounts tests/test_views.py::test_confirm_user -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workflow/.tox/c1/tmp def test_confirm_user(client,redis_connect,mocker): - mocker.patch("weko_accounts.views.RedisConnection.connection",return_value=redis_connect) + mocker.patch("weko_accounts.views.RedisConnection.connection", + return_value=redis_connect) mocker.patch("weko_accounts.views.ShibUser.shib_user_login") url = url_for("weko_accounts.confirm_user") @@ -188,19 +324,53 @@ def test_confirm_user(client,redis_connect,mocker): client.post(url,data=form) mock_flash.assert_called_with("csrf_random",category="error") + # not correct csrf_random(AMS) + form = {"csrf_random":"test_csrf"} + mock_redirect_ = mocker.patch("weko_accounts.views._redirect_method", + return_value=make_response()) + set_session(client,{"next": "ams"}) + client.post(url, data=form) + mock_redirect_.assert_called_once() + called_args, called_kwargs = mock_redirect_.call_args + assert called_args[0] is True + assert "csrf_random" in called_kwargs.get("ams_error", "") + # not exist shib_session_id set_session(client,{"csrf_random":"test_csrf","shib_session_id":None}) + del_session(client, "next") mock_flash = mocker.patch("weko_accounts.views.flash") client.post(url,data=form) mock_flash.assert_called_with("shib_session_id",category="error") + # not exist shib_session_id(AMS) + mock_redirect_ = mocker.patch("weko_accounts.views._redirect_method", + return_value=make_response()) + set_session(client,{"next": "ams"}) + client.post(url, data=form) + mock_redirect_.assert_called_once() + called_args, called_kwargs = mock_redirect_.call_args + assert called_args[0] is True + assert "Missing Shib-Session-ID!" in called_kwargs.get("ams_error", "") + # not exist cache_key set_session(client,{"csrf_random":"test_csrf","shib_session_id":"2222"}) + del_session(client, "next") mock_flash = mocker.patch("weko_accounts.views.flash") client.post(url,data=form) mock_flash.assert_called_with("cache_key",category="error") + # not exist cache_key(AMS) + mock_redirect_ = mocker.patch("weko_accounts.views._redirect_method", + return_value=make_response()) + set_session(client,{"next": "ams"}) + client.post(url, data=form) + mock_redirect_.assert_called_once() + called_args, called_kwargs = mock_redirect_.call_args + assert called_args[0] is True + assert "Missing SHIB_CACHE_PREFIX!" in called_kwargs.get("ams_error", "") + set_session(client,{"csrf_random":"test_csrf","shib_session_id":"1111"}) + del_session(client, "next") # not exist cache_value redis_connect.put("Shib-Session-1111",bytes("","utf-8")) mock_flash = mocker.patch("weko_accounts.views.flash") @@ -208,20 +378,45 @@ def test_confirm_user(client,redis_connect,mocker): mock_flash.assert_called_with("cache_val",category="error") assert redis_connect.redis.exists("Shib-Session-1111") is False + # not exist cache_value(AMS) + redis_connect.put("Shib-Session-1111",bytes("","utf-8")) + mock_redirect_ = mocker.patch("weko_accounts.views._redirect_method", + return_value=make_response()) + set_session(client,{"next": "ams"}) + client.post(url, data=form) + mock_redirect_.assert_called_once() + called_args, called_kwargs = mock_redirect_.call_args + assert called_args[0] is True + assert "Missing SHIB_ATTR!" in called_kwargs.get("ams_error", "") + # shib_user.check_weko_user is false redis_connect.put("Shib-Session-1111",bytes('{"shib_eppn":"test_eppn"}',"utf-8")) with patch("weko_accounts.views.ShibUser.check_weko_user",return_value=False): mock_flash = mocker.patch("weko_accounts.views.flash") + del_session(client, "next") client.post(url,data=form) mock_flash.assert_called_with("check_weko_user",category="error") assert redis_connect.redis.exists("Shib-Session-1111") is False + # shib_user.check_weko_user is false(AMS) + redis_connect.put("Shib-Session-1111",bytes('{"shib_eppn":"test_eppn"}',"utf-8")) + with patch("weko_accounts.views.ShibUser.check_weko_user",return_value=False): + mock_redirect_ = mocker.patch("weko_accounts.views._redirect_method", + return_value=make_response()) + set_session(client, {"next": "ams"}) + client.post(url,data=form) + mock_redirect_.assert_called_once() + called_args, called_kwargs = mock_redirect_.call_args + assert called_args[0] is True + assert "There is no user information." in called_kwargs.get("ams_error", "") + redis_connect.put("Shib-Session-1111",bytes('{"shib_eppn":"test_eppn"}',"utf-8")) with patch("weko_accounts.views.ShibUser.check_weko_user",return_value=True): # shib_user.bind_relation_info is false with patch("weko_accounts.views.ShibUser.bind_relation_info",return_value=False): redis_connect.put("Shib-Session-1111",bytes('{"shib_eppn":"test_eppn"}',"utf-8")) mock_flash = mocker.patch("weko_accounts.views.flash") + del_session(client, "next") client.post(url,data=form) mock_flash.assert_called_with("FAILED bind_relation_info!",category="error") with patch("weko_accounts.views.ShibUser.bind_relation_info",return_value=True): @@ -236,35 +431,87 @@ def test_confirm_user(client,redis_connect,mocker): redis_connect.put("Shib-Session-1111",bytes('{"shib_eppn":"test_eppn"}',"utf-8")) shibuser = ShibUser({}) shibuser.user = User(id=1) - with patch("weko_accounts.views.ShibUser",return_value=shibuser): - mock_redirect = mocker.patch("weko_accounts.views.redirect",return_value=make_response()) + with patch("weko_accounts.views.ShibUser", + return_value=shibuser): + mock_redirect = mocker.patch("weko_accounts.views.redirect", + return_value=make_response()) client.post(url,data=form) mock_redirect.assert_called_with("/") assert redis_connect.redis.exists("Shib-Session-1111") is False - # exist ShibUser.shib_user - set_session(client,{"csrf_random":"test_csrf","shib_session_id":"1111","next":"/next_page"}) - redis_connect.put("Shib-Session-1111",bytes('{"shib_eppn":"test_eppn"}',"utf-8")) - + # exist ShibUser.shib_user + set_session(client,{"csrf_random":"test_csrf", + "shib_session_id":"1111","next":"/next_page"}) + redis_connect.put("Shib-Session-1111",bytes('{"shib_eppn":"test_eppn"}',"utf-8")) shibuser = ShibUser({}) shibuser.shib_user = "test_user" shibuser.user = User(id=1) with patch("weko_accounts.views.ShibUser",return_value=shibuser): - mock_redirect = mocker.patch("weko_accounts.views.redirect",return_value=make_response()) + mock_redirect = mocker.patch("weko_accounts.views.redirect", + return_value=make_response()) mock_flash = mocker.patch("weko_accounts.views.flash") client.post(url,data=form) mock_redirect.assert_called_with("/next_page") assert redis_connect.redis.exists("Shib-Session-1111") is False + redis_connect.put("Shib-Session-1111",bytes('{"shib_eppn":"test_eppn"}',"utf-8")) + with patch("weko_accounts.views.ShibUser.check_weko_user",return_value=True): + # shib_user.bind_relation_info is false + with patch("weko_accounts.views.ShibUser.bind_relation_info",return_value=False): + redis_connect.put("Shib-Session-1111",bytes('{"shib_eppn":"test_eppn"}',"utf-8")) + mock_redirect_ = mocker.patch("weko_accounts.views._redirect_method", + return_value=make_response()) + set_session(client, {"next": "ams"}) + client.post(url,data=form) + mock_redirect_.assert_called_once() + called_args, called_kwargs = mock_redirect_.call_args + assert called_args[0] is True + assert "FAILED bind_relation_info!" in called_kwargs.get("ams_error", "") + with patch("weko_accounts.views.ShibUser.bind_relation_info",return_value=True): + # ShibUser.check_in is error + with patch("weko_accounts.views.ShibUser.check_in",return_value="test_error"): + mock_redirect_ = mocker.patch("weko_accounts.views._redirect_method", + return_value=make_response()) + client.post(url,data=form) + mock_redirect_.assert_called_once() + called_args, called_kwargs = mock_redirect_.call_args + assert called_args[0] is True + assert "test_error" in called_kwargs.get("ams_error", "") + with patch("weko_accounts.views.ShibUser.check_in",return_value=None): + # ShibUser.shib_user is None,not exist next in session + redis_connect.put("Shib-Session-1111",bytes('{"shib_eppn":"test_eppn"}',"utf-8")) + shibuser = ShibUser({}) + shibuser.user = User(id=1) + with patch("weko_accounts.views.ShibUser",return_value=shibuser): + mock_redirect = mocker.patch("weko_accounts.views.redirect", + return_value=make_response()) + client.post(url,data=form) + called_args, _ = mock_redirect.call_args + mock_redirect.assert_called_with("/?next=ams") + assert redis_connect.redis.exists("Shib-Session-1111") is False + # raise BaseException with patch("weko_accounts.views._redirect_method",side_effect=BaseException("test_error")): + del_session(client, "next") res = client.post(url,data=form) assert res.status_code == 400 + mock_redirect_ = mocker.patch("weko_accounts.views._redirect_method", + return_value=make_response()) + with patch("weko_accounts.views.RedisConnection",side_effect=BaseException("test_error")): + set_session(client, {"next": "ams"}) + res = client.post(url,data=form) + mock_redirect_.assert_called_once() + called_args, called_kwargs = mock_redirect_.call_args + assert called_args[0] is True + assert "Server error has occurred. Please contact server " \ + "administrator." in called_kwargs.get("ams_error", "") + #def confirm_user_without_page(): # .tox/c1/bin/pytest --cov=weko_accounts tests/test_views.py::test_confirm_user_without_page -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workflow/.tox/c1/tmp def test_confirm_user_without_page(client,redis_connect,mocker): - mocker.patch("weko_accounts.views.RedisConnection.connection",return_value=redis_connect) + mocker.patch("weko_accounts.views.RedisConnection.connection", + return_value=redis_connect) mocker.patch("weko_accounts.views.ShibUser.shib_user_login") url = url_for("weko_accounts.confirm_user_without_page") @@ -273,11 +520,29 @@ def test_confirm_user_without_page(client,redis_connect,mocker): client.get(url, query_string={"Shib-Session-ID":None}) mock_flash.assert_called_with("shib_session_id",category="error") + # not exist shib_session_id(AMS) + mock_redirect_ = mocker.patch("weko_accounts.views._redirect_method", + return_value=make_response()) + client.get(url+"?next=ams") + mock_redirect_.assert_called_once() + called_args, called_kwargs = mock_redirect_.call_args + assert called_args[0] is True + assert "Missing Shib-Session-ID!" in called_kwargs.get("ams_error", "") + # not exist cache_key mock_flash = mocker.patch("weko_accounts.views.flash") client.get(url, query_string={"Shib-Session-ID":"2222"}) mock_flash.assert_called_with("cache_key",category="error") + # not exist cache_key(AMS) + mock_redirect_ = mocker.patch("weko_accounts.views._redirect_method", + return_value=make_response()) + client.get(url, query_string={"next":"ams","Shib-Session-ID":"2222"}) + mock_redirect_.assert_called_once() + called_args, called_kwargs = mock_redirect_.call_args + assert called_args[0] is True + assert "Missing SHIB_CACHE_PREFIX!" in called_kwargs.get("ams_error", "") + # not exist cache_value redis_connect.put("Shib-Session-1111",bytes("","utf-8")) mock_flash = mocker.patch("weko_accounts.views.flash") @@ -285,6 +550,16 @@ def test_confirm_user_without_page(client,redis_connect,mocker): mock_flash.assert_called_with("cache_val",category="error") assert redis_connect.redis.exists("Shib-Session-1111") is False + # not exist cache_value(AMS) + redis_connect.put("Shib-Session-1111",bytes("","utf-8")) + mock_redirect_ = mocker.patch("weko_accounts.views._redirect_method", + return_value=make_response()) + client.get(url, query_string={"next":"ams","Shib-Session-ID":"1111"}) + mock_redirect_.assert_called_once() + called_args, called_kwargs = mock_redirect_.call_args + assert called_args[0] is True + assert "Missing SHIB_ATTR!" in called_kwargs.get("ams_error", "") + redis_connect.put("Shib-Session-1111",bytes('{"shib_eppn":"test_eppn"}',"utf-8")) with patch("weko_accounts.views.ShibUser.check_weko_user",return_value=True): # shib_user.bind_relation_info is false @@ -305,7 +580,8 @@ def test_confirm_user_without_page(client,redis_connect,mocker): with patch("weko_accounts.views.ShibUser.check_in",return_value=None): # ShibUser.shib_user is None,not exist next in session redis_connect.put("Shib-Session-1111",bytes('{"shib_eppn":"test_eppn"}',"utf-8")) - mock_redirect = mocker.patch("weko_accounts.views.redirect",return_value=make_response()) + mock_redirect = mocker.patch("weko_accounts.views.redirect", + return_value=make_response()) client.get(url, query_string={"Shib-Session-ID":"1111"}) mock_redirect.assert_called_with("/") assert redis_connect.redis.exists("Shib-Session-1111") is False @@ -316,7 +592,8 @@ def test_confirm_user_without_page(client,redis_connect,mocker): shibuser = ShibUser({}) shibuser.shib_user = "test_user" with patch("weko_accounts.views.ShibUser",return_value=shibuser): - mock_redirect = mocker.patch("weko_accounts.views.redirect",return_value=make_response()) + mock_redirect = mocker.patch("weko_accounts.views.redirect", + return_value=make_response()) mock_flash = mocker.patch("weko_accounts.views.flash") client.get(url, query_string={"Shib-Session-ID":"1111"}) mock_redirect.assert_called_with("/") @@ -328,22 +605,96 @@ def test_confirm_user_without_page(client,redis_connect,mocker): shibuser = ShibUser({}) shibuser.shib_user = "test_user" with patch("weko_accounts.views.ShibUser",return_value=shibuser): - mock_redirect = mocker.patch("weko_accounts.views.redirect",return_value=make_response()) + mock_redirect = mocker.patch("weko_accounts.views.redirect", + return_value=make_response()) mock_flash = mocker.patch("weko_accounts.views.flash") client.get(url, query_string={"Shib-Session-ID":"1111","next":"/next_page"}) mock_redirect.assert_called_with("/next_page") assert redis_connect.redis.exists("Shib-Session-1111") is False + redis_connect.put("Shib-Session-1111",bytes('{"shib_eppn":"test_eppn"}',"utf-8")) + with patch("weko_accounts.views.ShibUser.check_weko_user",return_value=True): + # shib_user.bind_relation_info is false + with patch("weko_accounts.views.ShibUser.bind_relation_info",return_value=False): + redis_connect.put("Shib-Session-1111",bytes('{"shib_eppn":"test_eppn"}',"utf-8")) + mock_redirect_ = mocker.patch("weko_accounts.views._redirect_method", + return_value=make_response()) + client.get(url, query_string={"next":"ams","Shib-Session-ID":"1111"}) + mock_redirect_.assert_called_once() + called_args, called_kwargs = mock_redirect_.call_args + assert called_args[0] is True + assert "FAILED bind_relation_info!" in called_kwargs.get("ams_error", "") + with patch("weko_accounts.views.ShibUser.bind_relation_info",return_value=True): + # ShibUser.check_in is error + with patch("weko_accounts.views.ShibUser.check_in",return_value="test_error"): + redis_connect.put("Shib-Session-1111",bytes('{"shib_eppn":"test_eppn"}',"utf-8")) + mock_redirect_ = mocker.patch("weko_accounts.views._redirect_method", + return_value=make_response()) + client.get(url, query_string={"next":"ams","Shib-Session-ID":"1111"}) + mock_redirect_.assert_called_once() + called_args, called_kwargs = mock_redirect_.call_args + assert called_args[0] is True + assert "test_error" in called_kwargs.get("ams_error", "") + with patch("weko_accounts.views.ShibUser.check_in",return_value=None): + # ShibUser.shib_user is None,not exist next in session + redis_connect.put("Shib-Session-1111",bytes('{"shib_eppn":"test_eppn"}',"utf-8")) + shibuser = ShibUser({}) + shibuser.user = User(id=1) + with patch("weko_accounts.views.ShibUser",return_value=shibuser): + mock_redirect = mocker.patch("weko_accounts.views.redirect", + return_value=make_response()) + client.get(url, query_string={"next":"ams","Shib-Session-ID":"1111"}) + called_args, _ = mock_redirect.call_args + mock_redirect.assert_called_with("/?next=ams") + assert redis_connect.redis.exists("Shib-Session-1111") is False + + # exist ShibUser.shib_user + redis_connect.put("Shib-Session-1111",bytes('{"shib_eppn":"test_eppn"}',"utf-8")) + + shibuser = ShibUser({}) + shibuser.shib_user = "test_user" + shibuser.user = User(id=1) + with patch("weko_accounts.views.ShibUser",return_value=shibuser): + mock_redirect = mocker.patch("weko_accounts.views.redirect", + return_value=make_response()) + client.get(url, query_string={"next":"ams","Shib-Session-ID":"1111"}) + called_args, _ = mock_redirect.call_args + mock_redirect.assert_called_with("/?next=ams") + assert redis_connect.redis.exists("Shib-Session-1111") is False + + # exist ShibUser.shib_user + redis_connect.put("Shib-Session-1111",bytes('{"shib_eppn":"test_eppn"}',"utf-8")) + shibuser = ShibUser({}) + shibuser.shib_user = "test_user" + shibuser.user = User(id=1) + with patch("weko_accounts.views.ShibUser",return_value=shibuser): + mock_redirect = mocker.patch("weko_accounts.views.redirect", + return_value=make_response()) + client.get(url, query_string={"next":"ams","Shib-Session-ID":"1111"}) + called_args, _ = mock_redirect.call_args + mock_redirect.assert_called_with("/?next=ams") + assert redis_connect.redis.exists("Shib-Session-1111") is False + # raise BaseException with patch("weko_accounts.views._redirect_method",side_effect=BaseException("test_error")): res = client.get(url) assert res.status_code == 400 + mock_redirect_ = mocker.patch("weko_accounts.views._redirect_method", + return_value=make_response()) + with patch("weko_accounts.views.RedisConnection",side_effect=BaseException("test_error")): + client.get(url, query_string={"next":"ams","Shib-Session-ID":"1111"}) + mock_redirect_.assert_called_once() + called_args, called_kwargs = mock_redirect_.call_args + assert called_args[0] is True + assert "Server error has occurred. Please contact server " \ + "administrator." in called_kwargs.get("ams_error", "") #def shib_login(): # .tox/c1/bin/pytest --cov=weko_accounts tests/test_views.py::test_shib_login -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workflow/.tox/c1/tmp def test_shib_login(client,redis_connect,users,mocker): - mocker.patch("weko_accounts.views.RedisConnection.connection",return_value=redis_connect) + mocker.patch("weko_accounts.views.RedisConnection.connection", + return_value=redis_connect) mocker.patch("weko_accounts.views.generate_random_str",return_value="asdfghjkl") url_base = url_for("weko_accounts.shib_login") @@ -352,13 +703,32 @@ def test_shib_login(client,redis_connect,users,mocker): client.get(url_base) mock_flash.assert_called_with("Missing Shib-Session-ID!",category="error") - url = url_base+"?Shib-Session-ID=2222" + # not shib_session_id(AMS) + mock_redirect_ = mocker.patch("weko_accounts.views._redirect_method", + return_value=make_response()) + client.get(url_base+"?next=ams") + mock_redirect_.assert_called_once() + called_args, called_kwargs = mock_redirect_.call_args + assert called_args[0] is True + assert "Missing Shib-Session-ID!" in called_kwargs.get("ams_error", "") + url = url_base+"?Shib-Session-ID=2222" # not exist cache_key mock_flash = mocker.patch("weko_accounts.views.flash") client.get(url) mock_flash.assert_called_with("Missing SHIB_CACHE_PREFIX!",category="error") + # not exist cache_key(AMS) + mocker.patch("weko_accounts.views.RedisConnection.connection", + return_value=redis_connect) + mock_redirect_ = mocker.patch("weko_accounts.views._redirect_method", + return_value=make_response()) + client.get(url+"&next=ams") + mock_redirect_.assert_called_once() + called_args, called_kwargs = mock_redirect_.call_args + assert called_args[0] is True + assert "Missing SHIB_CACHE_PREFIX!" in called_kwargs.get("ams_error", "") + url = url_base+"?Shib-Session-ID=1111" # not cache_val redis_connect.put("Shib-Session-1111",bytes('',"utf-8")) @@ -367,23 +737,50 @@ def test_shib_login(client,redis_connect,users,mocker): mock_flash.assert_called_with("Missing SHIB_ATTR!",category="error") assert redis_connect.redis.exists("Shib-Session-1111") is False + # not cache_val(AMS) + redis_connect.put("Shib-Session-1111",bytes('',"utf-8")) + mock_redirect_ = mocker.patch("weko_accounts.views._redirect_method", + return_value=make_response()) + client.get(url+"&next=ams") + mock_redirect_.assert_called_once() + called_args, called_kwargs = mock_redirect_.call_args + assert called_args[0] is True + assert "Missing SHIB_ATTR!" in called_kwargs.get("ams_error", "") + # exist user - redis_connect.put("Shib-Session-1111",bytes('{"shib_eppn":"test_eppn","shib_mail":"user@test.org"}',"utf-8")) - mock_render = mocker.patch("weko_accounts.views.render_template",return_value=make_response()) + redis_connect.put("Shib-Session-1111", + bytes('{"shib_eppn":"test_eppn","shib_mail":"user@test.org"}',"utf-8")) + mock_render = mocker.patch("weko_accounts.views.render_template", + return_value=make_response()) client.get(url) - mock_render.assert_called_with('weko_accounts/confirm_user.html',csrf_random="asdfghjkl",email="user@test.org") + mock_render.assert_called_with('weko_accounts/confirm_user.html', + csrf_random="asdfghjkl",email="user@test.org") # not exist user - redis_connect.put("Shib-Session-1111",bytes('{"shib_eppn":"test_eppn","shib_mail":"not_exist_user@test.org"}',"utf-8")) - mock_render = mocker.patch("weko_accounts.views.render_template",return_value=make_response()) + redis_connect.put("Shib-Session-1111", + bytes('{"shib_eppn":"test_eppn",' \ + '"shib_mail":"not_exist_user@test.org"}',"utf-8")) + mock_render = mocker.patch("weko_accounts.views.render_template", + return_value=make_response()) client.get(url) - mock_render.assert_called_with('weko_accounts/confirm_user.html',csrf_random="asdfghjkl",email="") + mock_render.assert_called_with('weko_accounts/confirm_user.html', + csrf_random="asdfghjkl",email="") # raise BaseException with patch("weko_accounts.views.flash",side_effect=BaseException("test_error")): res = client.get(url_base) assert res.status_code == 400 + mock_redirect_ = mocker.patch("weko_accounts.views._redirect_method", + return_value=make_response()) + with patch("weko_accounts.views.RedisConnection",side_effect=BaseException("test_error")): + res = client.get(url_base+"?Shib-Session-ID=1111&next=ams") + mock_redirect_.assert_called_once() + called_args, called_kwargs = mock_redirect_.call_args + assert called_args[0] is True + assert "Server error has occurred. Please contact server " \ + "administrator." in called_kwargs.get("ams_error", "") + #def shib_sp_login(): # .tox/c1/bin/pytest --cov=weko_accounts tests/test_views.py::test_shib_sp_login -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workflow/.tox/c1/tmp def test_shib_sp_login(client, redis_connect,mocker, db, users): @@ -397,6 +794,14 @@ def test_shib_sp_login(client, redis_connect,mocker, db, users): mock_flash.assert_called_with("Missing Shib-Session-ID!",category="error") mock_redirect.assert_called_with(url_for("security.login")) + # not shib_session_id(AMS) + mock_generate_ams_login_url = mocker.patch("weko_accounts.views.generate_ams_login_url", + return_value=make_response()) + client.post(url+"?next=ams") + mock_generate_ams_login_url.assert_called_once() + called_args, _ = mock_generate_ams_login_url.call_args + assert "Missing Shib-Session-ID!" in called_args[0] + current_app.config.update( WEKO_ACCOUNTS_SHIB_LOGIN_ENABLED=True ) @@ -410,6 +815,15 @@ def test_shib_sp_login(client, redis_connect,mocker, db, users): client.post(url,data=form) mock_flash.assert_called_with("Missing SHIB_ATTRs!",category="error") + # parse_attribute is error(AMS) + with patch("weko_accounts.views.parse_attributes",return_value=("attr",True)): + mock_generate_ams_login_url = mocker.patch("weko_accounts.views.generate_ams_login_url", + return_value=make_response()) + client.post(url+"?next=ams",data=form) + mock_generate_ams_login_url.assert_called_once() + called_args, _ = mock_generate_ams_login_url.call_args + assert "Missing SHIB_ATTRs!" in called_args[0] + # Check if shib_eppn is not included in the blocked user list try: db.session.add(AdminSettings( @@ -418,7 +832,7 @@ def test_shib_sp_login(client, redis_connect,mocker, db, users): settings='{"blocked_ePPNs": ["ePPN1", "ePPN2", "ePPN3", "ePPN5", "ePPP*"]}' )) db.session.commit() - except Exception as e: + except Exception: db.session.rollback() raise finally: @@ -432,7 +846,16 @@ def test_shib_sp_login(client, redis_connect,mocker, db, users): } client.post(url,data=form) mock_flash.assert_called_with("Failed to login.",category="error") - mock_redirect_ = mocker.patch("weko_accounts.views._redirect_method",return_value=make_response()) + mock_redirect_ = mocker.patch("weko_accounts.views._redirect_method", + return_value=make_response()) + + # Match with blocked user(AMS) + mock_generate_ams_login_url = mocker.patch("weko_accounts.views.generate_ams_login_url", + return_value=make_response()) + client.post(url+"?next=ams",data=form) + mock_generate_ams_login_url.assert_called_once() + called_args, _ = mock_generate_ams_login_url.call_args + assert "Login is blocked." in called_args[0] # Match found with a blocked user from the wildcard mock_flash = mocker.patch("weko_accounts.views.flash") @@ -442,7 +865,14 @@ def test_shib_sp_login(client, redis_connect,mocker, db, users): } client.post(url,data=form) mock_flash.assert_called_with("Failed to login.",category="error") - mock_redirect_ = mocker.patch("weko_accounts.views._redirect_method",return_value=make_response()) + + # Match found with a blocked user from the wildcard(AMS) + mock_generate_ams_login_url = mocker.patch("weko_accounts.views.generate_ams_login_url", + return_value=make_response()) + client.post(url+"?next=ams",data=form) + mock_generate_ams_login_url.assert_called_once() + called_args, _ = mock_generate_ams_login_url.call_args + assert "Login is blocked." in called_args[0] # Not a blocked user form = { @@ -450,17 +880,24 @@ def test_shib_sp_login(client, redis_connect,mocker, db, users): "eppn":"test_eppn" } + mock_redirect_ = mocker.patch("weko_accounts.views._redirect_method", + return_value=make_response()) # WEKO_ACCOUNTS_SHIB_BIND_GAKUNIN_MAP_GROUPSがTrueの場合のテスト current_app.config.update( WEKO_ACCOUNTS_SHIB_BIND_GAKUNIN_MAP_GROUPS=True ) - mock_sync_shib_gakunin_map_groups = mocker.patch("weko_accounts.views.sync_shib_gakunin_map_groups", return_value=None) + mock_sync_shib_gakunin_map_groups = \ + mocker.patch("weko_accounts.views.sync_shib_gakunin_map_groups", + return_value=None) client.post(url, data=form) mock_sync_shib_gakunin_map_groups.assert_called_once() # sync_shib_gakunin_map_groupsが例外をスローする場合のテスト - mock_sync_shib_gakunin_map_groups = mocker.patch("weko_accounts.views.sync_shib_gakunin_map_groups", side_effect=Exception("test_exception")) - mock_redirect_method = mocker.patch("weko_accounts.views._redirect_method", return_value=make_response()) + mock_sync_shib_gakunin_map_groups = mocker.patch( + "weko_accounts.views.sync_shib_gakunin_map_groups", + side_effect=Exception("test_exception")) + mock_redirect_method = mocker.patch("weko_accounts.views._redirect_method", + return_value=make_response()) res = client.post(url, data=form) mock_redirect_method.assert_called_once() assert res.status_code == 200 # _redirect_methodが呼び出されることを確認 @@ -474,22 +911,61 @@ def test_shib_sp_login(client, redis_connect,mocker, db, users): mock_sync_shib_gakunin_map_groups.assert_not_called() # shib_user.get_relation_info is None - with patch("weko_accounts.views.ShibUser.get_relation_info",return_value=None)\ - and patch("weko_accounts.views.redirect",return_value=make_response()): + with patch("weko_accounts.views.ShibUser.get_relation_info", + return_value=None)\ + and patch("weko_accounts.views.redirect", + return_value=make_response()): res = client.post(url,data=form) assert res.status_code == 200 assert res.data.decode() == "/weko/shib/login?Shib-Session-ID=1111&next=%2F" with client.session_transaction() as session: assert 'shib_session_id' not in session # shib_user.get_relation_info is not None - with patch("weko_accounts.views.ShibUser.get_relation_info",return_value="chib_user")\ - and patch("weko_accounts.views.redirect",return_value=make_response()): + with patch("weko_accounts.views.ShibUser.get_relation_info", + return_value="chib_user")\ + and patch("weko_accounts.views.redirect", + return_value=make_response()): res = client.post(url,data=form) assert res.status_code == 200 assert res.data.decode() == "/weko/shib/login?Shib-Session-ID=1111&next=%2F" with client.session_transaction() as session: assert 'shib_session_id' not in session + # test without blocked_user_settings + AdminSettings.query.filter_by(id=11).delete() + db.session.commit() + with patch("weko_accounts.views.ShibUser.get_relation_info", + return_value=None)\ + and patch("weko_accounts.views.redirect", + return_value=make_response()): + res = client.post(url,data=form) + assert res.status_code == 200 + assert res.data.decode() == "/weko/shib/login?Shib-Session-ID=1111&next=%2F" + with client.session_transaction() as session: + assert 'shib_session_id' not in session + + # test with blocked_user_setting dict + db.session.add(AdminSettings( + id=11, + name="blocked_user_settings", + settings={"blocked_ePPNs": ["ePPN1", "ePPN2", "ePPN3", "ePPN5", "ePPP*"]} + )) + db.session.commit() + mock_flash = mocker.patch("weko_accounts.views.flash") + form_blocked = { + "Shib-Session-ID":"1111", + "eppn":"ePPN3" + } + client.post(url,data=form_blocked) + mock_flash.assert_called_with("Failed to login.",category="error") + AdminSettings.query.filter_by(id=11).delete() + db.session.add(AdminSettings( + id=11, + name="blocked_user_settings", + settings='{"blocked_ePPNs": ["ePPN1", "ePPN2", "ePPN3", "ePPN5", "ePPP*"]}' + )) + db.session.commit() + current_app.config.update( WEKO_ACCOUNTS_SHIB_LOGIN_ENABLED=True, WEKO_ACCOUNTS_SKIP_CONFIRMATION_PAGE=True @@ -522,10 +998,25 @@ def test_shib_sp_login(client, redis_connect,mocker, db, users): # raise BaseException with patch("weko_accounts.views.flash",side_effect=BaseException("test_error"))\ - and patch("weko_accounts.views._redirect_method",return_value=make_response()) as mock_redirect_: + and patch("weko_accounts.views._redirect_method", + return_value=make_response()) as mock_redirect_: res = client.post(url,data={}) mock_redirect_.assert_called_once() + # raise BaseException(AMS) + mock_generate_ams_login_url = mocker.patch("weko_accounts.views.generate_ams_login_url", + return_value=make_response()) + with patch("weko_accounts.views.RedisConnection",side_effect=BaseException("test_error")): + form = { + "Shib-Session-ID":"1111", + "eppn":"test_eppn" + } + res = client.post(url+"?next=ams",data=form) + mock_generate_ams_login_url.assert_called_once() + called_args, _ = mock_generate_ams_login_url.call_args + assert "Server error has occurred. Please contact server " \ + "administrator." in called_args[0] + # all attributes have value and some shibboleth_user records don't have target eppn current_app.config.update( WEKO_ACCOUNTS_SHIB_LOGIN_ENABLED=True, @@ -715,7 +1206,6 @@ def test_shib_sp_login(client, redis_connect,mocker, db, users): assert res.status_code == 302 assert res.headers['Location'] == url_for("security.login", _external=True) - #def shib_stub_login(): # .tox/c1/bin/pytest --cov=weko_accounts tests/test_views.py::test_shib_stub_login -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workflow/.tox/c1/tmp def test_shib_stub_login(client,mocker): @@ -737,9 +1227,11 @@ def test_shib_stub_login(client,mocker): WEKO_ACCOUNTS_SHIB_IDP_LOGIN_ENABLED=False ) # WEKO_ACCOUNTS_SHIB_IDP_LOGIN_ENABLED is true - mock_render_template = mocker.patch("weko_accounts.views.render_template",return_value=make_response()) + mock_render_template = mocker.patch("weko_accounts.views.render_template", + return_value=make_response()) res = client.get(url) - mock_render_template.assert_called_with('weko_accounts/login_shibuser_pattern_1.html',module_name="WEKO-Accounts") + mock_render_template.assert_called_with('weko_accounts/login_shibuser_pattern_1.html', + module_name="WEKO-Accounts") #def shib_logout(): # .tox/c1/bin/pytest --cov=weko_accounts tests/test_views.py::test_shib_logout -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workflow/.tox/c1/tmp diff --git a/modules/weko-accounts/weko_accounts/api.py b/modules/weko-accounts/weko_accounts/api.py index 3590f7275a..74173fca78 100644 --- a/modules/weko-accounts/weko_accounts/api.py +++ b/modules/weko-accounts/weko_accounts/api.py @@ -51,24 +51,32 @@ def __init__(self, shib_attr=None): """The :class:`.models.ShibbolethUser` instance.""" self.is_member_of = [] - if shib_attr.get('shib_is_member_of'): + if 'shib_is_member_of' in shib_attr: is_member_of = shib_attr.get('shib_is_member_of', []) if isinstance(is_member_of, str): if is_member_of.find(';') != -1: is_member_of = is_member_of.split(';') - else: + elif is_member_of: is_member_of = [is_member_of] + else: + is_member_of = [] + elif not isinstance(is_member_of, list): + is_member_of = [] self.is_member_of = is_member_of del shib_attr['shib_is_member_of'] self.organizations = [] - if shib_attr.get('shib_organization'): + if 'shib_organization' in shib_attr: organizations = shib_attr.get('shib_organization', []) if isinstance(organizations, str): if organizations.find(';') != -1: organizations = organizations.split(';') - else: + elif organizations: organizations = [organizations] + else: + organizations = [] + elif not isinstance(organizations, list): + organizations = [] self.organizations = organizations del shib_attr['shib_organization'] diff --git a/modules/weko-accounts/weko_accounts/config.py b/modules/weko-accounts/weko_accounts/config.py index 6ee4afaf20..d39fec87da 100644 --- a/modules/weko-accounts/weko_accounts/config.py +++ b/modules/weko-accounts/weko_accounts/config.py @@ -61,6 +61,8 @@ WEKO_ACCOUNTS_SHIB_IDP_LOGIN_URL = '{}secure/login.py' """Login proxy URL.""" +WEKO_ACCOUNTS_SHIB_AMS_LOGIN_URL = '{}ams/login' + WEKO_ACCOUNTS_SSO_ATTRIBUTE_MAP = { 'SHIB_ATTR_EPPN': (False, 'shib_eppn'), # 'SHIB_ATTR_LOGIN_ID': (False, 'shib_uid'), @@ -101,15 +103,17 @@ 'givenName', 'eduPersonAffiliation', 'eduPersonScopedAffiliation', - 'eduPersonTargetedID' + 'eduPersonTargetedID', + 'HTTP_WEKOID', + 'HTTP_WEKOSOCIETYAFFILIATION' ] """Attribute List.""" WEKO_ACCOUNTS_ROLE_LIST = [ - 'System Administrator', - 'Repository Administrator', - 'Community Administrator', - 'Contributor', + 'System Administrator', + 'Repository Administrator', + 'Community Administrator', + 'Contributor', 'None' ] """Role List.""" @@ -119,26 +123,26 @@ WEKO_ACCOUNTS_GAKUNIN_ROLE = { 'defaultRole': 'Contributor', - 'organizationName': [] -} + 'organizationName': [] +} """Gakunin Default role.""" WEKO_ACCOUNTS_ORTHROS_INSIDE_ROLE = { 'defaultRole': 'Repository Administrator', - 'organizationName': [] -} + 'organizationName': [] +} """Orthros (Inside) Default role.""" WEKO_ACCOUNTS_ORTHROS_OUTSIDE_ROLE = { 'defaultRole': 'Community Administrator', - 'organizationName': [] -} + 'organizationName': [] +} """Orthros (Outside) Default role.""" WEKO_ACCOUNTS_EXTRA_ROLE = { 'defaultRole': 'None', # ロール無 - 'organizationName': [] -} + 'organizationName': [] +} """Extra Default role.""" WEKO_ACCOUNTS_SHIB_ROLE_RELATION = { diff --git a/modules/weko-accounts/weko_accounts/rest.py b/modules/weko-accounts/weko_accounts/rest.py index 0fb97c1436..01911db352 100644 --- a/modules/weko-accounts/weko_accounts/rest.py +++ b/modules/weko-accounts/weko_accounts/rest.py @@ -116,6 +116,12 @@ def post_v1(self, **kwargs): data = request.get_json() email = data['email'] password = data['password'] + if email: + email = email.strip() + email = email.strip('\u200b') # remove zero-width spaces + if password: + password = password.strip() + password = password.strip('\u200b') # remove zero-width spaces # Check if user is already logged in if current_user.is_authenticated: diff --git a/modules/weko-accounts/weko_accounts/templates/weko_accounts/login_shibuser_pattern_1.html b/modules/weko-accounts/weko_accounts/templates/weko_accounts/login_shibuser_pattern_1.html index 2ff28636f5..521d2d9b48 100644 --- a/modules/weko-accounts/weko_accounts/templates/weko_accounts/login_shibuser_pattern_1.html +++ b/modules/weko-accounts/weko_accounts/templates/weko_accounts/login_shibuser_pattern_1.html @@ -26,7 +26,7 @@ diff --git a/modules/weko-accounts/weko_accounts/templates/weko_accounts/login_user.html b/modules/weko-accounts/weko_accounts/templates/weko_accounts/login_user.html index 5ae2666a90..1d3c4f3e38 100644 --- a/modules/weko-accounts/weko_accounts/templates/weko_accounts/login_user.html +++ b/modules/weko-accounts/weko_accounts/templates/weko_accounts/login_user.html @@ -256,6 +256,9 @@

{{_('Log in to account') }}

// var wayf_additional_idps = [ ]; // Example of how to add Identity Provider from other federations + var wayf_additional_idps = [ + {name:"Orthros-Test",entityID:"https://core-stg.orthros.gakunin.nii.ac.jp/idp"} + ]; // var wayf_additional_idps = [ // // {name:"International University X", @@ -273,6 +276,7 @@

{{_('Log in to account') }}

// [Optional, commented out by default] // var wayf_discofeed_url = ""; + // The path of Cookie created by SP is set. As for default configuration, // the path of an access place is set. // var wayf_sp_cookie_path = "/"; @@ -310,7 +314,7 @@

{{_('Log in to account') }}

diff --git a/modules/weko-accounts/weko_accounts/utils.py b/modules/weko-accounts/weko_accounts/utils.py index eae9eab191..7dd2ede02a 100644 --- a/modules/weko-accounts/weko_accounts/utils.py +++ b/modules/weko-accounts/weko_accounts/utils.py @@ -30,7 +30,6 @@ import hashlib from .config import WEKO_API_LIMIT_RATE_DEFAULT -from weko_admin.models import AdminSettings limiter = Limiter( app=None, @@ -111,6 +110,7 @@ def parse_attributes(): error = False # Get attribute mapping from admin settings + from weko_admin.models import AdminSettings admin_settings = AdminSettings.get('attribute_mapping', dict_to_object=False) for header, attr in current_app.config[ @@ -221,7 +221,7 @@ def decorated_view(*args, **kwargs): def get_sp_info(): """Get Service Provider (SP) information for Shibboleth login. - + Returns: dict: A dictionary containing SP entityID, handlerURL, and return URL. """ @@ -233,7 +233,7 @@ def get_sp_info(): sp_entityID = 'https://' + current_app.config['WEB_HOST_NAME'] + '/shibboleth-sp' if 'SP_ENTITYID' in current_app.config: sp_entityID = current_app.config['SP_ENTITYID'] - + sp_handlerURL = 'https://' + current_app.config['WEB_HOST_NAME'] + '/Shibboleth.sso' if 'SP_HANDLERURL' in current_app.config: sp_handlerURL = current_app.config['SP_HANDLERURL'] diff --git a/modules/weko-accounts/weko_accounts/views.py b/modules/weko-accounts/weko_accounts/views.py index a26e313928..ccd9bcb554 100644 --- a/modules/weko-accounts/weko_accounts/views.py +++ b/modules/weko-accounts/weko_accounts/views.py @@ -17,6 +17,7 @@ # along with WEKO3; if not, write to the # Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, # MA 02111-1307, USA. +# """Views for weko-accounts. @@ -143,7 +144,7 @@ def _adjust_shib_admin_DB(): db.session.commit() -def _redirect_method(has_next=False): +def _redirect_method(has_next=False, ams_error=None): """Redirect method for instance login to IdP.""" shib_login = current_app.config['WEKO_ACCOUNTS_SHIB_LOGIN_ENABLED'] shib_login_url = current_app.config['WEKO_ACCOUNTS_SHIB_IDP_LOGIN_URL'] @@ -151,7 +152,13 @@ def _redirect_method(has_next=False): idp_login_inst = current_app.config[ 'WEKO_ACCOUNTS_SHIB_INST_LOGIN_DIRECTLY_ENABLED'] - if shib_login and idp_login and idp_login_inst: + if ams_error: + shib_ams_login_url = current_app.config['WEKO_ACCOUNTS_SHIB_AMS_LOGIN_URL'] + ams_url = shib_ams_login_url.format(request.url_root) + encoded_error = quote_plus(str(ams_error), encoding='utf-8', errors='replace') + url = f'{ams_url}?error={encoded_error}' + return redirect(url) + elif shib_login and idp_login and idp_login_inst: url = shib_login_url.format(request.url_root) if has_next: url += '?next=' + request.full_path @@ -162,6 +169,28 @@ def _redirect_method(has_next=False): next=request.full_path if has_next else None)) +def generate_ams_login_url(ams_error): + """ + Generate a redirect URL for the AMS login page with error details. + + The returned URL starts with a '/'. + + Args: + ams_error (str): Error message for the AMS login page to include + in the redirect URL. + + Returns: + str: URL for the AMS login page including the error + information as a query parameter. + """ + shib_ams_login_url = current_app.config['WEKO_ACCOUNTS_SHIB_AMS_LOGIN_URL'] + ams_url = shib_ams_login_url.format('/') + encoded_error = quote_plus( + str(ams_error), encoding='utf-8', errors='replace') + url = f'{ams_url}?error={encoded_error}' + return url + + @blueprint.route('/') def index(): """Render a basic view.""" @@ -176,29 +205,42 @@ def shib_auto_login(): :return: next url """ + ams_login = False try: is_auto_bind = False shib_session_id = request.args.get('Shib-Session-ID', None) - session['next'] = request.args.get('next', '/') - + next_value = request.args.get('next') + if next_value != 'ams': + next_value = session.get('next', '/') + session['next'] = next_value + ams_login = next_value == 'ams' if not shib_session_id: shib_session_id = session['shib_session_id'] is_auto_bind = True if not shib_session_id: - return _redirect_method() + if ams_login: + return _redirect_method(True, ams_error=_('Missing Shib-Session-ID!')) + else: + return _redirect_method() redis_connection = RedisConnection() datastore = redis_connection.connection(db=current_app.config['CACHE_REDIS_DB'], kv = True) cache_key = current_app.config[ 'WEKO_ACCOUNTS_SHIB_CACHE_PREFIX'] + shib_session_id if not datastore.redis.exists(cache_key): - return _redirect_method() + if ams_login: + return _redirect_method(True, ams_error=_('Missing SHIB_CACHE_PREFIX!')) + else: + return _redirect_method() cache_val = datastore.get(cache_key) if not cache_val: datastore.delete(cache_key) - return _redirect_method() + if ams_login: + return _redirect_method(True, ams_error=_('Missing SHIB_ATTR!')) + else: + return _redirect_method() cache_val = json.loads(str(cache_val, encoding='utf-8')) shib_user = ShibUser(cache_val) @@ -212,8 +254,11 @@ def shib_auto_login(): if error: datastore.delete(cache_key) current_app.logger.error(error) - flash(error, category='error') - return _redirect_method() + if ams_login: + return _redirect_method(True, ams_error=error) + else: + flash(error, category='error') + return _redirect_method() if shib_user.shib_user: shib_user.shib_user_login() @@ -225,7 +270,10 @@ def shib_auto_login(): target_key=shib_user.user.id, remarks="Shibboleth login" ) - return redirect(session['next'] if 'next' in session else '/') + if ams_login: + return redirect('/?next=ams') + else: + return redirect(session['next'] if 'next' in session else '/') except BaseException: db.session.rollback() current_app.logger.error("Unexpected error: {}".format(sys.exc_info())) @@ -235,6 +283,9 @@ def shib_auto_login(): operation="LOGIN", remarks=tb_info[0] ) + if ams_login: + return _redirect_method(True, ams_error=_( + 'Server error has occurred. Please contact server administrator.')) return abort(400) @@ -244,15 +295,25 @@ def confirm_user(): :return: """ + ams_login = False try: + next_value = session['next'] if 'next' in session else '' + if next_value == 'ams': + ams_login = True if request.form.get('csrf_random', '') != session['csrf_random']: - flash('csrf_random', category='error') - return _redirect_method() + if ams_login: + return _redirect_method(True, ams_error=_('csrf_random')) + else: + flash('csrf_random', category='error') + return _redirect_method() shib_session_id = session['shib_session_id'] if not shib_session_id: - flash('shib_session_id', category='error') - return _redirect_method() + if ams_login: + return _redirect_method(True, ams_error=_('Missing Shib-Session-ID!')) + else: + flash('shib_session_id', category='error') + return _redirect_method() redis_connection = RedisConnection() datastore = redis_connection.connection(db=current_app.config['CACHE_REDIS_DB'], kv = True) @@ -260,34 +321,55 @@ def confirm_user(): 'WEKO_ACCOUNTS_SHIB_CACHE_PREFIX'] + shib_session_id if not datastore.redis.exists(cache_key): - flash('cache_key', category='error') - return _redirect_method() + if ams_login: + return _redirect_method(True, ams_error=_('Missing SHIB_CACHE_PREFIX!')) + else: + flash('cache_key', category='error') + return _redirect_method() cache_val = datastore.get(cache_key) if not cache_val: - flash('cache_val', category='error') - datastore.delete(cache_key) - return _redirect_method() + if ams_login: + return _redirect_method(True, ams_error=_('Missing SHIB_ATTR!')) + else: + flash('cache_val', category='error') + datastore.delete(cache_key) + return _redirect_method() cache_val = json.loads(str(cache_val, encoding='utf-8')) shib_user = ShibUser(cache_val) account = request.form.get('WEKO_ATTR_ACCOUNT', None) password = request.form.get('WEKO_ATTR_PWD', None) + if account: + account = account.strip() + account = account.strip('\u200b') # remove zero-width spaces + if password: + password = password.strip() + password = password.strip('\u200b') # remove zero-width spaces if not shib_user.check_weko_user(account, password): - flash('check_weko_user', category='error') - datastore.delete(cache_key) - return _redirect_method() + if ams_login: + return _redirect_method(True, ams_error=_('There is no user information.')) + else: + flash('check_weko_user', category='error') + datastore.delete(cache_key) + return _redirect_method() if not shib_user.bind_relation_info(account): - flash('FAILED bind_relation_info!', category='error') - return _redirect_method() + if ams_login: + return _redirect_method(True, ams_error=_('FAILED bind_relation_info!')) + else: + flash('FAILED bind_relation_info!', category='error') + return _redirect_method() error = shib_user.check_in() if error: datastore.delete(cache_key) - flash(error, category='error') - return _redirect_method() + if ams_login: + return _redirect_method(True, ams_error=error) + else: + flash(error, category='error') + return _redirect_method() if shib_user.shib_user: shib_user.shib_user_login() @@ -297,7 +379,10 @@ def confirm_user(): target_key=shib_user.user.id, remarks="Shibboleth login" ) - return redirect(session['next'] if 'next' in session else '/') + if ams_login: + return redirect('/?next=ams') + else: + return redirect(session['next'] if 'next' in session else '/') except BaseException: current_app.logger.error("Unexpected error: {}".format(sys.exc_info())) exec_info = sys.exc_info() @@ -306,6 +391,9 @@ def confirm_user(): operation="LOGIN", remarks=tb_info[0] ) + if ams_login: + return _redirect_method(True, ams_error=_( + 'Server error has occurred. Please contact server administrator.')) return abort(400) @@ -315,12 +403,20 @@ def confirm_user_without_page(): :return: """ + ams_login = False try: + next_value = request.args.get('next', '') + if next_value == 'ams': + ams_login = True + # get shib_session_id from session shib_session_id = request.args.get('Shib-Session-ID', None) if not shib_session_id: - flash('shib_session_id', category='error') - return _redirect_method() + if ams_login: + return _redirect_method(True, ams_error=_('Missing Shib-Session-ID!')) + else: + flash('shib_session_id', category='error') + return _redirect_method() # get cache from redis redis_connection = RedisConnection() @@ -330,38 +426,56 @@ def confirm_user_without_page(): # check cache if not datastore.redis.exists(cache_key): - flash('cache_key', category='error') - return _redirect_method() + if ams_login: + return _redirect_method(True, ams_error=_('Missing SHIB_CACHE_PREFIX!')) + else: + flash('cache_key', category='error') + return _redirect_method() cache_val = datastore.get(cache_key) if not cache_val: - flash('cache_val', category='error') - datastore.delete(cache_key) - return _redirect_method() + if ams_login: + return _redirect_method(True, ams_error=_('Missing SHIB_ATTR!')) + else: + flash('cache_val', category='error') + datastore.delete(cache_key) + return _redirect_method() cache_val = json.loads(str(cache_val, encoding='utf-8')) shib_user = ShibUser(cache_val) # bind relation info if not shib_user.bind_relation_info(cache_val.get('shib_mail')): - flash('FAILED bind_relation_info!', category='error') datastore.delete(cache_key) - return _redirect_method() + if ams_login: + return _redirect_method(True, ams_error=_('FAILED bind_relation_info!')) + else: + flash('FAILED bind_relation_info!', category='error') + return _redirect_method() # check in error = shib_user.check_in() if error: datastore.delete(cache_key) - flash(error, category='error') - return _redirect_method() + if ams_login: + return _redirect_method(True, ams_error=error) + else: + flash(error, category='error') + return _redirect_method() if shib_user.shib_user: shib_user.shib_user_login() datastore.delete(cache_key) - return redirect(request.args.get('next', '/')) + if ams_login: + return redirect('/?next=ams') + else: + return redirect(request.args.get('next', '/')) except BaseException: current_app.logger.error("Unexpected error: {}".format(sys.exc_info())) + if ams_login: + return _redirect_method(True, ams_error=_( + 'Server error has occurred. Please contact server administrator.')) return abort(400) @@ -371,14 +485,19 @@ def shib_login(): :return: confirm user page when relation is empty """ + ams_login = False try: shib_session_id = request.args.get('Shib-Session-ID', None) session['next'] = request.args.get('next', '/') - + if session['next'] == 'ams': + ams_login = True if not shib_session_id: - current_app.logger.error(_("Missing Shib-Session-ID!")) - flash(_("Missing Shib-Session-ID!"), category='error') - return _redirect_method() + current_app.logger.error(_('Missing Shib-Session-ID!')) + if ams_login: + return _redirect_method(True, ams_error=_('Missing Shib-Session-ID!')) + else: + flash(_('Missing Shib-Session-ID!'), category='error') + return _redirect_method() redis_connection = RedisConnection() datastore = redis_connection.connection(db=current_app.config['CACHE_REDIS_DB'], kv = True) @@ -386,17 +505,23 @@ def shib_login(): 'WEKO_ACCOUNTS_SHIB_CACHE_PREFIX'] + shib_session_id if not datastore.redis.exists(cache_key): - current_app.logger.error(_("Missing SHIB_CACHE_PREFIX!")) - flash(_("Missing SHIB_CACHE_PREFIX!"), category='error') - return _redirect_method() + current_app.logger.error(_('Missing SHIB_CACHE_PREFIX!')) + flash(_('Missing SHIB_CACHE_PREFIX!'), category='error') + if ams_login: + return _redirect_method(True, ams_error=_('Missing SHIB_CACHE_PREFIX!')) + else: + return _redirect_method() cache_val = datastore.get(cache_key) if not cache_val: - current_app.logger.error(_("Missing SHIB_ATTR!")) - flash(_("Missing SHIB_ATTR!"), category='error') + current_app.logger.error(_('Missing SHIB_ATTR!')) datastore.delete(cache_key) - return _redirect_method() + if ams_login: + return _redirect_method(True, ams_error=_('Missing SHIB_ATTR!')) + else: + flash(_('Missing SHIB_ATTR!'), category='error') + return _redirect_method() cache_val = json.loads(str(cache_val, encoding='utf-8')) session['shib_session_id'] = shib_session_id @@ -412,6 +537,9 @@ def shib_login(): ) except BaseException: current_app.logger.error("Unexpected error: {}".format(sys.exc_info())) + if ams_login: + return _redirect_method(True, ams_error=_( + 'Server error has occurred. Please contact server administrator.')) return abort(400) def find_user_by_email(shib_attributes): @@ -432,7 +560,9 @@ def shib_sp_login(): _shib_username_config = current_app.config[ 'WEKO_ACCOUNTS_SHIB_ALLOW_USERNAME_INST_EPPN'] next = request.args.get('next', '/') - + ams_login = False + if next == 'ams': + ams_login = True try: # WEKO_ACCOUNTS_SHIB_BIND_GAKUNIN_MAP_GROUPSがTrueのときの処理 if current_app.config['WEKO_ACCOUNTS_SHIB_BIND_GAKUNIN_MAP_GROUPS']: @@ -440,8 +570,11 @@ def shib_sp_login(): shib_session_id = request.form.get('Shib-Session-ID', None) if not shib_session_id and not _shib_enable: - flash(_("Missing Shib-Session-ID!"), category='error') - return redirect(url_for_security('login')) + if ams_login: + return generate_ams_login_url(_('Missing Shib-Session-ID!')) + else: + flash(_('Missing Shib-Session-ID!'), category='error') + return redirect(url_for_security('login')) shib_attr, error = parse_attributes() @@ -449,8 +582,11 @@ def shib_sp_login(): if error or not ( shib_attr.get('shib_eppn', None) or _shib_username_config and shib_attr.get('shib_user_name')): - flash(_("Missing SHIB_ATTRs!"), category='error') - return _redirect_method() + if ams_login: + return generate_ams_login_url(_('Missing SHIB_ATTRs!')) + else: + flash(_('Missing SHIB_ATTRs!'), category='error') + return _redirect_method() # Check if shib_eppn is not included in the blocked user list if AdminSettings.query.filter_by(name='blocked_user_settings').first(): @@ -468,8 +604,11 @@ def _wildcard_to_regex(pattern): blocked = any(_wildcard_to_regex(pattern).match(shib_eppn) or pattern == shib_eppn for pattern in block_user_list) if blocked: - flash(_("Failed to login."), category='error') - return _redirect_method() + if ams_login: + return generate_ams_login_url(_('Login is blocked.')) + else: + flash(_('Failed to login.'), category='error') + return _redirect_method() # Redis connection redis_connection = RedisConnection() @@ -509,7 +648,11 @@ def _wildcard_to_regex(pattern): return url_for(next_url, **query_string) except BaseException: current_app.logger.error("Unexpected error: {}".format(sys.exc_info())) - return _redirect_method() + if ams_login: + return generate_ams_login_url( + _('Server error has occurred. Please contact server administrator.')) + else: + return _redirect_method() @blueprint.route('/shib/sp/login', methods=['GET']) diff --git a/modules/weko-admin/weko_admin/static/js/weko_admin/sword_api_jsonld_setting.js b/modules/weko-admin/weko_admin/static/js/weko_admin/sword_api_jsonld_setting.js index eaf74888f6..800acf6875 100644 --- a/modules/weko-admin/weko_admin/static/js/weko_admin/sword_api_jsonld_setting.js +++ b/modules/weko-admin/weko_admin/static/js/weko_admin/sword_api_jsonld_setting.js @@ -387,7 +387,7 @@ function saveDataFormat(type) { if (type === 'add') { url = '/admin/swordapi/jsonld/add/'; } else { - url = '/admin/swordapi/jsonld/edit/' + current_model_json['id']; + url = '/admin/swordapi/jsonld/edit/' + current_model_json['id'] + '/'; } fetch(url, { method: 'POST', diff --git a/modules/weko-deposit/requirements2.txt b/modules/weko-deposit/requirements2.txt index 3c30c7d8a7..f203b6675a 100644 --- a/modules/weko-deposit/requirements2.txt +++ b/modules/weko-deposit/requirements2.txt @@ -287,3 +287,4 @@ xmlschema==0.9.30 xmltodict==0.12.0 zipp==3.6.0 zope.interface==5.5.2 +pypdfium2==4.30.0 diff --git a/modules/weko-deposit/tests/data/test_files/sample_word.docx b/modules/weko-deposit/tests/data/test_files/sample_word.docx new file mode 100644 index 0000000000..6de626ae06 Binary files /dev/null and b/modules/weko-deposit/tests/data/test_files/sample_word.docx differ diff --git a/modules/weko-deposit/tests/test_utils.py b/modules/weko-deposit/tests/test_utils.py index a48feffb22..2b017a018e 100644 --- a/modules/weko-deposit/tests/test_utils.py +++ b/modules/weko-deposit/tests/test_utils.py @@ -19,12 +19,14 @@ # MA 02111-1307, USA. from weko_deposit.api import WekoDeposit -from weko_deposit.utils import update_pdf_contents_es +from weko_deposit.utils import update_pdf_contents_es, extract_text_from_pdf, extract_text_with_tika from mock import patch +from mock import MagicMock import uuid from tests.helpers import create_record_with_pdf - +import os +import pytest # .tox/c1/bin/pytest --cov=weko_deposit tests/test_utils.py::test_update_pdf_contents_es -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-deposit/.tox/c1/tmp def test_update_pdf_contents_es(app, db, location, mocker): @@ -46,4 +48,57 @@ def test_update_pdf_contents_es(app, db, location, mocker): for args, _ in args_list: test = pdf_file_infos[i] assert args[0] == (test,str(record_ids[i])) - i+=1 \ No newline at end of file + i+=1 + + +# .tox/c1/bin/pytest --cov=weko_deposit tests/test_utils.py::test_extract_text_from_pdf -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-deposit/.tox/c1/tmp +def test_extract_text_from_pdf(): + filepath = os.path.join(os.path.dirname(__file__),"data","test_files","test_file_1.2M.pdf") + + # file size > max_size + data = extract_text_from_pdf(filepath, 10000) + assert len(data.encode('utf-8')) <= 10000 + assert data.count("test file page") < 1240 + + # file size <= max_size + data = extract_text_from_pdf(filepath, 100000000) + assert len(data.encode('utf-8')) == 19561 + assert data.count("test file page") == 1240 + + # not exist file + filepath = "not_exist_file.pdf" + with pytest.raises(FileNotFoundError) as e: + data = extract_text_from_pdf(filepath, 10000) + assert str(e.value) == "/code/modules/weko-deposit/not_exist_file.pdf" + + +# .tox/c1/bin/pytest --cov=weko_deposit tests/test_utils.py::test_extract_text_with_tika -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-deposit/.tox/c1/tmp +def test_extract_text_with_tika(): + filepath = os.path.join(os.path.dirname(__file__),"data","test_files","sample_word.docx") + # not exist tika jar file. + mock_env_not_exist_tika = {"TIKA_JAR_FILE_PATH": "/not/exist/path/tika-server.jar"} + with patch.dict(os.environ, mock_env_not_exist_tika, clear=False): + with pytest.raises(Exception) as e: + data = extract_text_with_tika(filepath, 100) + assert str(e.value) == "not exist tika jar file." + + mock_env_not_exist_tika = {"TIKA_JAR_FILE_PATH": "/code/tika/tika-app-2.6.0.jar"} + with patch.dict(os.environ, mock_env_not_exist_tika, clear=False): + # error with subprocess + mock_run = MagicMock() + mock_run.returncode.return_value=1 + mock_run.stderr.decode.return_value="test_error" + with patch("weko_deposit.utils.subprocess.run", return_value=mock_run): + with pytest.raises(Exception) as e: + data = extract_text_with_tika(filepath, 100) + assert str(e.value) == "raise in tika: test_error" + + # file size > max_size + data = extract_text_with_tika(filepath, 50) + assert len(data.encode('utf-8')) < 50 + assert data == "これはテスト用のサンプルwordファイ" + + # file size <= max_size + data = extract_text_with_tika(filepath, 5000) + assert len(data.encode('utf-8')) > 50 + assert data == "これはテスト用のサンプルwordファイルです中身は特に意味がありません" diff --git a/modules/weko-deposit/weko_deposit/config.py b/modules/weko-deposit/weko_deposit/config.py index 52b9d7608c..dc211fdff5 100644 --- a/modules/weko-deposit/weko_deposit/config.py +++ b/modules/weko-deposit/weko_deposit/config.py @@ -37,7 +37,14 @@ 'text/tab-separated-values', 'text/xml', 'application/x-tex', - 'application/x-latex' + 'application/x-latex', + 'text/markdown', + 'application/json', + 'application/ld+json', + 'text/vnd.yaml', + 'text/yaml', + 'text/x-yaml', + 'application/x-yaml' ] WEKO_MIMETYPE_WHITELIST_FOR_ES = [ @@ -51,6 +58,7 @@ 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/vnd.oasis.opendocument.presentation', 'application/pdf', + 'application/rtf', ] + WEKO_DEPOSIT_TEXTMIMETYPE_WHITELIST_FOR_ES diff --git a/modules/weko-deposit/weko_deposit/utils.py b/modules/weko-deposit/weko_deposit/utils.py index a47761dd9e..0c7cd5a1d1 100644 --- a/modules/weko-deposit/weko_deposit/utils.py +++ b/modules/weko-deposit/weko_deposit/utils.py @@ -20,6 +20,10 @@ from .tasks import extract_pdf_and_update_file_contents from .api import WekoDeposit +import pypdfium2 +import os +import subprocess + def update_pdf_contents_es(record_ids): """register the contents of the record PDF file in elasticsearch @@ -29,4 +33,73 @@ def update_pdf_contents_es(record_ids): deposits = WekoDeposit.get_records(record_ids) for dep in deposits: file_infos = dep.get_pdf_info() - extract_pdf_and_update_file_contents.apply_async((file_infos, str(dep.id))) \ No newline at end of file + extract_pdf_and_update_file_contents.apply_async((file_infos, str(dep.id))) + + +def extract_text_from_pdf(filepath, max_size): + """Read PDF file and extract text. + + Args: + filepath (str): Path to the PDF file. + max_size (int): Maximum size of the extracted text in bytes. + + Returns: + str: Extracted text from the PDF file. + + """ + reader = None + data = "" + try: + reader = pypdfium2.PdfDocument(filepath) + texts = [] + total_bytes = 0 + for page in reader: + text = page.get_textpage().get_text_range() + encoded = text.encode('utf-8', errors='replace') + if total_bytes + len(encoded) > max_size: + remain = max_size - total_bytes + texts.append(encoded[:remain].decode('utf-8', errors='ignore')) + break + else: + texts.append(text) + total_bytes += len(encoded) + data = "".join(texts) + data = "".join(data.splitlines()) + finally: + if reader is not None: + reader.close() + + return data + + +def extract_text_with_tika(filepath, max_size): + """Read non-PDF file and extract text. + + Args: + filepath (str): Path to the PDF file. + max_size (int): Maximum size of the extracted text in bytes. + + Raises: + Exception: If Tika jar file does not exist. + Exception: If Tika processing fails. + + Returns: + str: Extracted text from the non-PDF file. + """ + tika_jar_path = os.environ.get("TIKA_JAR_FILE_PATH") + if not tika_jar_path or os.path.isfile(tika_jar_path) is False: + raise Exception("not exist tika jar file.") + args = ["java", "-jar", tika_jar_path, "-t", filepath] + result = subprocess.run( + args, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + if result.returncode != 0: + raise Exception("raise in tika: {}".format(result.stderr.decode("utf-8"))) + data = "".join(result.stdout.decode("utf-8").splitlines()) + if len(data.encode('utf-8')) > max_size: + encoded = data.encode('utf-8') + data = encoded[:max_size].decode('utf-8', errors='ignore') + + return data diff --git a/modules/weko-index-tree/tests/test_rest.py b/modules/weko-index-tree/tests/test_rest.py index 562d2d8c5c..17c374867c 100644 --- a/modules/weko-index-tree/tests/test_rest.py +++ b/modules/weko-index-tree/tests/test_rest.py @@ -1347,3 +1347,14 @@ def run_delete_index_server_error(self, app, client_rest, auth_headers): with patch.object(Indexes, "delete", side_effect=SQLAlchemyError): response = client_rest.delete(url, headers=auth_headers) assert response.status_code == 500, f"{response.json}" + +# class GetIndex: +# .tox/c1/bin/pytest --cov=weko_index_tree tests/test_rest.py::TestGetIndex -vv -s --cov-branch --cov-report=term --cov-report=html --basetemp=/code/modules/weko_index_tree/.tox/c1/tmp --full-trace +class TestGetIndex: + # .tox/c1/bin/pytest --cov=weko_index_tree tests/test_rest.py::TestGetIndex::test_get_v1 -vv -s --cov-branch --cov-report=term --cov-report=html --basetemp=/code/modules/weko_index_tree/.tox/c1/tmp --full-trace + def test_get_v1(self, client_rest, users, test_indices, auth_headers_sysadmin): + res = client_rest.get('/v1/tree/index/1', headers=auth_headers_sysadmin) + assert res.status_code == 200 + data = json.loads(res.get_data()) + assert data['index']['name'] == 'Test index 1' + assert data['index']['public_date'] == '20220101' diff --git a/modules/weko-index-tree/weko_index_tree/rest.py b/modules/weko-index-tree/weko_index_tree/rest.py index bfadf5c0c0..d881fe6231 100644 --- a/modules/weko-index-tree/weko_index_tree/rest.py +++ b/modules/weko-index-tree/weko_index_tree/rest.py @@ -651,6 +651,21 @@ def get(self, **kwargs): raise VersionNotFoundRESTError() def get_v1(self, **kwargs): + + def json_serialize(obj): + """Serialize object to JSON. + + Args: + obj: The object to serialize. + + Returns: + str: The serialized JSON string. + """ + if isinstance(obj, (datetime, date)): + return obj.strftime("%Y%m%d") + else: + return str(obj) + try: pid = kwargs.get('index_id') @@ -707,7 +722,8 @@ def get_v1(self, **kwargs): # Create Response res = Response( - response=json.dumps(result_tree, indent=indent), + response=json.dumps( + result_tree, indent=indent, default=json_serialize), status=200, content_type='application/json') res.set_etag(etag) diff --git a/modules/weko-records-ui/weko_records_ui/config.py b/modules/weko-records-ui/weko_records_ui/config.py index 52083eea6e..f426fdafbb 100644 --- a/modules/weko-records-ui/weko_records_ui/config.py +++ b/modules/weko-records-ui/weko_records_ui/config.py @@ -68,7 +68,7 @@ OPEN_DATE_HIDE_VALUE = '0' # setting the release date if display -DISPLAY_REQUEST_FORM = False +DISPLAY_REQUEST_FORM = True # Default setting whether to display the request form # CSL Citation Formatter diff --git a/modules/weko-records-ui/weko_records_ui/rest.py b/modules/weko-records-ui/weko_records_ui/rest.py index 921ba83497..5bcdc7513e 100644 --- a/modules/weko-records-ui/weko_records_ui/rest.py +++ b/modules/weko-records-ui/weko_records_ui/rest.py @@ -26,6 +26,7 @@ from flask import Blueprint, current_app, jsonify, make_response, request, Response from flask_babelex import get_locale from flask_babelex import gettext as _ +from flask_login import current_user from werkzeug.http import generate_etag from redis import RedisError from invenio_oauth2server import require_api_auth, require_oauth_scopes @@ -53,8 +54,11 @@ from .views import escape_str from .permissions import page_permission_factory, file_permission_factory -from .errors import AvailableFilesNotFoundRESTError, ContentsNotFoundError, InvalidRequestError, VersionNotFoundRESTError, InternalServerError, \ - RecordsNotFoundRESTError, PermissionError, DateFormatRESTError, FilesNotFoundRESTError, ModeNotFoundRESTError, RequiredItemNotExistError +from .errors import AvailableFilesNotFoundRESTError, ContentsNotFoundError, \ + InvalidRequestError, VersionNotFoundRESTError, InternalServerError, \ + RecordsNotFoundRESTError, PermissionError, DateFormatRESTError, \ + FilesNotFoundRESTError, ModeNotFoundRESTError, RequiredItemNotExistError, \ + AuthenticationRequiredError from .scopes import file_read_scope @@ -343,7 +347,10 @@ def get_v1(self, **kwargs): # Check Permission if not page_permission_factory(record).can(): - raise PermissionError() + if current_user.is_authenticated: + raise PermissionError() + else: + raise AuthenticationRequiredError() # Convert RO-Crate format from .utils import RoCrateConverter @@ -388,8 +395,10 @@ def get_v1(self, **kwargs): return res - except (PermissionError, SameContentException) as e: - raise e + except (PermissionError, + SameContentException, + AuthenticationRequiredError) as e: + raise e except PIDDoesNotExistError: raise RecordsNotFoundRESTError() diff --git a/modules/weko-records-ui/weko_records_ui/templates/weko_records_ui/output_detail_data.html b/modules/weko-records-ui/weko_records_ui/templates/weko_records_ui/output_detail_data.html index 0c5c412a5c..b1c7ed794b 100644 --- a/modules/weko-records-ui/weko_records_ui/templates/weko_records_ui/output_detail_data.html +++ b/modules/weko-records-ui/weko_records_ui/templates/weko_records_ui/output_detail_data.html @@ -72,7 +72,7 @@ {%- endfor -%} {%- else -%} {{ output_attribute_value_mlt_Init( record_detail_data ) }} - {%- endif -%} + {%- endif -%} {%- endif -%} {% endmacro %} @@ -83,7 +83,7 @@ {% if parrent_name %} {%- set labels = parrent_name.split('.') -%} {%- if labels|length == 1 -%} - {{ child_data(parrent_name, '', level) }} + {{ child_data(parrent_name, ' ', level) }} {%- else -%} {%- set displayflag = False -%} {%- endif -%} @@ -118,7 +118,7 @@ {{ content | escape_str }} {%- elif content|url_to_link -%} {{ content | escape_str }} - {%- else -%} + {%- else -%} {{ content | escape_str }} {%- endif -%} {% endautoescape %} @@ -207,7 +207,7 @@ {%- set nsflg.dispflg = True -%} {%- endif -%} {%- endfor -%} - {%- endif -%} + {%- endif -%} {%- endif -%} {%- endfor -%} {%- for language_value in language_data -%} @@ -245,7 +245,7 @@ {%- if value is string -%} {%- if attribute_data|length == 2 -%} {{ output_attribute_value_mlt(attribute_data, level) }} - {%- else -%} + {%- else -%} {{ output_attribute_value_mlt_exceptlang(attribute_data,level) }} {%- endif -%} {%- else -%} diff --git a/modules/weko-search-ui/requirements2.txt b/modules/weko-search-ui/requirements2.txt index 3c30c7d8a7..f203b6675a 100644 --- a/modules/weko-search-ui/requirements2.txt +++ b/modules/weko-search-ui/requirements2.txt @@ -287,3 +287,4 @@ xmlschema==0.9.30 xmltodict==0.12.0 zipp==3.6.0 zope.interface==5.5.2 +pypdfium2==4.30.0 diff --git a/modules/weko-search-ui/tests/conftest.py b/modules/weko-search-ui/tests/conftest.py index 9c9c58582c..03f0156a8f 100644 --- a/modules/weko-search-ui/tests/conftest.py +++ b/modules/weko-search-ui/tests/conftest.py @@ -136,7 +136,13 @@ from weko_deposit.api import WekoDeposit from weko_deposit.api import WekoDeposit as aWekoDeposit from weko_deposit.api import WekoIndexer, WekoRecord -from weko_deposit.config import WEKO_BUCKET_QUOTA_SIZE, WEKO_MAX_FILE_SIZE +from weko_deposit.config import ( + WEKO_BUCKET_QUOTA_SIZE, + WEKO_MAX_FILE_SIZE, + WEKO_DEPOSIT_FILESIZE_LIMIT, + WEKO_MIMETYPE_WHITELIST_FOR_ES, + WEKO_DEPOSIT_TEXTMIMETYPE_WHITELIST_FOR_ES +) from weko_groups import WekoGroups from weko_index_tree import WekoIndexTree, WekoIndexTreeREST from weko_index_tree.api import Indexes @@ -694,7 +700,10 @@ def base_app(instance_path, search_class, request): WEKO_ITEMS_UI_INDEX_PATH_SPLIT = '///', WEKO_SEARCH_UI_BULK_EXPORT_RETRY = 5, WEKO_SEARCH_UI_BULK_EXPORT_LIMIT = 100, - RECORDS_UI_ENDPOINTS = RECORDS_UI_ENDPOINTS + RECORDS_UI_ENDPOINTS = RECORDS_UI_ENDPOINTS, + WEKO_DEPOSIT_FILESIZE_LIMIT = WEKO_DEPOSIT_FILESIZE_LIMIT, + WEKO_MIMETYPE_WHITELIST_FOR_ES = WEKO_MIMETYPE_WHITELIST_FOR_ES, + WEKO_DEPOSIT_TEXTMIMETYPE_WHITELIST_FOR_ES = WEKO_DEPOSIT_TEXTMIMETYPE_WHITELIST_FOR_ES ) app_.url_map.converters["pid"] = PIDConverter app_.config["RECORDS_REST_ENDPOINTS"]["recid"]["search_class"] = search_class diff --git a/modules/weko-search-ui/tests/data/ams/broken_word.docx b/modules/weko-search-ui/tests/data/ams/broken_word.docx new file mode 100644 index 0000000000..3fa5f4e582 Binary files /dev/null and b/modules/weko-search-ui/tests/data/ams/broken_word.docx differ diff --git a/modules/weko-search-ui/tests/data/ams/png_file.pdf b/modules/weko-search-ui/tests/data/ams/png_file.pdf new file mode 100644 index 0000000000..cc8d7bda54 Binary files /dev/null and b/modules/weko-search-ui/tests/data/ams/png_file.pdf differ diff --git a/modules/weko-search-ui/tests/data/ams/png_file.txt b/modules/weko-search-ui/tests/data/ams/png_file.txt new file mode 100644 index 0000000000..cc8d7bda54 Binary files /dev/null and b/modules/weko-search-ui/tests/data/ams/png_file.txt differ diff --git a/modules/weko-search-ui/tests/data/ams/sample.txt b/modules/weko-search-ui/tests/data/ams/sample.txt new file mode 100644 index 0000000000..990383c64b --- /dev/null +++ b/modules/weko-search-ui/tests/data/ams/sample.txt @@ -0,0 +1,2 @@ +This is a +text file. diff --git a/modules/weko-search-ui/tests/data/ams/with_two_extended_metadata.json b/modules/weko-search-ui/tests/data/ams/with_two_extended_metadata.json new file mode 100644 index 0000000000..eb70def6c2 --- /dev/null +++ b/modules/weko-search-ui/tests/data/ams/with_two_extended_metadata.json @@ -0,0 +1,99 @@ +{ + "@id": "./", + "@type": "Dataset", + "datePublished": "2025-10-22", + "name": "extract", + "description": "Item metadata for Item ID: 2000036. Title: extract.", + "wk:index": [ + "1623632832836" + ], + "wk:publishStatus": "public", + "wk:feedbackMail": [], + "wk:requestMail": [], + "wk:grant": [], + "wk:editMode": "Keep", + "dc:title": [ + { + "@id": "#:title_0", + "@type": "PropertyValue", + "value": "extract", + "language": "ja" + } + ], + "dc:type": { + "@id": "#:type_1", + "@type": "PropertyValue", + "rdf:resource": "departmental bulletin paper", + "value": "http://purl.org/coar/resource_type/c_6501" + }, + "hasPart": [ + { + "@id": "data/guide.pdf", + "@type": "File", + "dcterms:accessRights": "open_access", + "datePublished": "2025-10-22", + "name": "guide.pdf", + "jpcoar:extent": [ + { + "@id": "#:extent_3", + "@type": "File", + "value": "844 KB" + } + ], + "jpcoar:mimeType": "application/pdf", + "jpcoar:URI": { + "@id": "#:URI_4", + "@type": "URL", + "value": "https://192.168.56.102/record/2000036/files/guide.pdf" + }, + "wk:textExtraction": true, + "wk:extendedMetadata": true + }, + { + "@id": "data/sample.txt", + "@type": "File", + "dcterms:accessRights": "open_access", + "datePublished": "2025-10-22", + "name": "sample.txt", + "jpcoar:extent": [ + { + "@id": "#:extent_6", + "@type": "File", + "value": "51 B" + } + ], + "jpcoar:mimeType": "text/plain", + "jpcoar:URI": { + "@id": "#:URI_7", + "@type": "URL", + "value": "https://192.168.56.102/record/2000036/files/sample.txt" + }, + "wk:textExtraction": true, + "wk:extendedMetadata": true + }, + { + "@id": "data/pp.pptx", + "@type": "File", + "dcterms:accessRights": "open_access", + "datePublished": "2025-10-22", + "name": "pp.pptx", + "jpcoar:extent": [ + { + "@id": "#:extent_9", + "@type": "File", + "value": "432 KB" + } + ], + "jpcoar:mimeType": "application/vnd.openxmlformats-officedocument.presentationml.presentation", + "jpcoar:URI": { + "@id": "#:URI_10", + "@type": "URL", + "value": "https://192.168.56.102/record/2000036/files/pp.pptx" + }, + "wk:textExtraction": true, + "wk:extendedMetadata": false + } + ], + "wk:itemLinks": [], + "wk:metadataAutoFill": false +} diff --git a/modules/weko-search-ui/tests/data/ams/without_hasPart.json b/modules/weko-search-ui/tests/data/ams/without_hasPart.json new file mode 100644 index 0000000000..a4a18e1ba1 --- /dev/null +++ b/modules/weko-search-ui/tests/data/ams/without_hasPart.json @@ -0,0 +1,31 @@ +{ + "@id": "./", + "@type": "Dataset", + "datePublished": "2025-10-22", + "name": "extract", + "description": "Item metadata for Item ID: 2000036. Title: extract.", + "wk:index": [ + "1623632832836" + ], + "wk:publishStatus": "public", + "wk:feedbackMail": [], + "wk:requestMail": [], + "wk:grant": [], + "wk:editMode": "Keep", + "dc:title": [ + { + "@id": "#:title_0", + "@type": "PropertyValue", + "value": "extract", + "language": "ja" + } + ], + "dc:type": { + "@id": "#:type_1", + "@type": "PropertyValue", + "rdf:resource": "departmental bulletin paper", + "value": "http://purl.org/coar/resource_type/c_6501" + }, + "wk:itemLinks": [], + "wk:metadataAutoFill": false +} diff --git "a/modules/weko-search-ui/tests/data/ams/\343\202\265\343\203\263\343\203\227\343\203\2532.txt" "b/modules/weko-search-ui/tests/data/ams/\343\202\265\343\203\263\343\203\227\343\203\2532.txt" new file mode 100644 index 0000000000..793629ceea --- /dev/null +++ "b/modules/weko-search-ui/tests/data/ams/\343\202\265\343\203\263\343\203\227\343\203\2532.txt" @@ -0,0 +1 @@ +上限:8バイト diff --git a/modules/weko-search-ui/tests/data/jsonld/ro-crate-metadata.json b/modules/weko-search-ui/tests/data/jsonld/ro-crate-metadata.json index 4cf2aec9d1..6783aa33ac 100644 --- a/modules/weko-search-ui/tests/data/jsonld/ro-crate-metadata.json +++ b/modules/weko-search-ui/tests/data/jsonld/ro-crate-metadata.json @@ -90,13 +90,13 @@ ], "hasPart": [ { - "@id": "data/sample.txt" + "@id": "sample.txt" }, { - "@id": "data/data.csv" + "@id": "data.csv" }, { - "@id": "data/0606/data.csv" + "@id": "0606/data.csv" }, { "@id": "https://example.com/test/sample/1" @@ -447,7 +447,7 @@ "value": "Example Organization" }, { - "@id": "data/data.csv", + "@id": "data.csv", "@type": "File", "dcterms:accessRights": "open_login", "datePublished": "2025-06-06", @@ -469,7 +469,7 @@ "wk:textExtraction": false }, { - "@id": "data/0606/data.csv", + "@id": "0606/data.csv", "@type": "File", "dcterms:accessRights": "open_login", "datePublished": "2025-06-06", @@ -491,7 +491,7 @@ "wk:textExtraction": true }, { - "@id": "data/sample.txt", + "@id": "sample.txt", "@type": "File", "dcterms:accessRights": "open_access", "datePublished": "2025-06-06", diff --git a/modules/weko-search-ui/tests/data/jsonld/ro-crate-metadata2.json b/modules/weko-search-ui/tests/data/jsonld/ro-crate-metadata2.json index 813a3e6155..14ecf082e1 100644 --- a/modules/weko-search-ui/tests/data/jsonld/ro-crate-metadata2.json +++ b/modules/weko-search-ui/tests/data/jsonld/ro-crate-metadata2.json @@ -72,7 +72,7 @@ ], "hasPart": [ { - "@id": "data/sample.rst" + "@id": "sample.rst" } ], "hasPolicy": [ @@ -155,7 +155,7 @@ ], "hasPart": [ { - "@id": "data/data.csv" + "@id": "data.csv" } ], "hasPolicy": [ @@ -344,7 +344,7 @@ "jpcoar:identifierRegistration": "DataCite" }, { - "@id": "data/sample.rst", + "@id": "sample.rst", "@type": "File", "name": "sample.rst", "contentSize": "333", @@ -360,7 +360,7 @@ "wk:accessMode": "open_access" }, { - "@id": "data/data.csv", + "@id": "data.csv", "@type": "File", "name": "data.csv", "contentSize": "1234", diff --git a/modules/weko-search-ui/tests/test_mapper.py b/modules/weko-search-ui/tests/test_mapper.py index 0392cb4f55..ce4510661f 100644 --- a/modules/weko-search-ui/tests/test_mapper.py +++ b/modules/weko-search-ui/tests/test_mapper.py @@ -1,10 +1,13 @@ +import json import pytest import xmltodict import uuid +import json from datetime import date from mock import patch from unittest.mock import MagicMock from collections import OrderedDict +from pypdfium2 import PdfiumError from weko_records.api import Mapping from weko_records.models import ItemType,ItemTypeName @@ -4717,6 +4720,7 @@ def test_to_item_metadata(self, app, db, item_type2, item_type_mapping2): assert system_info["cnri"] == "1234/5678" assert system_info["doi_ra"] == "DataCite" assert system_info["doi"] == "10.1234/5678" + assert system_info["file_path"] == ["sample.txt", "data.csv", "0606/data.csv", ""] assert system_info["non_extract"] == ["data.csv"] assert system_info["save_as_is"] == False assert system_info["amend_doi"] == "10.2964/jsik_2021_067" @@ -4757,14 +4761,53 @@ def test_to_item_metadata(self, app, db, item_type2, item_type_mapping2): assert list_record[0].get("errors") is None + schema["properties"].update({ + "item_1754636750964": { + "type": "string", + "title": "Extra", + "format": "textarea" + } + }) + item_type2.model.schema = schema + db.session.commit() + json_ld["@graph"][0].update({ + "additional": { "@id": "#additional" } + }) + json_ld["@graph"].append({ + "@id": "#additional", + "value": "This is an extra field for testing." + }) + with app.test_request_context(): + mapper = JsonLdMapper(item_type2.model.id, json_mapping) + item_metadatas, format = mapper.to_item_metadata(json_ld) + item_metadata, system_info = item_metadatas[0] + assert isinstance(item_metadata["item_1754636750964"], str) + assert isinstance(json.loads(item_metadata["item_1754636750964"]), dict) + + schema = json_data("data/jsonld/item_type_schema.json") schema = json_data("data/jsonld/item_type_schema.json") + schema["properties"].update({ + "item_1744171568909": { + "type": "array", + "items": { + "type": "object", + "properties": { + "interim": { + "type": "string" + } + } + }, + "title": "Extra", + "maxItems": 9999, + "minItems": 1 + } + }) item_type2.model.schema = schema mapping = json_data("data/jsonld/item_type_mapping.json") item_type_mapping2.model.mapping = mapping db.session.commit() json_mapping = json_data("data/jsonld/ro-crate_mapping.json") json_ld = json_data("data/jsonld/ro-crate-metadata2.json") - with app.test_request_context(): mapper = JsonLdMapper(item_type2.model.id, json_mapping) item_metadatas, format = mapper.to_item_metadata(json_ld) @@ -4776,21 +4819,25 @@ def test_to_item_metadata(self, app, db, item_type2, item_type_mapping2): assert system_info["_id"] == "_:JournalPaper1" assert system_info["link_data"][0]["item_id"] == "_:EvidenceData1" assert system_info["link_data"][0]["sele_id"] == "isSupplementedBy" + assert system_info["file_path"] == ["sample.rst"] assert thesis["pubdate"] == "2021-10-15" assert thesis["path"] == [1623632832836] assert thesis["item_30001_title0"][0]["subitem_title"] == "The Sample Dataset for WEKO" assert thesis["item_30001_title0"][1]["subitem_title"] == "WEKO用サンプルデータセット" assert thesis["files_info"][0]["key"] == "item_30001_file22" + assert thesis["item_1744171568909"][0]["interim"] evidence, system_info = item_metadatas[1] assert system_info["_id"] == "_:EvidenceData1" assert system_info["link_data"][0]["item_id"] == "_:JournalPaper1" assert system_info["link_data"][0]["sele_id"] == "isSupplementTo" + assert system_info["file_path"] == ["data.csv"] assert system_info["non_extract"] == ["data.csv"] assert evidence["pubdate"] == "2021-10-15" assert evidence["path"] == [1623632832836] assert evidence["item_30001_title0"][0]["subitem_title"] == "The Sample Dataset for WEKO, evidence part" assert evidence["item_30001_title0"][1]["subitem_title"] == "WEKO用サンプルデータセットのエビデンス部分" + assert evidence["item_1744171568909"][0]["interim"] list_record = [ { @@ -4808,16 +4855,18 @@ def test_to_item_metadata(self, app, db, item_type2, item_type_mapping2): # def deconstruct_json_ld(json_ld): # .tox/c1/bin/pytest --cov=weko_search_ui tests/test_mapper.py::TestJsonLdMapper::test__deconstruct_json_ld -v -vv -s --cov-branch --cov-report=xml --basetemp=/code/modules/weko-search-ui/.tox/c1/tmp - def test__deconstruct_json_ld(self, app): + def test__deconstruct_json_ld(self, app, item_type2): json_ld = json_data("data/jsonld/ro-crate-metadata.json") - deconstructed_metadata, format = JsonLdMapper._deconstruct_json_ld(json_ld) + mapper = JsonLdMapper(item_type2.model.id, None) + deconstructed_metadata, format = mapper._deconstruct_json_ld(json_ld) metadata, system_info = deconstructed_metadata[0] assert format == "ro-crate" assert system_info["cnri"] == "1234/5678" assert system_info["doi_ra"] == "DataCite" assert system_info["doi"] == "10.1234/5678" - assert system_info["non_extract"] == ["data/data.csv"] + assert system_info["file_path"] == ["sample.txt", "data.csv", "0606/data.csv", "https://example.com/test/sample/1"] + assert system_info["non_extract"] == ["data.csv"] assert system_info["save_as_is"] == False assert metadata["@id"] == "./" assert metadata["name"] == "The Sample Dataset for WEKO" @@ -4829,15 +4878,16 @@ def test__deconstruct_json_ld(self, app): assert metadata["dc:title[1].language"] == "ja" assert metadata["dc:type.rdf:resource"] == "http://purl.org/coar/resource_type/c_ddb1" assert metadata["dc:type.value"] == "dataset" - assert metadata["hasPart[0].@id"] == "data/sample.txt" + assert metadata["hasPart[0].@id"] == "sample.txt" assert metadata["hasPart[0].name"] == "sample.txt" - assert metadata["hasPart[1].@id"] == "data/data.csv" + assert metadata["hasPart[1].@id"] == "data.csv" assert metadata["hasPart[1].name"] == "data.csv" assert metadata["dcterms:accessRights.value"] == "embargoed access" assert not any("@type" in key for key in metadata.keys()) json_ld = json_data("data/jsonld/ro-crate-metadata2.json") - deconstructed_metadata, format = JsonLdMapper._deconstruct_json_ld(json_ld) + mapper = JsonLdMapper(item_type2.model.id, None) + deconstructed_metadata, format = mapper._deconstruct_json_ld(json_ld) thesis, system_info = deconstructed_metadata[0] assert format == "ro-crate" @@ -4846,6 +4896,7 @@ def test__deconstruct_json_ld(self, app): assert system_info["link_data"][0]["sele_id"] == "isSupplementedBy" assert system_info["link_data"][1]["item_id"] == "https://example.repo.nii.ac.jp/records/123456789" assert system_info["link_data"][1]["sele_id"] == "isSupplementedBy" + assert system_info["file_path"] == ["sample.rst"] assert thesis["@id"] == "_:JournalPaper1" assert thesis["dc:title[0].value"] == "The Sample Dataset for WEKO" assert thesis["dc:title[1].value"] == "WEKO用サンプルデータセット" @@ -4857,7 +4908,8 @@ def test__deconstruct_json_ld(self, app): assert system_info["_id"] == "_:EvidenceData1" assert system_info["link_data"][0]["item_id"] == "_:JournalPaper1" assert system_info["link_data"][0]["sele_id"] == "isSupplementTo" - assert system_info["non_extract"] == ["data/data.csv"] + assert system_info["file_path"] == ["data.csv"] + assert system_info["non_extract"] == ["data.csv"] assert evidence["@id"] == "_:EvidenceData1" assert evidence["dc:title[0].value"] == "The Sample Dataset for WEKO, evidence part" assert evidence["dc:title[1].value"] == "WEKO用サンプルデータセットのエビデンス部分" @@ -4865,7 +4917,8 @@ def test__deconstruct_json_ld(self, app): assert evidence["dc:type.@id"] == "http://purl.org/coar/resource_type/c_1843" with pytest.raises(ValueError) as ex: - deconstructed_metadata, format = JsonLdMapper._deconstruct_json_ld({}) + mapper = JsonLdMapper(item_type2.model.id, None) + deconstructed_metadata, format = mapper._deconstruct_json_ld({}) ex.match('Invalid json-ld format: "@context" is invalid.') # def to_rocrate_metadata(self, metadata): @@ -4929,10 +4982,10 @@ def test_to_rocrate_metadata(self, app, db, item_type2, item_type_mapping2, mock haspart_1 = graph["hasPart"][1]["@id"] file_1 = rocrate.dereference(haspart_1) - assert haspart_0 == "data/sample.txt" + assert haspart_0 == "sample.txt" assert file_0["name"] == "sample.txt" assert rocrate.dereference(file_0["jpcoar:URI"]["@id"])["value"] == "https://localhost/record/2000001/files/sample.txt" - assert haspart_1 == "data/data.csv" + assert haspart_1 == "data.csv" assert file_1["name"] == "data.csv" assert rocrate.dereference(file_1["jpcoar:URI"]["@id"])["value"] == "https://localhost/record/2000001/files/data.csv" @@ -5240,3 +5293,102 @@ def test_to_item_metadata_ams_dict(self, app, db, item_type2): assert item_metadata["item_1736145554459"]["subitem_date_issued_datetime"] == "2025-06-11" assert item_metadata["item_1749689698804"]["subitem_relation_type_id"]["subitem_relation_type_id_text"] == "grdm" assert item_metadata["item_1749689698804"]["subitem_relation_type"] == "isVersionOf" + + + # def extract_extended_metadata(self, list_extracted): + # .tox/c1/bin/pytest --cov=weko_search_ui tests/test_mapper.py::TestJsonLdMapper::test_extract_extended_metadata -v -vv -s --cov-branch --cov-report=html --basetemp=/code/modules/weko-search-ui/.tox/c1/tmp + def test_extract_extended_metadata(self, app, db, item_type2, mocker): + mapper = JsonLdMapper(item_type2.model.id, None) + values = ["first", "second"] + mocker.patch.object(mapper, "extract_text_from_files", + side_effect = values) + + rocrate = json_data("data/ams/with_two_extended_metadata.json") + rocrate = mapper.extract_extended_metadata([rocrate])[0] + + ids = [part["@id"] + for part in rocrate.get("hasPart", []) + if "@id" in part] + assert ids == ["data/pp.pptx"] + + ext = rocrate["extended_metadata"]["value"] + ext = json.loads(ext) + + assert len(ext) == 2 + assert ext["data/sample.txt"] == "first" + assert ext["data/guide.pdf"] == "second" + + # without hasPart + rocrate = json_data("data/ams/without_hasPart.json") + rocrate = mapper.extract_extended_metadata([rocrate])[0] + assert "extended_metadata" not in rocrate + + # def extract_text_from_files(self, filename): + # .tox/c1/bin/pytest --cov=weko_search_ui tests/test_mapper.py::TestJsonLdMapper::test_extract_text_from_files -v -vv -s --cov-branch --cov-report=html --basetemp=/code/modules/weko-search-ui/.tox/c1/tmp + def test_extract_text_from_files(self, app, db, item_type2, mocker, tmp_path): + mapper = JsonLdMapper(item_type2.model.id, None) + + mapper.data_path = str(tmp_path) + file_content = "これは\r\nテキストファイルです\r\n" + + file_name = "サンプル.txt" + tmpfile = tmp_path / file_name + tmpfile.write_text(file_content, encoding="shift_jis") + extract_text = mapper.extract_text_from_files(file_name) + assert extract_text == file_content + + file_name = "サンプル.TXT" + tmpfile = tmp_path / file_name + tmpfile.write_text(file_content, encoding="utf-8") + extract_text = mapper.extract_text_from_files(file_name) + assert extract_text == file_content + + file_name = "サンプル.txt" + tmpfile = tmp_path / file_name + tmpfile.write_text(file_content, encoding="utf-8") + extract_text = mapper.extract_text_from_files(file_name) + assert extract_text == "" + + mapper.data_path = "tests/data/ams" + + app.config.update({"WEKO_DEPOSIT_FILESIZE_LIMIT": 8}) + extract_text = mapper.extract_text_from_files("sample.txt") + assert extract_text == "This is " + + app.config.update({"WEKO_DEPOSIT_FILESIZE_LIMIT": 8}) + extract_text = mapper.extract_text_from_files("サンプル2.txt") + assert extract_text == "上限:8" + + app.config.update({"WEKO_DEPOSIT_FILESIZE_LIMIT": 2 * 1024 * 1024}) + extract_text = mapper.extract_text_from_files("sample.txt") + assert extract_text == "This is a\ntext file.\n" + + with pytest.raises(ValueError) as e: + extract_text = mapper.extract_text_from_files("png_file.txt") + assert str(e.value) == "Failed to load text file: png_file.txt" + + with pytest.raises(PdfiumError) as e: + extract_text = mapper.extract_text_from_files("png_file.pdf") + assert str(e.value) == "Failed to load PDF file: png_file.pdf" + + with pytest.raises(ValueError) as e: + extract_text = mapper.extract_text_from_files("broken_word.docx") + assert str(e.value) == "Failed to load document: broken_word.docx" + + with pytest.raises(FileNotFoundError) as e: + extract_text = mapper.extract_text_from_files("not_exist.txt") + assert str(e.value) == "File Not Found: not_exist.txt" + + mocker.patch("weko_search_ui.mapper.extract_text_from_pdf", + return_value="This is a pdf file.") + mocker.patch("os.path.isfile", return_value=True) + extract_text = mapper.extract_text_from_files("pdffile.pdf") + assert extract_text == "This is a pdf file." + + mocker.patch("weko_search_ui.mapper.extract_text_with_tika", + return_value="This is a pptx file.") + extract_text = mapper.extract_text_from_files("powerpoint.pptx") + assert extract_text == "This is a pptx file." + + extract_text = mapper.extract_text_from_files("sample.other") + assert extract_text == "" diff --git a/modules/weko-search-ui/tests/test_rest.py b/modules/weko-search-ui/tests/test_rest.py index d7255a5356..60d366e3e2 100644 --- a/modules/weko-search-ui/tests/test_rest.py +++ b/modules/weko-search-ui/tests/test_rest.py @@ -398,6 +398,10 @@ def test_IndexSearchResourceAPI(client_rest, db_register2, db_rocrate_mapping): res = client_rest.get('/v1/records', headers=headers) assert res.status_code == 200 + headers = {'Accept-Language': 'ja,en-US;q=0.9,en;q=0.8'} + res = client_rest.get('/v1/records', headers=headers) + assert res.status_code == 200 + with patch('weko_search_ui.rest.SearchSetting.get_sort_key', return_value=False): res = client_rest.get('/v1/records') assert res.status_code == 200 diff --git a/modules/weko-search-ui/tests/test_utils.py b/modules/weko-search-ui/tests/test_utils.py index 04e8336c13..ec5cb95c46 100644 --- a/modules/weko-search-ui/tests/test_utils.py +++ b/modules/weko-search-ui/tests/test_utils.py @@ -405,7 +405,7 @@ def filename(self): file = TestFile() assert check_tsv_import_items(file, True, True) - time.sleep(1) + time.sleep(0.1) file_name = "sample_file.zip" file_path = os.path.join(current_path, "data", "sample_file", file_name) prefix = current_app.config["WEKO_SEARCH_UI_IMPORT_TMP_PREFIX"] @@ -533,7 +533,7 @@ def test_check_xml_import_items(i18n_app, db_itemtype_jpcoar): with i18n_app.test_request_context(): broken_file_name = "sample_zip_broken.zip" broken_file_path = os.path.join('tests', "data", "jpcoar", "v2", broken_file_name) - time.sleep(2) + time.sleep(0.1) result = check_xml_import_items(broken_file_path, item_type.id) assert result["error"] == "The format of the specified file sample_zip_broken.zip does not support import." \ " Please specify one of the following formats: zip, tar, gztar, bztar, xztar." @@ -541,7 +541,7 @@ def test_check_xml_import_items(i18n_app, db_itemtype_jpcoar): # Case04: Xml files not included with i18n_app.test_request_context(): zip_file_path = os.path.join('tests', "data", "helloworld.zip") - time.sleep(2) + time.sleep(0.1) result = check_xml_import_items(zip_file_path, item_type.id) assert result["error"] == "The xml file was not found in the specified file helloworld.zip." \ " Check if the directory structure is correct." @@ -549,7 +549,7 @@ def test_check_xml_import_items(i18n_app, db_itemtype_jpcoar): with i18n_app.test_request_context(): failed_file_name = "no_jpcoar_xml_file.zip" failed_file_path = os.path.join('tests', "data", "jpcoar", "v2", failed_file_name) - time.sleep(2) + time.sleep(0.1) print("Case04") result = check_xml_import_items(failed_file_path, item_type.id) assert result["error"] == "The xml file was not found in the specified file no_jpcoar_xml_file.zip." \ @@ -559,21 +559,21 @@ def test_check_xml_import_items(i18n_app, db_itemtype_jpcoar): # Case05: UnicodeDecodeError occured with i18n_app.test_request_context(): with patch("weko_search_ui.utils.handle_check_file_metadata", side_effect=lambda x,y: "foo".encode('utf-16').decode('utf-8')): - time.sleep(2) + time.sleep(0.1) result = check_xml_import_items(file_path, item_type.id) assert result["error"] == "invalid start byte" # Case06: Other exception occured (without args) with i18n_app.test_request_context(): with patch("weko_search_ui.utils.handle_check_file_metadata", side_effect=Exception()): - time.sleep(2) + time.sleep(0.1) result = check_xml_import_items(file_path, item_type.id) assert result["error"] == "Internal server error" # Case07: Other exception occured (with args) with i18n_app.test_request_context(): with patch("weko_search_ui.utils.handle_check_file_metadata", side_effect=Exception({"error_msg": "error_msg_sample"})): - time.sleep(2) + time.sleep(0.1) result = check_xml_import_items(file_path, item_type.id) assert result["error"] == "error_msg_sample" @@ -707,6 +707,7 @@ def test_check_jsonld_import_items(i18n_app, db, test_indices, item_type2, item_ assert "data_path" not in result assert "item_type_id" not in result assert "list_record" not in result + time.sleep(0.1) with patch("weko_search_ui.utils.bagit.Bag.validate",side_effect=bagit.BagValidationError("Bag validation error")): result = check_jsonld_import_items(ro_crate, "SimpleZip", obj.id, shared_id=-1, validate_bagit=False) @@ -929,7 +930,8 @@ def test_handle_convert_validate_msg_to_jp(i18n_app): # def handle_validate_item_import(list_record, schema) -> list: -def test_handle_validate_item_import(app, mocker_itemtype): +# .tox/c1/bin/pytest --cov=weko_search_ui tests/test_utils.py::test_handle_validate_item_import -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-search-ui/.tox/c1/tmp +def test_handle_validate_item_import(app, mocker_itemtype, mocker): filepath = os.path.join( os.path.dirname(os.path.realpath(__file__)), "data", "csv", "data.json" ) @@ -963,6 +965,96 @@ def test_handle_validate_item_import(app, mocker_itemtype): == result ) + schema = { + "type": "object", + "properties": { + "item_xxx": { + "type": "object", + "properties": { + "subitem_yyy": { + "type": "array", + "items": { + "type": "object", + "properties": { + "subitem_zzz": { + "type": "string", + } + } + } + } + } + } + } + } + + list_record = [ + { + "metadata": { + 'item_xxx': { + 'subitem_yyy':[ + {"subitem_zzz": 123} + ] + } + } + } + ] + with app.test_request_context(): + with set_locale("en"): + result = handle_validate_item_import(list_record, schema) + warnings = result[0].get("warnings", []) + target = list_record[0]["metadata"]['item_xxx']['subitem_yyy'][0]["subitem_zzz"] + assert any("Replace value of" in w for w in warnings) + assert any("is different from existing" in w for w in warnings) + assert type(target) == str + + list_record[0]["metadata"]['item_xxx']['subitem_yyy'][0]["subitem_zzz"] = 456 + + with app.test_request_context(): + with set_locale("ja"): + result = handle_validate_item_import(list_record, schema) + warnings = result[0].get("warnings", []) + assert any("へ置き換えました。" in w for w in warnings) + assert any("と異なっています。" in w for w in warnings) + + schema = { + "type": "object", + "properties": { + "item_aaa": { + "type": "object", + "properties": { + "subitem_bbb": { + "type": "object", + "properties": { + "subitem_ccc": { + "enum": [None, "Yes|Yes", "No|No"], + } + } + } + } + } + } + } + + list_record = [ + { + "metadata": { + 'item_aaa': { + 'subitem_bbb': { + "subitem_ccc": "Yes" + } + } + } + } + ] + with app.test_request_context(): + with set_locale("en"): + result = handle_validate_item_import(list_record, schema) + assert "errors" in result[0] + + with app.test_request_context(): + with set_locale("ja"): + result = handle_validate_item_import(list_record, schema) + assert "errors" in result[0] # def represents_int(s): def test_represents_int(): diff --git a/modules/weko-search-ui/weko_search_ui/mapper.py b/modules/weko-search-ui/weko_search_ui/mapper.py index 9ebeb7d102..d792df92d4 100644 --- a/modules/weko-search-ui/weko_search_ui/mapper.py +++ b/modules/weko-search-ui/weko_search_ui/mapper.py @@ -10,6 +10,10 @@ import os import re +import json +import chardet +import mimetypes +from pypdfium2 import PdfiumError import itertools import xmltodict import traceback @@ -18,6 +22,7 @@ from functools import partial, reduce from rocrate.rocrate import ROCrate from rocrate.model.contextentity import ContextEntity +from urllib.parse import urlparse from flask import current_app, url_for @@ -26,6 +31,7 @@ Mapping, ItemTypes, FeedbackMailList, RequestMailList, ItemLink ) from weko_records.serializers.utils import get_full_mapping +from weko_deposit.utils import extract_text_from_pdf, extract_text_with_tika from .config import ROCRATE_METADATA_FILE, ROCRATE_METADATA_WK_CONTEXT_V1 @@ -1270,7 +1276,6 @@ def _get_property_type(self, chained_path): Returns: str: property type. e.g. "string", "array", "object" """ - # property_type = "" properties = self.itemtype.schema.get("properties") for p in chained_path.split("."): if properties[p].get("type") == "object": @@ -1449,6 +1454,13 @@ def _map_to_item(self, metadata, system_info): fixed_properties[key] = {} fixed_properties[key][sub_key] = value + def is_url(s: str) -> bool: + try: + result = urlparse(s) + return all([result.scheme, result.netloc]) + except ValueError: + return False + mapped_metadata = {} system_info = { **system_info, @@ -1458,115 +1470,19 @@ def _map_to_item(self, metadata, system_info): **({"uri": system_info["uri"]} if isinstance(system_info.get("uri"), str) else {}), "file_path": [ - filename[5:] for filename in system_info["file_path"] - if filename.startswith("data/") + filename if not is_url(filename) else "" + for filename in system_info["file_path"] ], "non_extract": [ - filename[5:] for filename in system_info["non_extract"] - if filename.startswith("data/") + filename for filename in system_info["non_extract"] + if not is_url(filename) ], + "warnings": [], } missing_metadata = {} - def _empty_metadata(parent_prop_key): - return fixed_properties.get(parent_prop_key, {}) - - def _set_metadata(parent, meta_props, prop_props): - """ - Args: - parent (dict): parent metadata. - meta_props (list[str]): - json-ld hierarchy split by ".". - prop_props (list[str]): - itemtype metadata metadata split by ".". - """ - # META_KEY="dc:type.@id", meta_props=["dc:type", "@id"] - # PROP_PATH=item_30001_resource_type11.resourceuri, prop_props=["item_30001_resource_type11","resourceuri"] - if len(prop_props) == 0: - raise Exception("Unexpected error: prop_props is empty.") - if len(prop_props) == 1: - if self._get_property_type(PROP_PATH) == "array": - schema = self.itemtype.schema["properties"] - for prop in PROP_PATH: - schema = schema.get(prop) - schema = schema.get("items").get("properties") - interim = list(schema.keys())[0] - if parent.get(prop_props[0]) is None: - parent[prop_props[0]] = [ - {interim: META_VALUE} - ] - else: - parent[prop_props[0]].append( - {interim: META_VALUE} - ) - else: - parent.update({prop_props[0]: META_VALUE}) - return - - full_props = PROP_PATH.split(".") - parent_prop_key = ".".join( - full_props[:(len(full_props) - len(prop_props) + 1)] - ) - m_index = re.search(r"\[(\d+)\]", meta_props[0]) - index = int(m_index.group(1)) if m_index is not None else None - if ( - not parent_prop_key in properties_mapping.values() - and not len(meta_props) == 1 - ): - # The corresponding layers are different, - # so the prop_path needs to progress to the lower layer. - sub_prop_key = parent_prop_key + "." + prop_props[1] - if self._get_property_type(parent_prop_key) == "object": - sub_prop_object = parent.get( - prop_props[0], _empty_metadata(parent_prop_key) - ) - sub_sub_object = sub_prop_object.get( - prop_props[1], _empty_metadata(sub_prop_key) - ) - _set_metadata( - sub_sub_object, meta_props[1:], prop_props[1:] - ) - sub_prop_object.update({prop_props[1]: sub_sub_object}) - parent.update({prop_props[0]: sub_prop_object}) - elif self._get_property_type(parent_prop_key) == "array": - sub_prop_array = parent.get(prop_props[0], []) - index = 0 if index is None else index - if len(sub_prop_array) <= index: - sub_prop_array.extend([ - _empty_metadata(parent_prop_key) - for _ in range(index - len(sub_prop_array) + 1) - ]) - sub_sub_object = _empty_metadata(sub_prop_key) - _set_metadata(sub_sub_object, meta_props, prop_props[1:] - ) - sub_prop_array[index].update(sub_sub_object) - parent.update({prop_props[0]: sub_prop_array}) - return - if self._get_property_type(parent_prop_key) == "object": - sub_prop_object = parent.get( - prop_props[0], _empty_metadata(parent_prop_key) - ) - if index is not None and index > 1: - return - _set_metadata(sub_prop_object, meta_props[1:], prop_props[1:] - ) - parent.update({prop_props[0]: sub_prop_object}) - - elif self._get_property_type(parent_prop_key) == "array": - sub_prop_array = parent.get(prop_props[0], []) - index = 0 if index is None else index - if len(sub_prop_array) <= index: - sub_prop_array.extend([ - _empty_metadata(parent_prop_key) - for _ in range(index - len(sub_prop_array) + 1) - ]) - _set_metadata( - sub_prop_array[index], meta_props[1:], prop_props[1:] - ) - parent.update({prop_props[0]: sub_prop_array}) - return - + from flask_babelex import gettext as _ for META_KEY, META_VALUE in metadata.items(): if not isinstance(META_KEY, str): continue @@ -1595,8 +1511,14 @@ def _set_metadata(parent, meta_props, prop_props): ) mapped_metadata["request_mail_list"] = request_mail_list elif META_PATH not in properties_mapping: - if not META_KEY.endswith("@id"): + if ("wk:" not in META_KEY and not META_KEY.endswith("@id") + and META_KEY not in ["name", "description"]): missing_metadata[META_KEY] = META_VALUE + system_info["warnings"].append(_( + "Cannot map to item type from json-ld; " + "Mapping is not defined for the metadata, " + '"%(key)s": "%(value)s"', key=META_KEY, value=META_VALUE + )) else: # item metadata meta_props = META_KEY.split(".") @@ -1610,11 +1532,28 @@ def _set_metadata(parent, meta_props, prop_props): # META_KEY="dc:type.@id", meta_props=["dc:type","@id"], # PROP_PATH=item_30001_resource_type11.resourceuri, prop_props=["item_30001_resource_type11","resourceuri"] try: - _set_metadata(mapped_metadata, meta_props, prop_props) + adjusted_meta_key = self._align_index(META_KEY, properties_mapping) + valid_path = self._check_settable_path(adjusted_meta_key) + if valid_path: + set_by_jsonpath( + mapped_metadata, valid_path, META_VALUE, fixed_properties=fixed_properties + ) + else: + missing_metadata[META_KEY] = META_VALUE + system_info["warnings"].append(_( + "Cannot map to item type from json-ld; " + "Not found mapping destination for the metadata, " + '"%(key)s": "%(value)s"', key=META_KEY, value=META_VALUE + )) except Exception as ex: current_app.logger.warning( - f"Failed to set metadata for {META_KEY}: {META_VALUE}" + f"Failed to set metadata for '{META_KEY}': '{META_VALUE}'" ) + missing_metadata[META_KEY] = META_VALUE + system_info["warnings"].append(_( + "Failed to set metadata for json-ld, " + '"%(key)s": "%(value)s"', key=META_KEY, value=META_VALUE + )) traceback.print_exc() # Check if "Extra" prepared in itemtype schema form item_map @@ -1626,10 +1565,17 @@ def _set_metadata(parent, meta_props, prop_props): extra_key).get("items").get("properties") interim = list(extra_schema.keys())[0] mapped_metadata[item_map.get("Extra")] = [ - {interim: str(missing_metadata)} + {interim: json.dumps(missing_metadata, ensure_ascii=False)} ] else: - mapped_metadata[item_map.get("Extra")] = str(missing_metadata) + mapped_metadata[item_map.get("Extra")] = json.dumps(missing_metadata, ensure_ascii=False) + system_info["warnings"] = [ + _("Metadata which could not be mapped to item type will be set in 'Extra'.") + ] + system_info["warnings"] + elif missing_metadata: + system_info["warnings"] = [ + _("Metadata which could not be mapped to item type will be discarded.") + ] + system_info["warnings"] files_info = [] for v in item_map.values(): @@ -1637,16 +1583,6 @@ def _set_metadata(parent, meta_props, prop_props): continue files_key = v.split(".")[0] - files = mapped_metadata.get(files_key, []) - - # remove "data/" prefix from label - files = [ - file["url"].update({"label": label[5:]}) - for file in files - for label in [file["url"].get("label")] - if label.startswith("data/") - ] - files_info.append({"key": files_key}) mapped_metadata["files_info"] = files_info # mapped_metadata = { @@ -1659,8 +1595,7 @@ def _set_metadata(parent, meta_props, prop_props): # } return mapped_metadata, system_info - @classmethod - def _deconstruct_json_ld(cls, json_ld): + def _deconstruct_json_ld(self, json_ld): """Deconstruct json-ld. Deconstructing json-ld metadata values ​​one by one @@ -1772,10 +1707,11 @@ def _resolve_link(parent, key, value): list_extracted = [ extracted ] else: list_extracted = [ extracted ] + self.extract_extended_metadata(list_extracted) list_deconstructed = [] for extracted in list_extracted: - metadata = cls._deconstruct_dict(extracted) + metadata = self._deconstruct_dict(extracted) system_info = {} system_info.update( {"id": extracted["identifier"]} @@ -1874,6 +1810,177 @@ def _deconstructer(metadata, parent, key, value): return return_data + + def _align_index(self, metadata_key, properties_mapping): + """Map path between json-ld and itemtype metadata. + + Align the indexes of the path and item type path in the json-ld + based on the mapping. + + Args: + metadata_key (str): path in json-ld metadata. + properties_mapping (dict): mapping between json-ld and itemtype metadata. + + Returns: + str|None: Mapped path with index, or None if not found. + """ + # Split meta_key and extract indices + key_parts = re.findall(r'([^\.\[]+)(?:\[(\d+)\])?', metadata_key) # type: list[tuple[str, str]] + key_names = [k for k, _ in key_parts] + key_indices = [idx for _, idx in key_parts] + + # Find the longest matching mapping key + for i in range(len(key_names), 0, -1): + chained = ".".join(key_names[:i]) + if chained in properties_mapping: + mapped = properties_mapping[chained] # type: str + mapped_parts = mapped.split('.') + # meta_key: hasPart[0].dcterms:accessRights + # mapping: hasPart.dcterms:accessRights -> item_30002_file35.accessrole + # → item_30002_file35[0].accessrole + result_parts = [] + for j, part in enumerate(mapped_parts): + idx = key_indices[j] if j < len(key_indices) else None + if idx: + if not re.search(r"\[\d+\]$", part): + part = f"{part}[{idx}]" + result_parts.append(part) + return ".".join(result_parts) + return None + + def _check_settable_path(self, json_path): + """Check if can put value to itemtype property by json path. + + Args: + json_path (str): JSONPath-style string in itemtype property. + + Returns: + str|None: settable json path or None if not settable. + """ + tokens = tokenize_jsonpath(json_path) + settable_path = None + + for element, index, current_path in tokens: + type = self._get_property_type(current_path) + + if type == "array": + if index is not None: + if settable_path: + settable_path += f".{element}[{index}]" + else: + settable_path = f"{element}[{index}]" + else: + if settable_path: + settable_path += f".{element}[0]" + else: + settable_path = f"{element}[0]" + else: + if index is not None and index > 0: + settable_path = None + break + else: + if settable_path: + settable_path += f".{element}" + else: + settable_path = f"{element}" + + if settable_path and re.search(r'\[\d+\]$', settable_path): + return None + + return settable_path + + + def extract_extended_metadata(self, list_extracted): + """ + Store the content of files with wk:extendedMetadata set to True in extended_metadata, + and remove files with wk:extendedMetadata set to True from hasPart. + + Args: + list_extracted (list): List of extracted metadata dictionaries. + Returns: + list: The updated list of extracted metadata with extended metadata merged. + """ + for extracted in list_extracted: + extracted.pop('extended_metadata', None) + if 'hasPart' not in extracted: + continue + file_indices = [ + idx for idx, item in enumerate(extracted['hasPart']) + if item.get('wk:extendedMetadata') is True + ] + if not file_indices: + continue + extended_metadatas = {} + extracted['extended_metadata'] = {} + for idx in reversed(file_indices): + filename = extracted['hasPart'].pop(idx).get('@id') + content = self.extract_text_from_files(filename) + extended_metadatas[filename] = content + extracted['extended_metadata']['value'] = json.dumps( + extended_metadatas, ensure_ascii=False) + return list_extracted + + def extract_text_from_files(self, filename): + """ + Extract text content from the specified file, + only if the file is of a specific MIME type. + Args: + filename (str): The name of the file to extract text from. + + Returns: + str: The extracted text content from the file. + """ + + data_path = self.data_path + "/data" + try: + file_path = os.path.join(data_path, filename) + if not os.path.isfile(file_path): + raise FileNotFoundError + data = "" + mimetype = mimetypes.guess_type(filename)[0] + file_size_limit = current_app.config['WEKO_DEPOSIT_FILESIZE_LIMIT'] + # List of text-based MIME types allowed for text extraction and processing. + text_mimetypes = current_app.config["WEKO_DEPOSIT_TEXTMIMETYPE_WHITELIST_FOR_ES"] + # All mimetypes subject to text extraction (including text_mimetypes) + extract_mimetypes = current_app.config["WEKO_MIMETYPE_WHITELIST_FOR_ES"] + if mimetype not in (extract_mimetypes+text_mimetypes): + return data + + # Extract content from file + current_app.logger.debug(f"extracting content from {filename}") + if mimetype in text_mimetypes: + with open(file_path, "rb") as fp: + data = fp.read(file_size_limit) + inf = chardet.detect(data) + if inf["encoding"] is None: + raise ValueError( + f"Failed to load text file: {filename}") + data = data.decode(inf["encoding"], errors="replace") + elif mimetype == 'application/pdf': + data = extract_text_from_pdf(file_path, file_size_limit) + else: + try: + data = extract_text_with_tika(file_path, file_size_limit) + except Exception as e: + current_app.logger.error(e) + traceback.print_exc() + raise ValueError( + f"Failed to load document: {filename}") from e + except FileNotFoundError as e: + current_app.logger.error(e) + traceback.print_exc() + raise FileNotFoundError(f"File Not Found: {filename}") from e + except PdfiumError as e: + current_app.logger.error(e) + traceback.print_exc() + raise PdfiumError(f"Failed to load PDF file: {filename}") from e + except Exception as e: + current_app.logger.error(e) + traceback.print_exc() + raise ValueError(f"Failed to load file: {filename}") from e + + return data + def to_rocrate_metadata( self, record_metadata=None, tsv_row_metadata=None, **kwargs ): @@ -2272,7 +2379,7 @@ def dereference(keys, initial_entity=None): return value # files entity reconstruction - # "@id" in files entity is format like "data/sample.txt" + # "@id" in files entity is format like "sample.txt" filename_mapping = "" file_url_url_mapping = "" for k, m in properties_mapping.items(): @@ -2289,24 +2396,21 @@ def dereference(keys, initial_entity=None): if file_key == "hasPart" and files_entity: del rocrate.root_dataset["hasPart"] - extracted_files = kwargs.get("extracted_files", []) + extracted_files = kwargs.get("extracted_files", []) # type: list[str] for entity in files_entity: - file_metadata = entity._jsonld + file_metadata = entity._jsonld # type: dict del file_metadata["@id"] del file_metadata["@type"] - filename = dereference(filename_mapping.split(".")[1:], entity) + filename = dereference(filename_mapping.split(".")[1:], entity) # type: str url = dereference(file_url_url_mapping.split(".")[1:], entity) entity.delete() - host_url = current_app.config["THEME_SITEURL"] + host_url = current_app.config["THEME_SITEURL"] # type: str if isinstance(url, str) and host_url not in url: rocrate.add_file(url, properties=file_metadata) else: file_metadata["wk:textExtraction"] = filename in extracted_files - rocrate.add_file( - dest_path=f"data/{filename}", - properties=file_metadata - ) + rocrate.add_file( dest_path=filename, properties=file_metadata) # Extra if "Extra" in item_map: @@ -2351,3 +2455,126 @@ def dereference(keys, initial_entity=None): rocrate.root_dataset["wk:metadataAutoFill"] = False return rocrate + + +def set_by_jsonpath(root, path, value, fixed_properties=None): + """ + Set a value inside a nested dict/list structure using a JSONPath-like syntax. + + Args: + root (dict | list): The root object to modify. + path (str): The JSONPath-like path to the location to set the value. + value: The value to set at the specified location. + fixed_properties (dict | None): + Fixed value info in the form {parent_path: {sub_key: value}}. + If the path matches when creating a dict, merge these values. + """ + tokens = [] + i = 0 + while i < len(path): + if path[i] == '.': + i += 1 + continue + if path[i] == '[': + j = path.find(']', i) + if j == -1: + raise ValueError("Unmatched '[' in path") + index_str = path[i+1:j] + if not index_str.isdigit(): + raise ValueError("Only integer indices are allowed inside []") + tokens.append(int(index_str)) + i = j + 1 + else: + j = i + while j < len(path) and path[j] not in '.[': + j += 1 + tokens.append(path[i:j]) + i = j + + if not tokens: + raise ValueError("Empty path") + + # Traverse until the second last token + cur = root + for idx in range(len(tokens) - 1): + tok = tokens[idx] + next_tok = tokens[idx + 1] + + if isinstance(tok, int): + # Current should be a list + if not isinstance(cur, list): + raise TypeError("Expected list when accessing by index") + # Extend list if necessary + while len(cur) <= tok: + if isinstance(next_tok, int): + cur.append([]) + else: + # Merge fixed properties if available + parent_path = ".".join( + [t for t in tokens[:idx+1] if not isinstance(t, int)] + ) + if fixed_properties and parent_path in fixed_properties: + d = dict(fixed_properties[parent_path]) + cur.append(d) + else: + cur.append({}) + cur = cur[tok] + else: + # Current should be a dict + if not isinstance(cur, dict): + raise TypeError("Expected dict when accessing by key") + if tok not in cur: + if isinstance(next_tok, int): + cur[tok] = [] + else: + # Merge fixed properties if available + parent_path = ".".join( + [t for t in tokens[:idx+1] if not isinstance(t, int)] + ) + if fixed_properties and parent_path in fixed_properties: + d = dict(fixed_properties[parent_path]) + cur[tok] = d + else: + cur[tok] = {} + cur = cur[tok] + + # Set the final value + last = tokens[-1] + if isinstance(last, int): + if not isinstance(cur, list): + raise TypeError("Expected list for final index") + while len(cur) <= last: + cur.append(None) + cur[last] = value + else: + if not isinstance(cur, dict): + raise TypeError("Expected dict for final key") + cur[last] = value + + +def tokenize_jsonpath(json_path): + """Tokenize the json path. + + Separate keys and indices from json path. + + Args: + json_path (str): json path. + + Returns: + list[tuple[str,int|None],str]: list of tokens (element, index). + """ + matches = re.findall(r"([^.\[]+)(?:\[(\d+)\])?\.?", json_path) + tokens = [] # type: list[tuple[str, int|None, str]] + current_path = "" + for element, index_str in matches: + if current_path: + current_path += "." + element + else: + current_path = element + + if index_str: + index = int(index_str) + else: + index = None + tokens.append((element, index, current_path)) + return tokens diff --git a/modules/weko-search-ui/weko_search_ui/rest.py b/modules/weko-search-ui/weko_search_ui/rest.py index 20bdd7ecf8..5587287b6f 100644 --- a/modules/weko-search-ui/weko_search_ui/rest.py +++ b/modules/weko-search-ui/weko_search_ui/rest.py @@ -594,6 +594,8 @@ def get_v1(self, **kwargs): try: # Language setting language = request.headers.get('Accept-Language') + if isinstance(language, str): + language = language.split(',')[0].split(';')[0].strip() if language: get_locale().language = language diff --git a/modules/weko-search-ui/weko_search_ui/translations/en/LC_MESSAGES/messages.mo b/modules/weko-search-ui/weko_search_ui/translations/en/LC_MESSAGES/messages.mo index 343b548c26..727dc9c791 100644 Binary files a/modules/weko-search-ui/weko_search_ui/translations/en/LC_MESSAGES/messages.mo and b/modules/weko-search-ui/weko_search_ui/translations/en/LC_MESSAGES/messages.mo differ diff --git a/modules/weko-search-ui/weko_search_ui/translations/en/LC_MESSAGES/messages.po b/modules/weko-search-ui/weko_search_ui/translations/en/LC_MESSAGES/messages.po index 821582acfd..92aab53922 100644 --- a/modules/weko-search-ui/weko_search_ui/translations/en/LC_MESSAGES/messages.po +++ b/modules/weko-search-ui/weko_search_ui/translations/en/LC_MESSAGES/messages.po @@ -8,102 +8,167 @@ msgid "" msgstr "" "Project-Id-Version: weko-search-ui 0.1.0.dev20170000\n" "Report-Msgid-Bugs-To: wekosoftware@nii.ac.jp\n" -"POT-Creation-Date: 2025-05-27 19:16+0900\n" +"POT-Creation-Date: 2025-11-12 12:12+0900\n" "PO-Revision-Date: 2025-04-09 23:22+0900\n" "Last-Translator: FULL NAME \n" "Language: en\n" "Language-Team: en \n" -"Plural-Forms: nplurals=2; plural=(n != 1)\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.5.1\n" +"Generated-By: Babel 2.17.0\n" -#: tests/conftest.py:399 +#: tests/conftest.py:409 msgid "write your own license" msgstr "" -#: tests/conftest.py:404 +#: tests/conftest.py:415 msgid "Creative Commons CC0 1.0 Universal Public Domain Designation" msgstr "" -#: tests/conftest.py:420 +#: tests/conftest.py:430 msgid "Creative Commons Attribution 3.0 Unported (CC BY 3.0)" msgstr "" -#: tests/conftest.py:432 +#: tests/conftest.py:443 msgid "Creative Commons Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)" msgstr "" -#: tests/conftest.py:447 +#: tests/conftest.py:458 msgid "Creative Commons Attribution-NoDerivs 3.0 Unported (CC BY-ND 3.0)" msgstr "" -#: tests/conftest.py:461 +#: tests/conftest.py:472 msgid "Creative Commons Attribution-NonCommercial 3.0 Unported (CC BY-NC 3.0)" msgstr "" -#: tests/conftest.py:476 +#: tests/conftest.py:487 msgid "" "Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported (CC " "BY-NC-SA 3.0)" msgstr "" -#: tests/conftest.py:491 +#: tests/conftest.py:502 msgid "" "Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported (CC BY-" "NC-ND 3.0)" msgstr "" -#: tests/conftest.py:507 +#: tests/conftest.py:517 msgid "Creative Commons Attribution 4.0 International (CC BY 4.0)" msgstr "" -#: tests/conftest.py:519 +#: tests/conftest.py:530 msgid "Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)" msgstr "" -#: tests/conftest.py:534 +#: tests/conftest.py:545 msgid "" "Creative Commons Attribution-NoDerivatives 4.0 International (CC BY-ND " "4.0)" msgstr "" -#: tests/conftest.py:549 +#: tests/conftest.py:560 msgid "" "Creative Commons Attribution-NonCommercial 4.0 International (CC BY-NC " "4.0)" msgstr "" -#: tests/conftest.py:564 +#: tests/conftest.py:575 msgid "" "Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International " "(CC BY-NC-SA 4.0)" msgstr "" -#: tests/conftest.py:579 +#: tests/conftest.py:590 msgid "" "Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 " "International (CC BY-NC-ND 4.0)" msgstr "" -#: weko_search_ui/admin.py:158 +#: tests/helpers.py:84 +msgid "Bagging a parent of the current directory is not supported" +msgstr "" + +#: tests/helpers.py:87 +#, python-format +msgid "Creating tag for directory %s" +msgstr "" + +#: tests/helpers.py:90 tests/helpers.py:91 +#, python-format +msgid "Bag directory %s does not exist" +msgstr "" + +#: tests/helpers.py:96 tests/helpers.py:97 +#, python-format +msgid "Bag directory %s does not contain a data directory" +msgstr "" + +#: tests/helpers.py:104 +#, python-format +msgid "" +"Unable to write to the following directories and files:\n" +"%s" +msgstr "" + +#: tests/helpers.py:107 +msgid "Missing permissions to move all files and directories" +msgstr "" + +#: tests/helpers.py:114 +#, python-format +msgid "" +"The following directories do not have read permissions:\n" +"%s" +msgstr "" + +#: tests/helpers.py:119 +#, python-format +msgid "" +"The following files do not have read permissions:\n" +"%s" +msgstr "" + +#: tests/helpers.py:123 +msgid "Read permissions are required to calculate file fixities" +msgstr "" + +#: tests/helpers.py:126 +msgid "Creating data directory" +msgstr "" + +#: tests/helpers.py:143 +msgid "Creating bagit.txt" +msgstr "" + +#: tests/helpers.py:148 +msgid "Creating bag-info.txt" +msgstr "" + +#: tests/helpers.py:167 +#, python-format +msgid "An error occurred creating a bag in %s" +msgstr "" + +#: weko_search_ui/admin.py:157 msgid "The following item(s) cannot be deleted." msgstr "" -#: weko_search_ui/admin.py:163 +#: weko_search_ui/admin.py:162 msgid "DOI granting item(s):" msgstr "" -#: weko_search_ui/admin.py:168 +#: weko_search_ui/admin.py:167 msgid "Editing item(s):" msgstr "" -#: weko_search_ui/admin.py:171 +#: weko_search_ui/admin.py:170 msgid "Success" msgstr "" -#: weko_search_ui/admin.py:204 +#: weko_search_ui/admin.py:203 msgid "Index Delete is in progress on another device." msgstr "" @@ -114,27 +179,27 @@ msgid "" "you want to continue deleting items that are not grant DOI?" msgstr "" -#: weko_search_ui/admin.py:216 +#: weko_search_ui/admin.py:215 msgid "Are you sure you want to delete it?" msgstr "Are you sure you want to discard the input contents?" -#: weko_search_ui/admin.py:219 +#: weko_search_ui/admin.py:218 msgid "No such index." msgstr "" -#: weko_search_ui/admin.py:310 weko_search_ui/admin.py:1243 +#: weko_search_ui/admin.py:309 weko_search_ui/admin.py:1250 msgid "Custom Sort" msgstr "" -#: weko_search_ui/admin.py:312 weko_search_ui/admin.py:1234 +#: weko_search_ui/admin.py:311 weko_search_ui/admin.py:1241 msgid "Bulk Delete" msgstr "" -#: weko_search_ui/admin.py:325 +#: weko_search_ui/admin.py:324 msgid "Bulk Update" msgstr "" -#: weko_search_ui/admin.py:440 weko_search_ui/admin.py:868 +#: weko_search_ui/admin.py:439 weko_search_ui/admin.py:872 #: weko_search_ui/templates/weko_search_ui/admin/export.html:53 #: weko_search_ui/templates/weko_search_ui/admin/import.html:96 #: weko_search_ui/templates/weko_search_ui/admin/rocrate_import.html:97 @@ -142,29 +207,29 @@ msgstr "" msgid "Internal server error" msgstr "" -#: weko_search_ui/admin.py:1225 weko_search_ui/admin.py:1242 +#: weko_search_ui/admin.py:1232 weko_search_ui/admin.py:1249 #: weko_search_ui/templates/weko_search_ui/admin/import.html:53 #: weko_search_ui/templates/weko_search_ui/admin/rocrate_import.html:54 msgid "Index Tree" msgstr "" -#: weko_search_ui/admin.py:1233 weko_search_ui/admin.py:1251 -#: weko_search_ui/admin.py:1260 weko_search_ui/admin.py:1270 +#: weko_search_ui/admin.py:1240 weko_search_ui/admin.py:1258 +#: weko_search_ui/admin.py:1267 weko_search_ui/admin.py:1277 msgid "Items" msgstr "" -#: weko_search_ui/admin.py:1252 +#: weko_search_ui/admin.py:1259 #: weko_search_ui/templates/weko_search_ui/admin/import.html:42 #: weko_search_ui/templates/weko_search_ui/admin/rocrate_import.html:43 msgid "Import" msgstr "" -#: weko_search_ui/admin.py:1261 +#: weko_search_ui/admin.py:1268 #: weko_search_ui/templates/weko_search_ui/admin/rocrate_import.html:42 msgid "RO-Crate Import" msgstr "" -#: weko_search_ui/admin.py:1271 +#: weko_search_ui/admin.py:1278 msgid "Bulk Export" msgstr "" @@ -172,23 +237,53 @@ msgstr "" msgid "contents" msgstr "" -#: weko_search_ui/mapper.py:1360 +#: weko_search_ui/mapper.py:1375 +#, python-brace-format msgid "\"{key}\" is required." msgstr "" -#: weko_search_ui/mapper.py:1381 +#: weko_search_ui/mapper.py:1396 +#, python-brace-format msgid "\"{key}\" is not in itemtype." msgstr "" -#: weko_search_ui/mapper.py:1383 +#: weko_search_ui/mapper.py:1398 +#, python-brace-format msgid "\"{key}\" is not in itemtype, did you mean \"{similar_key}\"?" msgstr "" -#: weko_search_ui/tasks.py:328 +#: weko_search_ui/mapper.py:1517 +#, python-format +msgid "" +"Cannot map to item type from json-ld; Mapping is not defined for the " +"metadata, \"%(key)s\": \"%(value)s\"" +msgstr "" + +#: weko_search_ui/mapper.py:1543 +#, python-format +msgid "" +"Cannot map to item type from json-ld; Not found mapping destination for " +"the metadata, \"%(key)s\": \"%(value)s\"" +msgstr "" + +#: weko_search_ui/mapper.py:1553 +#, python-format +msgid "Failed to set metadata for json-ld, \"%(key)s\": \"%(value)s\"" +msgstr "" + +#: weko_search_ui/mapper.py:1572 +msgid "Metadata which could not be mapped to item type will be set in 'Extra'." +msgstr "" + +#: weko_search_ui/mapper.py:1576 +msgid "Metadata which could not be mapped to item type will be discarded." +msgstr "" + +#: weko_search_ui/tasks.py:337 msgid "`wk:metadata_replace` flag cannot be used in RO-Crate Import." msgstr "" -#: weko_search_ui/tasks.py:343 +#: weko_search_ui/tasks.py:352 msgid "`wk:isSplited` flag cannot be used in RO-Crate Import." msgstr "" @@ -196,25 +291,28 @@ msgstr "" msgid "The same item may have been registered." msgstr "" -#: weko_search_ui/utils.py:592 weko_search_ui/utils.py:708 +#: weko_search_ui/utils.py:593 weko_search_ui/utils.py:709 +#, python-brace-format msgid "" "The format of the specified file {} does not support import. Please " "specify one of the following formats: zip, tar, gztar, bztar, xztar." msgstr "" -#: weko_search_ui/utils.py:600 +#: weko_search_ui/utils.py:601 +#, python-brace-format msgid "" "The csv/tsv file was not found in the specified file {}. Check if the " "directory structure is correct." msgstr "" -#: weko_search_ui/utils.py:649 +#: weko_search_ui/utils.py:650 msgid "" "The item type of the item to be imported is missing or has already been " "deleted." msgstr "" -#: weko_search_ui/utils.py:715 +#: weko_search_ui/utils.py:716 +#, python-brace-format msgid "" "The xml file was not found in the specified file {}. Check if the " "directory structure is correct." @@ -224,268 +322,305 @@ msgstr "" msgid "The item type ID specified in the XML file does not exist." msgstr "" -#: weko_search_ui/utils.py:1215 +#: weko_search_ui/utils.py:1206 +#, python-brace-format msgid "" "There is an error in the format of the first line of the header of the {}" " file." msgstr "" -#: weko_search_ui/utils.py:1234 +#: weko_search_ui/utils.py:1225 +#, python-brace-format msgid "The item type ID specified in the {} file does not exist." msgstr "" -#: weko_search_ui/utils.py:1243 +#: weko_search_ui/utils.py:1236 msgid "Cannot register because the specified item type is not the latest version." msgstr "" -#: weko_search_ui/utils.py:1258 +#: weko_search_ui/utils.py:1250 +#, python-brace-format msgid "The following metadata keys are duplicated.
{}" msgstr "" -#: weko_search_ui/utils.py:1281 +#: weko_search_ui/utils.py:1274 +#, python-brace-format msgid "The item does not consistent with the specified item type.
{}" msgstr "" -#: weko_search_ui/utils.py:1311 +#: weko_search_ui/utils.py:1303 +#, python-brace-format msgid "Cannot read {} file correctly." msgstr "" -#: weko_search_ui/utils.py:1331 +#: weko_search_ui/utils.py:1328 +#, python-brace-format msgid "" "The following items are not registered because they do not exist in the " "specified item type. {}" msgstr "" -#: weko_search_ui/utils.py:1341 +#: weko_search_ui/utils.py:1336 +#, python-brace-format msgid "" "The {} file could not be read. Make sure the file format is {} and that " "the file is UTF-8 encoded." msgstr "" -#: weko_search_ui/utils.py:1372 +#: weko_search_ui/utils.py:1369 msgid "" "The XML file could not be read. Make sure the file format is XML and that" " the file is UTF-8 encoded." msgstr "" -#: weko_search_ui/utils.py:1444 +#: weko_search_ui/utils.py:1441 msgid "Please specify item ID by half-width number." msgstr "" -#: weko_search_ui/utils.py:1459 +#: weko_search_ui/utils.py:1462 +#, python-format +msgid "Replace value of %(target_path)s from %(old_value)s to '%(new_value)s'." +msgstr "" + +#: weko_search_ui/utils.py:1475 msgid "Specified item type does not exist." msgstr "" -#: weko_search_ui/utils.py:1533 +#: weko_search_ui/utils.py:1481 +#, python-format +msgid "Specified %(type)s is different from existing %(existing_type)s." +msgstr "" + +#: weko_search_ui/utils.py:1556 msgid "Specified URI and system URI do not match." msgstr "" -#: weko_search_ui/utils.py:1545 +#: weko_search_ui/utils.py:1568 msgid "Item does not exist in the system." msgstr "" -#: weko_search_ui/utils.py:1552 +#: weko_search_ui/utils.py:1575 msgid "Item already DELETED in the system." msgstr "" -#: weko_search_ui/utils.py:1569 +#: weko_search_ui/utils.py:1592 msgid "Please specify either \"Keep\" or \"Upgrade\"." msgstr "" -#: weko_search_ui/utils.py:2472 +#: weko_search_ui/utils.py:2508 msgid "Title is required item." msgstr "" -#: weko_search_ui/utils.py:2489 +#: weko_search_ui/utils.py:2525 +#, python-brace-format msgid "{} is required item." msgstr "" -#: weko_search_ui/utils.py:2492 +#: weko_search_ui/utils.py:2528 +#, python-brace-format msgid "Please set \"public\" or \"private\" for {}." msgstr "" -#: weko_search_ui/utils.py:2533 +#: weko_search_ui/utils.py:2569 +#, python-brace-format msgid "The specified {} does not exist in system." msgstr "" -#: weko_search_ui/utils.py:2543 +#: weko_search_ui/utils.py:2579 +#, python-brace-format msgid "Specified {} does not match with existing index." msgstr "" -#: weko_search_ui/utils.py:2587 +#: weko_search_ui/utils.py:2623 msgid "Your role cannot register items in this index." msgstr "" -#: weko_search_ui/utils.py:2608 +#: weko_search_ui/utils.py:2644 msgid "Both of IndexID and POS_INDEX are not being set." msgstr "" -#: weko_search_ui/utils.py:2654 weko_search_ui/utils.py:2679 +#: weko_search_ui/utils.py:2690 weko_search_ui/utils.py:2715 +#, python-brace-format msgid "Specified {} is invalid." msgstr "" -#: weko_search_ui/utils.py:2721 weko_search_ui/utils.py:2754 -#: weko_search_ui/utils.py:2846 weko_search_ui/utils.py:2916 -#: weko_search_ui/utils.py:2920 weko_search_ui/utils.py:2942 -#: weko_search_ui/utils.py:2966 +#: weko_search_ui/utils.py:2757 weko_search_ui/utils.py:2790 +#: weko_search_ui/utils.py:2882 weko_search_ui/utils.py:2952 +#: weko_search_ui/utils.py:2956 weko_search_ui/utils.py:2978 +#: weko_search_ui/utils.py:3002 +#, python-brace-format msgid "Please specify {}." msgstr "" -#: weko_search_ui/utils.py:2724 weko_search_ui/utils.py:2923 +#: weko_search_ui/utils.py:2760 weko_search_ui/utils.py:2960 +#, python-brace-format msgid "The specified {} exceeds the maximum length." msgstr "" -#: weko_search_ui/utils.py:2739 weko_search_ui/utils.py:2897 -#: weko_search_ui/utils.py:2907 weko_search_ui/utils.py:2938 +#: weko_search_ui/utils.py:2775 weko_search_ui/utils.py:2933 +#: weko_search_ui/utils.py:2943 weko_search_ui/utils.py:2974 +#, python-brace-format msgid "Specified Prefix of {} is incorrect." msgstr "" -#: weko_search_ui/utils.py:2747 weko_search_ui/utils.py:2891 -#: weko_search_ui/utils.py:2900 +#: weko_search_ui/utils.py:2783 weko_search_ui/utils.py:2927 +#: weko_search_ui/utils.py:2936 +#, python-brace-format msgid "{} cannot be set." msgstr "" -#: weko_search_ui/utils.py:2756 weko_search_ui/utils.py:2760 -#: weko_search_ui/utils.py:2829 weko_search_ui/utils.py:2968 +#: weko_search_ui/utils.py:2793 weko_search_ui/utils.py:2797 +#: weko_search_ui/utils.py:2865 weko_search_ui/utils.py:3005 +#, python-brace-format msgid "Specified {} is different from existing {}." msgstr "" -#: weko_search_ui/utils.py:2780 +#: weko_search_ui/utils.py:2817 msgid "" "When assigning a DOI to an item, it must be associated with an index " "whose index status is \"Public\" and Harvest Publishing is \"Public\"." msgstr "" -#: weko_search_ui/utils.py:2785 +#: weko_search_ui/utils.py:2822 msgid "" "Since the item has a DOI, it must be associated with an index whose index" " status is \"Public\" and whose Harvest Publishing is \"Public\"." msgstr "" -#: weko_search_ui/utils.py:2796 +#: weko_search_ui/utils.py:2832 msgid "You cannot keep an item private because it has a DOI." msgstr "" -#: weko_search_ui/utils.py:2850 weko_search_ui/utils.py:4070 +#: weko_search_ui/utils.py:2887 weko_search_ui/utils.py:4106 msgid "DOI_RA should be set by one of JaLC, Crossref, DataCite, NDL JaLC." msgstr "" -#: weko_search_ui/utils.py:3001 +#: weko_search_ui/utils.py:3037 +#, python-brace-format msgid "Item Link type: '{}' is not one of {}." msgstr "" -#: weko_search_ui/utils.py:3005 +#: weko_search_ui/utils.py:3041 msgid "Please specify Item URL for item link." msgstr "" -#: weko_search_ui/utils.py:3019 +#: weko_search_ui/utils.py:3055 msgid "Specified Item Link URI and system URI do not match." msgstr "" -#: weko_search_ui/utils.py:3025 +#: weko_search_ui/utils.py:3061 msgid "Linking item does not exist in the system." msgstr "" -#: weko_search_ui/utils.py:3028 +#: weko_search_ui/utils.py:3064 msgid "Linking item already deleted in the system." msgstr "" -#: weko_search_ui/utils.py:3067 +#: weko_search_ui/utils.py:3103 msgid "It is not allowed to create links to the item itself." msgstr "" -#: weko_search_ui/utils.py:3072 +#: weko_search_ui/utils.py:3108 +#, python-brace-format msgid "It is not allowed to create links other than {} between split items." msgstr "" -#: weko_search_ui/utils.py:3108 +#: weko_search_ui/utils.py:3144 msgid "Duplicate Item Link." msgstr "" -#: weko_search_ui/utils.py:3447 +#: weko_search_ui/utils.py:3483 +#, python-brace-format msgid "" "One of the following required values ​​has not been " "registered.
{}
" msgstr "" -#: weko_search_ui/utils.py:3452 +#: weko_search_ui/utils.py:3489 +#, python-brace-format msgid "" "The mapping of required items for DOI validation is not set. Please " "recheck the following mapping settings.
{}" msgstr "" -#: weko_search_ui/utils.py:3462 +#: weko_search_ui/utils.py:3498 +#, python-brace-format msgid "The following metadata are required.
{}" msgstr "" -#: weko_search_ui/utils.py:3467 +#: weko_search_ui/utils.py:3504 +#, python-brace-format msgid "One of the following metadata is required.
{}
" msgstr "" -#: weko_search_ui/utils.py:3530 weko_search_ui/utils.py:3542 +#: weko_search_ui/utils.py:3567 weko_search_ui/utils.py:3579 msgid "Please specify the date with any format of YYYY-MM-DD, YYYY-MM, YYYY." msgstr "" -#: weko_search_ui/utils.py:3536 +#: weko_search_ui/utils.py:3572 +#, python-brace-format msgid "Replace value of {} from {} to {}." msgstr "" -#: weko_search_ui/utils.py:3559 +#: weko_search_ui/utils.py:3595 msgid "Please specify PubDate with YYYY-MM-DD." msgstr "" -#: weko_search_ui/utils.py:3589 +#: weko_search_ui/utils.py:3625 msgid "ID is specified for the newly registered item. Ignore the ID and register." msgstr "" -#: weko_search_ui/utils.py:3672 +#: weko_search_ui/utils.py:3708 msgid "Please specify Open Access Date with YYYY-MM-DD." msgstr "" -#: weko_search_ui/utils.py:3909 weko_search_ui/utils.py:4055 -#: weko_search_ui/utils.py:4061 +#: weko_search_ui/utils.py:3945 weko_search_ui/utils.py:4091 +#: weko_search_ui/utils.py:4097 msgid "Please specify DOI prefix/suffix." msgstr "" -#: weko_search_ui/utils.py:4047 +#: weko_search_ui/utils.py:4083 msgid "The specified DOI is wrong and fixed with the registered DOI." msgstr "" -#: weko_search_ui/utils.py:4050 +#: weko_search_ui/utils.py:4086 msgid "" "The specified DOI RA is wrong and fixed with the correct DOI RA of the " "registered DOI." msgstr "" -#: weko_search_ui/utils.py:4057 weko_search_ui/utils.py:4063 +#: weko_search_ui/utils.py:4093 weko_search_ui/utils.py:4099 msgid "Please specify DOI suffix." msgstr "" -#: weko_search_ui/utils.py:4066 +#: weko_search_ui/utils.py:4102 msgid "Do not specify DOI suffix." msgstr "" -#: weko_search_ui/utils.py:4072 +#: weko_search_ui/utils.py:4108 msgid "Specified Prefix of DOI is incorrect." msgstr "" -#: weko_search_ui/utils.py:4104 +#: weko_search_ui/utils.py:4141 msgid "" "Please specify the image file(gif, jpg, jpe, jpeg, png, bmp, tiff, tif) " "for the thumbnail." msgstr "" -#: weko_search_ui/utils.py:5220 +#: weko_search_ui/utils.py:5256 +#, python-brace-format msgid "The file specified in ({}) does not exist." msgstr "" -#: weko_search_ui/utils.py:5224 +#: weko_search_ui/utils.py:5261 +#, python-brace-format msgid "" "The file specified in ({}) does not exist.
The file will not be " "updated. Update only the metadata with csv/tsv contents." msgstr "" -#: weko_search_ui/utils.py:5511 +#: weko_search_ui/utils.py:5547 +#, python-brace-format msgid "The file name specified in {} and {} do not match." msgstr "" diff --git a/modules/weko-search-ui/weko_search_ui/translations/ja/LC_MESSAGES/messages.mo b/modules/weko-search-ui/weko_search_ui/translations/ja/LC_MESSAGES/messages.mo index 2b124cce9d..fbc36b019c 100644 Binary files a/modules/weko-search-ui/weko_search_ui/translations/ja/LC_MESSAGES/messages.mo and b/modules/weko-search-ui/weko_search_ui/translations/ja/LC_MESSAGES/messages.mo differ diff --git a/modules/weko-search-ui/weko_search_ui/translations/ja/LC_MESSAGES/messages.po b/modules/weko-search-ui/weko_search_ui/translations/ja/LC_MESSAGES/messages.po index 8a47f9a05d..c7dce7d302 100644 --- a/modules/weko-search-ui/weko_search_ui/translations/ja/LC_MESSAGES/messages.po +++ b/modules/weko-search-ui/weko_search_ui/translations/ja/LC_MESSAGES/messages.po @@ -8,102 +8,167 @@ msgid "" msgstr "" "Project-Id-Version: weko-search-ui 0.1.0.dev20170000\n" "Report-Msgid-Bugs-To: wekosoftware@nii.ac.jp\n" -"POT-Creation-Date: 2025-05-27 19:16+0900\n" +"POT-Creation-Date: 2025-11-12 12:12+0900\n" "PO-Revision-Date: 2025-04-09 22:35+0900\n" "Last-Translator: FULL NAME \n" "Language: ja\n" "Language-Team: ja \n" -"Plural-Forms: nplurals=1; plural=0\n" +"Plural-Forms: nplurals=1; plural=0;\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.5.1\n" +"Generated-By: Babel 2.17.0\n" -#: tests/conftest.py:399 +#: tests/conftest.py:409 msgid "write your own license" msgstr "" -#: tests/conftest.py:404 +#: tests/conftest.py:415 msgid "Creative Commons CC0 1.0 Universal Public Domain Designation" msgstr "" -#: tests/conftest.py:420 +#: tests/conftest.py:430 msgid "Creative Commons Attribution 3.0 Unported (CC BY 3.0)" msgstr "" -#: tests/conftest.py:432 +#: tests/conftest.py:443 msgid "Creative Commons Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)" msgstr "" -#: tests/conftest.py:447 +#: tests/conftest.py:458 msgid "Creative Commons Attribution-NoDerivs 3.0 Unported (CC BY-ND 3.0)" msgstr "" -#: tests/conftest.py:461 +#: tests/conftest.py:472 msgid "Creative Commons Attribution-NonCommercial 3.0 Unported (CC BY-NC 3.0)" msgstr "" -#: tests/conftest.py:476 +#: tests/conftest.py:487 msgid "" "Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported (CC " "BY-NC-SA 3.0)" msgstr "" -#: tests/conftest.py:491 +#: tests/conftest.py:502 msgid "" "Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported (CC BY-" "NC-ND 3.0)" msgstr "" -#: tests/conftest.py:507 +#: tests/conftest.py:517 msgid "Creative Commons Attribution 4.0 International (CC BY 4.0)" msgstr "" -#: tests/conftest.py:519 +#: tests/conftest.py:530 msgid "Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)" msgstr "" -#: tests/conftest.py:534 +#: tests/conftest.py:545 msgid "" "Creative Commons Attribution-NoDerivatives 4.0 International (CC BY-ND " "4.0)" msgstr "" -#: tests/conftest.py:549 +#: tests/conftest.py:560 msgid "" "Creative Commons Attribution-NonCommercial 4.0 International (CC BY-NC " "4.0)" msgstr "" -#: tests/conftest.py:564 +#: tests/conftest.py:575 msgid "" "Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International " "(CC BY-NC-SA 4.0)" msgstr "" -#: tests/conftest.py:579 +#: tests/conftest.py:590 msgid "" "Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 " "International (CC BY-NC-ND 4.0)" msgstr "" -#: weko_search_ui/admin.py:158 +#: tests/helpers.py:84 +msgid "Bagging a parent of the current directory is not supported" +msgstr "" + +#: tests/helpers.py:87 +#, python-format +msgid "Creating tag for directory %s" +msgstr "" + +#: tests/helpers.py:90 tests/helpers.py:91 +#, python-format +msgid "Bag directory %s does not exist" +msgstr "" + +#: tests/helpers.py:96 tests/helpers.py:97 +#, python-format +msgid "Bag directory %s does not contain a data directory" +msgstr "" + +#: tests/helpers.py:104 +#, python-format +msgid "" +"Unable to write to the following directories and files:\n" +"%s" +msgstr "" + +#: tests/helpers.py:107 +msgid "Missing permissions to move all files and directories" +msgstr "" + +#: tests/helpers.py:114 +#, python-format +msgid "" +"The following directories do not have read permissions:\n" +"%s" +msgstr "" + +#: tests/helpers.py:119 +#, python-format +msgid "" +"The following files do not have read permissions:\n" +"%s" +msgstr "" + +#: tests/helpers.py:123 +msgid "Read permissions are required to calculate file fixities" +msgstr "" + +#: tests/helpers.py:126 +msgid "Creating data directory" +msgstr "" + +#: tests/helpers.py:143 +msgid "Creating bagit.txt" +msgstr "" + +#: tests/helpers.py:148 +msgid "Creating bag-info.txt" +msgstr "" + +#: tests/helpers.py:167 +#, python-format +msgid "An error occurred creating a bag in %s" +msgstr "" + +#: weko_search_ui/admin.py:157 msgid "The following item(s) cannot be deleted." msgstr "以下のアイテムを削除することはできませんでした。" -#: weko_search_ui/admin.py:163 +#: weko_search_ui/admin.py:162 msgid "DOI granting item(s):" msgstr "DOI付与済みのアイテム:" -#: weko_search_ui/admin.py:168 +#: weko_search_ui/admin.py:167 msgid "Editing item(s):" msgstr "編集中のアイテム:" -#: weko_search_ui/admin.py:171 +#: weko_search_ui/admin.py:170 msgid "Success" msgstr "" -#: weko_search_ui/admin.py:204 +#: weko_search_ui/admin.py:203 msgid "Index Delete is in progress on another device." msgstr "" @@ -114,27 +179,27 @@ msgid "" "you want to continue deleting items that are not grant DOI?" msgstr "削除対象にDOI付与済みアイテムが含まれています。
DOI付与済みアイテムはDOI取下げを行わないと削除できません。
DOI付与済み以外のアイテム削除を続行しますか?" -#: weko_search_ui/admin.py:216 +#: weko_search_ui/admin.py:215 msgid "Are you sure you want to delete it?" msgstr "削除してよろしいですか?" -#: weko_search_ui/admin.py:219 +#: weko_search_ui/admin.py:218 msgid "No such index." msgstr "" -#: weko_search_ui/admin.py:310 weko_search_ui/admin.py:1243 +#: weko_search_ui/admin.py:309 weko_search_ui/admin.py:1250 msgid "Custom Sort" msgstr "カスタムソート" -#: weko_search_ui/admin.py:312 weko_search_ui/admin.py:1234 +#: weko_search_ui/admin.py:311 weko_search_ui/admin.py:1241 msgid "Bulk Delete" msgstr "一括削除" -#: weko_search_ui/admin.py:325 +#: weko_search_ui/admin.py:324 msgid "Bulk Update" msgstr "一括更新" -#: weko_search_ui/admin.py:440 weko_search_ui/admin.py:868 +#: weko_search_ui/admin.py:439 weko_search_ui/admin.py:872 #: weko_search_ui/templates/weko_search_ui/admin/export.html:53 #: weko_search_ui/templates/weko_search_ui/admin/import.html:96 #: weko_search_ui/templates/weko_search_ui/admin/rocrate_import.html:97 @@ -142,29 +207,29 @@ msgstr "一括更新" msgid "Internal server error" msgstr "サーバ内部エラー" -#: weko_search_ui/admin.py:1225 weko_search_ui/admin.py:1242 +#: weko_search_ui/admin.py:1232 weko_search_ui/admin.py:1249 #: weko_search_ui/templates/weko_search_ui/admin/import.html:53 #: weko_search_ui/templates/weko_search_ui/admin/rocrate_import.html:54 msgid "Index Tree" msgstr "インデックスツリー" -#: weko_search_ui/admin.py:1233 weko_search_ui/admin.py:1251 -#: weko_search_ui/admin.py:1260 weko_search_ui/admin.py:1270 +#: weko_search_ui/admin.py:1240 weko_search_ui/admin.py:1258 +#: weko_search_ui/admin.py:1267 weko_search_ui/admin.py:1277 msgid "Items" msgstr "アイテム" -#: weko_search_ui/admin.py:1252 +#: weko_search_ui/admin.py:1259 #: weko_search_ui/templates/weko_search_ui/admin/import.html:42 #: weko_search_ui/templates/weko_search_ui/admin/rocrate_import.html:43 msgid "Import" msgstr "インポート" -#: weko_search_ui/admin.py:1261 +#: weko_search_ui/admin.py:1268 #: weko_search_ui/templates/weko_search_ui/admin/rocrate_import.html:42 msgid "RO-Crate Import" msgstr "RO-Crate インポート" -#: weko_search_ui/admin.py:1271 +#: weko_search_ui/admin.py:1278 msgid "Bulk Export" msgstr "一括エクスポート" @@ -172,23 +237,53 @@ msgstr "一括エクスポート" msgid "contents" msgstr "" -#: weko_search_ui/mapper.py:1360 +#: weko_search_ui/mapper.py:1375 +#, python-brace-format msgid "\"{key}\" is required." msgstr "「{key}」 に対応するマッピングが必要です。" -#: weko_search_ui/mapper.py:1381 +#: weko_search_ui/mapper.py:1396 +#, python-brace-format msgid "\"{key}\" is not in itemtype." msgstr "「{key}」 はアイテムタイプに存在しません。" -#: weko_search_ui/mapper.py:1383 +#: weko_search_ui/mapper.py:1398 +#, python-brace-format msgid "\"{key}\" is not in itemtype, did you mean \"{similar_key}\"?" msgstr "「{key}」 はアイテムタイプに存在しません。「{similar_key}」であれば存在します。" -#: weko_search_ui/tasks.py:328 +#: weko_search_ui/mapper.py:1517 +#, python-format +msgid "" +"Cannot map to item type from json-ld; Mapping is not defined for the " +"metadata, \"%(key)s\": \"%(value)s\"" +msgstr "対応するマッピングが定義されてないため、このメタデータはアイテムタイプに変換できません。\"%(key)s\": \"%(value)s\"" + +#: weko_search_ui/mapper.py:1543 +#, python-format +msgid "" +"Cannot map to item type from json-ld; Not found mapping destination for " +"the metadata, \"%(key)s\": \"%(value)s\"" +msgstr "マッピング先が見つからないため、このメタデータはアイテムタイプに変換できません。\"%(key)s\": \"%(value)s\"" + +#: weko_search_ui/mapper.py:1553 +#, python-format +msgid "Failed to set metadata for json-ld, \"%(key)s\": \"%(value)s\"" +msgstr "アイテムタイプへのマッピングに失敗しました。\"%(key)s\": \"%(value)s\"" + +#: weko_search_ui/mapper.py:1572 +msgid "Metadata which could not be mapped to item type will be set in 'Extra'." +msgstr "アイテムタイプにマッピングできなかったメタデータは、「Extra」プロパティに登録されます。" + +#: weko_search_ui/mapper.py:1576 +msgid "Metadata which could not be mapped to item type will be discarded." +msgstr "アイテムタイプにマッピングできなかったメタデータは破棄されます。" + +#: weko_search_ui/tasks.py:337 msgid "`wk:metadata_replace` flag cannot be used in RO-Crate Import." msgstr "RO-Crate インポートでは、`wk:metadata_replace`フラグを有効にできません。" -#: weko_search_ui/tasks.py:343 +#: weko_search_ui/tasks.py:352 msgid "`wk:isSplited` flag cannot be used in RO-Crate Import." msgstr "RO-Crate インポートでは、`wk:isSplited`フラグを有効にできません。" @@ -196,25 +291,28 @@ msgstr "RO-Crate インポートでは、`wk:isSplited`フラグを有効にで msgid "The same item may have been registered." msgstr "同じアイテムが登録されている可能性があります。" -#: weko_search_ui/utils.py:592 weko_search_ui/utils.py:708 +#: weko_search_ui/utils.py:593 weko_search_ui/utils.py:709 +#, python-brace-format msgid "" "The format of the specified file {} does not support import. Please " "specify one of the following formats: zip, tar, gztar, bztar, xztar." msgstr "指定されたファイル{}の形式はインポートに対応していません。zip,tar,gztar,bztar,xztarいずれかの形式を指定してください。" -#: weko_search_ui/utils.py:600 +#: weko_search_ui/utils.py:601 +#, python-brace-format msgid "" "The csv/tsv file was not found in the specified file {}. Check if the " "directory structure is correct." msgstr "指定されたファイル{}にtsv/csvファイルが見つかりませんでした。ディレクトリ構成が正しいか確認してください。" -#: weko_search_ui/utils.py:649 +#: weko_search_ui/utils.py:650 msgid "" "The item type of the item to be imported is missing or has already been " "deleted." msgstr "" -#: weko_search_ui/utils.py:715 +#: weko_search_ui/utils.py:716 +#, python-brace-format msgid "" "The xml file was not found in the specified file {}. Check if the " "directory structure is correct." @@ -224,268 +322,305 @@ msgstr "" msgid "The item type ID specified in the XML file does not exist." msgstr "" -#: weko_search_ui/utils.py:1215 +#: weko_search_ui/utils.py:1206 +#, python-brace-format msgid "" "There is an error in the format of the first line of the header of the {}" " file." msgstr "{}ファイルのヘッダ1行目の形式に誤りがあります。" -#: weko_search_ui/utils.py:1234 +#: weko_search_ui/utils.py:1225 +#, python-brace-format msgid "The item type ID specified in the {} file does not exist." msgstr "{}ファイルで指定されたアイテムタイプIDは存在しません。" -#: weko_search_ui/utils.py:1243 +#: weko_search_ui/utils.py:1236 msgid "Cannot register because the specified item type is not the latest version." msgstr "指定されたアイテムタイプが最新のバージョンでないため登録できません。" -#: weko_search_ui/utils.py:1258 +#: weko_search_ui/utils.py:1250 +#, python-brace-format msgid "The following metadata keys are duplicated.
{}" msgstr "以下のメタデータキーが重複しています。
{}" -#: weko_search_ui/utils.py:1281 +#: weko_search_ui/utils.py:1274 +#, python-brace-format msgid "The item does not consistent with the specified item type.
{}" msgstr "指定されたアイテムタイプと項目が一致しません。
{}" -#: weko_search_ui/utils.py:1311 +#: weko_search_ui/utils.py:1303 +#, python-brace-format msgid "Cannot read {} file correctly." msgstr "{}ファイルが正しく読み込めません。" -#: weko_search_ui/utils.py:1331 +#: weko_search_ui/utils.py:1328 +#, python-brace-format msgid "" "The following items are not registered because they do not exist in the " "specified item type. {}" msgstr "次の項目指定されたアイテムタイプに存在しないため登録されません。{}" -#: weko_search_ui/utils.py:1341 +#: weko_search_ui/utils.py:1336 +#, python-brace-format msgid "" "The {} file could not be read. Make sure the file format is {} and that " "the file is UTF-8 encoded." msgstr "{}ファイルを読み込めませんでした。ファイル形式が{}であること、またそのファイルがUTF-8でエンコードされているかを確認してください。" -#: weko_search_ui/utils.py:1372 +#: weko_search_ui/utils.py:1369 msgid "" "The XML file could not be read. Make sure the file format is XML and that" " the file is UTF-8 encoded." msgstr "" -#: weko_search_ui/utils.py:1444 +#: weko_search_ui/utils.py:1441 msgid "Please specify item ID by half-width number." msgstr "アイテムIDは半角数字で指定してください。" -#: weko_search_ui/utils.py:1459 +#: weko_search_ui/utils.py:1462 +#, python-format +msgid "Replace value of %(target_path)s from %(old_value)s to '%(new_value)s'." +msgstr "%(target_path)sの値を%(old_value)sから'%(new_value)s'へ置き換えました。" + +#: weko_search_ui/utils.py:1475 msgid "Specified item type does not exist." msgstr "指定されたアイテムタイプが存在していません。" -#: weko_search_ui/utils.py:1533 +#: weko_search_ui/utils.py:1481 +#, python-brace-format, python-format +msgid "Specified %(type)s is different from existing %(existing_type)s." +msgstr "指定された%(type)sは登録している%(existing_type)sと異なっています。" + +#: weko_search_ui/utils.py:1556 msgid "Specified URI and system URI do not match." msgstr "指定されたURIとシステムURIが一致しません。" -#: weko_search_ui/utils.py:1545 +#: weko_search_ui/utils.py:1568 msgid "Item does not exist in the system." msgstr "アイテムがシステムに存在しません。" -#: weko_search_ui/utils.py:1552 +#: weko_search_ui/utils.py:1575 msgid "Item already DELETED in the system." msgstr "リンク先アイテムは削除済です。" -#: weko_search_ui/utils.py:1569 +#: weko_search_ui/utils.py:1592 msgid "Please specify either \"Keep\" or \"Upgrade\"." msgstr "Keep、Upgradeのいずれかを指定してください。" -#: weko_search_ui/utils.py:2472 +#: weko_search_ui/utils.py:2508 msgid "Title is required item." msgstr "タイトルは必須項目です。" -#: weko_search_ui/utils.py:2489 +#: weko_search_ui/utils.py:2525 +#, python-brace-format msgid "{} is required item." msgstr "{}は必須項目です。" -#: weko_search_ui/utils.py:2492 +#: weko_search_ui/utils.py:2528 +#, python-brace-format msgid "Please set \"public\" or \"private\" for {}." msgstr "{}はpublic,privateのいずれかを設定してください。" -#: weko_search_ui/utils.py:2533 +#: weko_search_ui/utils.py:2569 +#, python-brace-format msgid "The specified {} does not exist in system." msgstr "指定された{}はシステムに存在しません。" -#: weko_search_ui/utils.py:2543 +#: weko_search_ui/utils.py:2579 +#, python-brace-format msgid "Specified {} does not match with existing index." msgstr "指定された{}はシステムのものと一致していません。" -#: weko_search_ui/utils.py:2587 +#: weko_search_ui/utils.py:2623 msgid "Your role cannot register items in this index." msgstr "ロールの権限が足りずこのインデックスにアイテム登録ができません。" -#: weko_search_ui/utils.py:2608 +#: weko_search_ui/utils.py:2644 msgid "Both of IndexID and POS_INDEX are not being set." msgstr "IndexID, POS_INDEXがどちらも設定されていません。" -#: weko_search_ui/utils.py:2654 weko_search_ui/utils.py:2679 +#: weko_search_ui/utils.py:2690 weko_search_ui/utils.py:2715 +#, python-brace-format msgid "Specified {} is invalid." msgstr "指定された{}は不正です。" -#: weko_search_ui/utils.py:2721 weko_search_ui/utils.py:2754 -#: weko_search_ui/utils.py:2846 weko_search_ui/utils.py:2916 -#: weko_search_ui/utils.py:2920 weko_search_ui/utils.py:2942 -#: weko_search_ui/utils.py:2966 +#: weko_search_ui/utils.py:2757 weko_search_ui/utils.py:2790 +#: weko_search_ui/utils.py:2882 weko_search_ui/utils.py:2952 +#: weko_search_ui/utils.py:2956 weko_search_ui/utils.py:2978 +#: weko_search_ui/utils.py:3002 +#, python-brace-format msgid "Please specify {}." msgstr "{}を設定してください。" -#: weko_search_ui/utils.py:2724 weko_search_ui/utils.py:2923 +#: weko_search_ui/utils.py:2760 weko_search_ui/utils.py:2960 +#, python-brace-format msgid "The specified {} exceeds the maximum length." msgstr "指定された{}が最大長を超えています。" -#: weko_search_ui/utils.py:2739 weko_search_ui/utils.py:2897 -#: weko_search_ui/utils.py:2907 weko_search_ui/utils.py:2938 +#: weko_search_ui/utils.py:2775 weko_search_ui/utils.py:2933 +#: weko_search_ui/utils.py:2943 weko_search_ui/utils.py:2974 +#, python-brace-format msgid "Specified Prefix of {} is incorrect." msgstr "指定された{}のPrefixが誤っています。" -#: weko_search_ui/utils.py:2747 weko_search_ui/utils.py:2891 -#: weko_search_ui/utils.py:2900 +#: weko_search_ui/utils.py:2783 weko_search_ui/utils.py:2927 +#: weko_search_ui/utils.py:2936 +#, python-brace-format msgid "{} cannot be set." msgstr "{}は設定できません。" -#: weko_search_ui/utils.py:2756 weko_search_ui/utils.py:2760 -#: weko_search_ui/utils.py:2829 weko_search_ui/utils.py:2968 +#: weko_search_ui/utils.py:2793 weko_search_ui/utils.py:2797 +#: weko_search_ui/utils.py:2865 weko_search_ui/utils.py:3005 +#, python-brace-format msgid "Specified {} is different from existing {}." msgstr "指定された{}は登録している{}と異なっています。" -#: weko_search_ui/utils.py:2780 +#: weko_search_ui/utils.py:2817 msgid "" "When assigning a DOI to an item, it must be associated with an index " "whose index status is \"Public\" and Harvest Publishing is \"Public\"." msgstr "" -#: weko_search_ui/utils.py:2785 +#: weko_search_ui/utils.py:2822 msgid "" "Since the item has a DOI, it must be associated with an index whose index" " status is \"Public\" and whose Harvest Publishing is \"Public\"." msgstr "アイテムにDOIが付与されているため、インデックス状態が「公開」かつハーベスト公開が「公開」のインデックスに関連付けが必要です。" -#: weko_search_ui/utils.py:2796 +#: weko_search_ui/utils.py:2832 msgid "You cannot keep an item private because it has a DOI." msgstr "アイテムにDOIが付与されているため、アイテムを非公開にすることはできません。" -#: weko_search_ui/utils.py:2850 weko_search_ui/utils.py:4070 +#: weko_search_ui/utils.py:2887 weko_search_ui/utils.py:4106 msgid "DOI_RA should be set by one of JaLC, Crossref, DataCite, NDL JaLC." msgstr "DOI_RAはJaLC,Crossref,DataCite,NDL JaLCのいずれかを設定してください。" -#: weko_search_ui/utils.py:3001 +#: weko_search_ui/utils.py:3037 +#, python-brace-format msgid "Item Link type: '{}' is not one of {}." msgstr "リンクタイプ'{}'は次の決められた選択肢に含まれていません。{}" -#: weko_search_ui/utils.py:3005 +#: weko_search_ui/utils.py:3041 msgid "Please specify Item URL for item link." msgstr "アイテムリンク先のURLを指定してください。" -#: weko_search_ui/utils.py:3019 +#: weko_search_ui/utils.py:3055 msgid "Specified Item Link URI and system URI do not match." msgstr "指定されたアイテムリンク先URIとシステムURIが一致しません。" -#: weko_search_ui/utils.py:3025 +#: weko_search_ui/utils.py:3061 msgid "Linking item does not exist in the system." msgstr "リンク先アイテムがシステムに存在しません。" -#: weko_search_ui/utils.py:3028 +#: weko_search_ui/utils.py:3064 msgid "Linking item already deleted in the system." msgstr "リンク先アイテムは削除済です。" -#: weko_search_ui/utils.py:3067 +#: weko_search_ui/utils.py:3103 msgid "It is not allowed to create links to the item itself." msgstr "自身へのアイテムリンクを作成することはできません。" -#: weko_search_ui/utils.py:3072 +#: weko_search_ui/utils.py:3108 +#, python-brace-format msgid "It is not allowed to create links other than {} between split items." msgstr "分割したアイテム間で{}以外のリンクを作成することはできません。" -#: weko_search_ui/utils.py:3108 +#: weko_search_ui/utils.py:3144 msgid "Duplicate Item Link." msgstr "重複するアイテムリンクを作成することはできません。" -#: weko_search_ui/utils.py:3447 +#: weko_search_ui/utils.py:3483 +#, python-brace-format msgid "" "One of the following required values ​​has not been " "registered.
{}
" msgstr "次のいずれかの必要値が登録していません。
{}
" -#: weko_search_ui/utils.py:3452 +#: weko_search_ui/utils.py:3489 +#, python-brace-format msgid "" "The mapping of required items for DOI validation is not set. Please " "recheck the following mapping settings.
{}" msgstr "" -#: weko_search_ui/utils.py:3462 +#: weko_search_ui/utils.py:3498 +#, python-brace-format msgid "The following metadata are required.
{}" msgstr "" -#: weko_search_ui/utils.py:3467 +#: weko_search_ui/utils.py:3504 +#, python-brace-format msgid "One of the following metadata is required.
{}
" msgstr "{}は必須項目です。" -#: weko_search_ui/utils.py:3530 weko_search_ui/utils.py:3542 +#: weko_search_ui/utils.py:3567 weko_search_ui/utils.py:3579 msgid "Please specify the date with any format of YYYY-MM-DD, YYYY-MM, YYYY." msgstr "日付はYYYY-MM-DD、YYYY-MM、YYYYのいずれかで指定してください。" -#: weko_search_ui/utils.py:3536 +#: weko_search_ui/utils.py:3572 +#, python-brace-format msgid "Replace value of {} from {} to {}." msgstr "{}の値を{}から{}へ置き換えました。" -#: weko_search_ui/utils.py:3559 +#: weko_search_ui/utils.py:3595 msgid "Please specify PubDate with YYYY-MM-DD." msgstr "公開日はYYYY-MM-DDで指定してください。" -#: weko_search_ui/utils.py:3589 +#: weko_search_ui/utils.py:3625 msgid "ID is specified for the newly registered item. Ignore the ID and register." msgstr "新規登録アイテムにIDが指定されています。IDを無視して登録を行います。" -#: weko_search_ui/utils.py:3672 +#: weko_search_ui/utils.py:3708 msgid "Please specify Open Access Date with YYYY-MM-DD." msgstr "オープンアクセスの日付はYYYY-MM-DDで指定してください。" -#: weko_search_ui/utils.py:3909 weko_search_ui/utils.py:4055 -#: weko_search_ui/utils.py:4061 +#: weko_search_ui/utils.py:3945 weko_search_ui/utils.py:4091 +#: weko_search_ui/utils.py:4097 msgid "Please specify DOI prefix/suffix." msgstr "DOIは prefix/suffix の形式で指定して下さい。" -#: weko_search_ui/utils.py:4047 +#: weko_search_ui/utils.py:4083 msgid "The specified DOI is wrong and fixed with the registered DOI." msgstr "" -#: weko_search_ui/utils.py:4050 +#: weko_search_ui/utils.py:4086 msgid "" "The specified DOI RA is wrong and fixed with the correct DOI RA of the " "registered DOI." msgstr "" -#: weko_search_ui/utils.py:4057 weko_search_ui/utils.py:4063 +#: weko_search_ui/utils.py:4093 weko_search_ui/utils.py:4099 msgid "Please specify DOI suffix." msgstr "" -#: weko_search_ui/utils.py:4066 +#: weko_search_ui/utils.py:4102 msgid "Do not specify DOI suffix." msgstr "" -#: weko_search_ui/utils.py:4072 +#: weko_search_ui/utils.py:4108 msgid "Specified Prefix of DOI is incorrect." msgstr "" -#: weko_search_ui/utils.py:4104 +#: weko_search_ui/utils.py:4141 msgid "" "Please specify the image file(gif, jpg, jpe, jpeg, png, bmp, tiff, tif) " "for the thumbnail." msgstr "サムネイルは画像ファイル(gif, jpg, jpe, jpeg, png, bmp, tiff, tif)を指定してください。" -#: weko_search_ui/utils.py:5220 +#: weko_search_ui/utils.py:5256 +#, python-brace-format msgid "The file specified in ({}) does not exist." msgstr "({})に指定したファイルが存在しません。" -#: weko_search_ui/utils.py:5224 +#: weko_search_ui/utils.py:5261 +#, python-brace-format msgid "" "The file specified in ({}) does not exist.
The file will not be " "updated. Update only the metadata with csv/tsv contents." msgstr "({})に指定したファイルが存在しません。
ファイルの更新はしません。csv/tsv内容でメタデータのみ更新します。" -#: weko_search_ui/utils.py:5511 +#: weko_search_ui/utils.py:5547 +#, python-brace-format msgid "The file name specified in {} and {} do not match." msgstr "{}に指定されたファイル名と{}が一致しません。" diff --git a/modules/weko-search-ui/weko_search_ui/translations/messages.pot b/modules/weko-search-ui/weko_search_ui/translations/messages.pot index c0f4ceb528..dee931c764 100644 --- a/modules/weko-search-ui/weko_search_ui/translations/messages.pot +++ b/modules/weko-search-ui/weko_search_ui/translations/messages.pot @@ -9,100 +9,165 @@ msgid "" msgstr "" "Project-Id-Version: weko-search-ui 0.1.0.dev20170000\n" "Report-Msgid-Bugs-To: wekosoftware@nii.ac.jp\n" -"POT-Creation-Date: 2025-05-27 19:16+0900\n" +"POT-Creation-Date: 2025-11-12 12:12+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.5.1\n" +"Generated-By: Babel 2.17.0\n" -#: tests/conftest.py:399 +#: tests/conftest.py:409 msgid "write your own license" msgstr "" -#: tests/conftest.py:404 +#: tests/conftest.py:415 msgid "Creative Commons CC0 1.0 Universal Public Domain Designation" msgstr "" -#: tests/conftest.py:420 +#: tests/conftest.py:430 msgid "Creative Commons Attribution 3.0 Unported (CC BY 3.0)" msgstr "" -#: tests/conftest.py:432 +#: tests/conftest.py:443 msgid "Creative Commons Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)" msgstr "" -#: tests/conftest.py:447 +#: tests/conftest.py:458 msgid "Creative Commons Attribution-NoDerivs 3.0 Unported (CC BY-ND 3.0)" msgstr "" -#: tests/conftest.py:461 +#: tests/conftest.py:472 msgid "Creative Commons Attribution-NonCommercial 3.0 Unported (CC BY-NC 3.0)" msgstr "" -#: tests/conftest.py:476 +#: tests/conftest.py:487 msgid "" "Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported (CC " "BY-NC-SA 3.0)" msgstr "" -#: tests/conftest.py:491 +#: tests/conftest.py:502 msgid "" "Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported (CC BY-" "NC-ND 3.0)" msgstr "" -#: tests/conftest.py:507 +#: tests/conftest.py:517 msgid "Creative Commons Attribution 4.0 International (CC BY 4.0)" msgstr "" -#: tests/conftest.py:519 +#: tests/conftest.py:530 msgid "Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)" msgstr "" -#: tests/conftest.py:534 +#: tests/conftest.py:545 msgid "" "Creative Commons Attribution-NoDerivatives 4.0 International (CC BY-ND " "4.0)" msgstr "" -#: tests/conftest.py:549 +#: tests/conftest.py:560 msgid "" "Creative Commons Attribution-NonCommercial 4.0 International (CC BY-NC " "4.0)" msgstr "" -#: tests/conftest.py:564 +#: tests/conftest.py:575 msgid "" "Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International " "(CC BY-NC-SA 4.0)" msgstr "" -#: tests/conftest.py:579 +#: tests/conftest.py:590 msgid "" "Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 " "International (CC BY-NC-ND 4.0)" msgstr "" -#: weko_search_ui/admin.py:158 +#: tests/helpers.py:84 +msgid "Bagging a parent of the current directory is not supported" +msgstr "" + +#: tests/helpers.py:87 +#, python-format +msgid "Creating tag for directory %s" +msgstr "" + +#: tests/helpers.py:90 tests/helpers.py:91 +#, python-format +msgid "Bag directory %s does not exist" +msgstr "" + +#: tests/helpers.py:96 tests/helpers.py:97 +#, python-format +msgid "Bag directory %s does not contain a data directory" +msgstr "" + +#: tests/helpers.py:104 +#, python-format +msgid "" +"Unable to write to the following directories and files:\n" +"%s" +msgstr "" + +#: tests/helpers.py:107 +msgid "Missing permissions to move all files and directories" +msgstr "" + +#: tests/helpers.py:114 +#, python-format +msgid "" +"The following directories do not have read permissions:\n" +"%s" +msgstr "" + +#: tests/helpers.py:119 +#, python-format +msgid "" +"The following files do not have read permissions:\n" +"%s" +msgstr "" + +#: tests/helpers.py:123 +msgid "Read permissions are required to calculate file fixities" +msgstr "" + +#: tests/helpers.py:126 +msgid "Creating data directory" +msgstr "" + +#: tests/helpers.py:143 +msgid "Creating bagit.txt" +msgstr "" + +#: tests/helpers.py:148 +msgid "Creating bag-info.txt" +msgstr "" + +#: tests/helpers.py:167 +#, python-format +msgid "An error occurred creating a bag in %s" +msgstr "" + +#: weko_search_ui/admin.py:157 msgid "The following item(s) cannot be deleted." msgstr "" -#: weko_search_ui/admin.py:163 +#: weko_search_ui/admin.py:162 msgid "DOI granting item(s):" msgstr "" -#: weko_search_ui/admin.py:168 +#: weko_search_ui/admin.py:167 msgid "Editing item(s):" msgstr "" -#: weko_search_ui/admin.py:171 +#: weko_search_ui/admin.py:170 msgid "Success" msgstr "" -#: weko_search_ui/admin.py:204 +#: weko_search_ui/admin.py:203 msgid "Index Delete is in progress on another device." msgstr "" @@ -113,27 +178,27 @@ msgid "" "you want to continue deleting items that are not grant DOI?" msgstr "" -#: weko_search_ui/admin.py:216 +#: weko_search_ui/admin.py:215 msgid "Are you sure you want to delete it?" msgstr "" -#: weko_search_ui/admin.py:219 +#: weko_search_ui/admin.py:218 msgid "No such index." msgstr "" -#: weko_search_ui/admin.py:310 weko_search_ui/admin.py:1243 +#: weko_search_ui/admin.py:309 weko_search_ui/admin.py:1250 msgid "Custom Sort" msgstr "" -#: weko_search_ui/admin.py:312 weko_search_ui/admin.py:1234 +#: weko_search_ui/admin.py:311 weko_search_ui/admin.py:1241 msgid "Bulk Delete" msgstr "" -#: weko_search_ui/admin.py:325 +#: weko_search_ui/admin.py:324 msgid "Bulk Update" msgstr "" -#: weko_search_ui/admin.py:440 weko_search_ui/admin.py:868 +#: weko_search_ui/admin.py:439 weko_search_ui/admin.py:872 #: weko_search_ui/templates/weko_search_ui/admin/export.html:53 #: weko_search_ui/templates/weko_search_ui/admin/import.html:96 #: weko_search_ui/templates/weko_search_ui/admin/rocrate_import.html:97 @@ -141,29 +206,29 @@ msgstr "" msgid "Internal server error" msgstr "" -#: weko_search_ui/admin.py:1225 weko_search_ui/admin.py:1242 +#: weko_search_ui/admin.py:1232 weko_search_ui/admin.py:1249 #: weko_search_ui/templates/weko_search_ui/admin/import.html:53 #: weko_search_ui/templates/weko_search_ui/admin/rocrate_import.html:54 msgid "Index Tree" msgstr "" -#: weko_search_ui/admin.py:1233 weko_search_ui/admin.py:1251 -#: weko_search_ui/admin.py:1260 weko_search_ui/admin.py:1270 +#: weko_search_ui/admin.py:1240 weko_search_ui/admin.py:1258 +#: weko_search_ui/admin.py:1267 weko_search_ui/admin.py:1277 msgid "Items" msgstr "" -#: weko_search_ui/admin.py:1252 +#: weko_search_ui/admin.py:1259 #: weko_search_ui/templates/weko_search_ui/admin/import.html:42 #: weko_search_ui/templates/weko_search_ui/admin/rocrate_import.html:43 msgid "Import" msgstr "" -#: weko_search_ui/admin.py:1261 +#: weko_search_ui/admin.py:1268 #: weko_search_ui/templates/weko_search_ui/admin/rocrate_import.html:42 msgid "RO-Crate Import" msgstr "" -#: weko_search_ui/admin.py:1271 +#: weko_search_ui/admin.py:1278 msgid "Bulk Export" msgstr "" @@ -171,23 +236,53 @@ msgstr "" msgid "contents" msgstr "" -#: weko_search_ui/mapper.py:1360 +#: weko_search_ui/mapper.py:1375 +#, python-brace-format msgid "\"{key}\" is required." msgstr "" -#: weko_search_ui/mapper.py:1381 +#: weko_search_ui/mapper.py:1396 +#, python-brace-format msgid "\"{key}\" is not in itemtype." msgstr "" -#: weko_search_ui/mapper.py:1383 +#: weko_search_ui/mapper.py:1398 +#, python-brace-format msgid "\"{key}\" is not in itemtype, did you mean \"{similar_key}\"?" msgstr "" -#: weko_search_ui/tasks.py:328 +#: weko_search_ui/mapper.py:1517 +#, python-format +msgid "" +"Cannot map to item type from json-ld; Mapping is not defined for the " +"metadata, \"%(key)s\": \"%(value)s\"" +msgstr "" + +#: weko_search_ui/mapper.py:1543 +#, python-format +msgid "" +"Cannot map to item type from json-ld; Not found mapping destination for " +"the metadata, \"%(key)s\": \"%(value)s\"" +msgstr "" + +#: weko_search_ui/mapper.py:1553 +#, python-format +msgid "Failed to set metadata for json-ld, \"%(key)s\": \"%(value)s\"" +msgstr "" + +#: weko_search_ui/mapper.py:1572 +msgid "Metadata which could not be mapped to item type will be set in 'Extra'." +msgstr "" + +#: weko_search_ui/mapper.py:1576 +msgid "Metadata which could not be mapped to item type will be discarded." +msgstr "" + +#: weko_search_ui/tasks.py:337 msgid "`wk:metadata_replace` flag cannot be used in RO-Crate Import." msgstr "" -#: weko_search_ui/tasks.py:343 +#: weko_search_ui/tasks.py:352 msgid "`wk:isSplited` flag cannot be used in RO-Crate Import." msgstr "" @@ -195,25 +290,28 @@ msgstr "" msgid "The same item may have been registered." msgstr "" -#: weko_search_ui/utils.py:592 weko_search_ui/utils.py:708 +#: weko_search_ui/utils.py:593 weko_search_ui/utils.py:709 +#, python-brace-format msgid "" "The format of the specified file {} does not support import. Please " "specify one of the following formats: zip, tar, gztar, bztar, xztar." msgstr "" -#: weko_search_ui/utils.py:600 +#: weko_search_ui/utils.py:601 +#, python-brace-format msgid "" "The csv/tsv file was not found in the specified file {}. Check if the " "directory structure is correct." msgstr "" -#: weko_search_ui/utils.py:649 +#: weko_search_ui/utils.py:650 msgid "" "The item type of the item to be imported is missing or has already been " "deleted." msgstr "" -#: weko_search_ui/utils.py:715 +#: weko_search_ui/utils.py:716 +#, python-brace-format msgid "" "The xml file was not found in the specified file {}. Check if the " "directory structure is correct." @@ -223,268 +321,305 @@ msgstr "" msgid "The item type ID specified in the XML file does not exist." msgstr "" -#: weko_search_ui/utils.py:1215 +#: weko_search_ui/utils.py:1206 +#, python-brace-format msgid "" "There is an error in the format of the first line of the header of the {}" " file." msgstr "" -#: weko_search_ui/utils.py:1234 +#: weko_search_ui/utils.py:1225 +#, python-brace-format msgid "The item type ID specified in the {} file does not exist." msgstr "" -#: weko_search_ui/utils.py:1243 +#: weko_search_ui/utils.py:1236 msgid "Cannot register because the specified item type is not the latest version." msgstr "" -#: weko_search_ui/utils.py:1258 +#: weko_search_ui/utils.py:1250 +#, python-brace-format msgid "The following metadata keys are duplicated.
{}" msgstr "" -#: weko_search_ui/utils.py:1281 +#: weko_search_ui/utils.py:1274 +#, python-brace-format msgid "The item does not consistent with the specified item type.
{}" msgstr "" -#: weko_search_ui/utils.py:1311 +#: weko_search_ui/utils.py:1303 +#, python-brace-format msgid "Cannot read {} file correctly." msgstr "" -#: weko_search_ui/utils.py:1331 +#: weko_search_ui/utils.py:1328 +#, python-brace-format msgid "" "The following items are not registered because they do not exist in the " "specified item type. {}" msgstr "" -#: weko_search_ui/utils.py:1341 +#: weko_search_ui/utils.py:1336 +#, python-brace-format msgid "" "The {} file could not be read. Make sure the file format is {} and that " "the file is UTF-8 encoded." msgstr "" -#: weko_search_ui/utils.py:1372 +#: weko_search_ui/utils.py:1369 msgid "" "The XML file could not be read. Make sure the file format is XML and that" " the file is UTF-8 encoded." msgstr "" -#: weko_search_ui/utils.py:1444 +#: weko_search_ui/utils.py:1441 msgid "Please specify item ID by half-width number." msgstr "" -#: weko_search_ui/utils.py:1459 +#: weko_search_ui/utils.py:1462 +#, python-format +msgid "Replace value of %(target_path)s from %(old_value)s to '%(new_value)s'." +msgstr "" + +#: weko_search_ui/utils.py:1475 msgid "Specified item type does not exist." msgstr "" -#: weko_search_ui/utils.py:1533 +#: weko_search_ui/utils.py:1481 +#, python-format +msgid "Specified %(type)s is different from existing %(existing_type)s." +msgstr "" + +#: weko_search_ui/utils.py:1556 msgid "Specified URI and system URI do not match." msgstr "" -#: weko_search_ui/utils.py:1545 +#: weko_search_ui/utils.py:1568 msgid "Item does not exist in the system." msgstr "" -#: weko_search_ui/utils.py:1552 +#: weko_search_ui/utils.py:1575 msgid "Item already DELETED in the system." msgstr "" -#: weko_search_ui/utils.py:1569 +#: weko_search_ui/utils.py:1592 msgid "Please specify either \"Keep\" or \"Upgrade\"." msgstr "" -#: weko_search_ui/utils.py:2472 +#: weko_search_ui/utils.py:2508 msgid "Title is required item." msgstr "" -#: weko_search_ui/utils.py:2489 +#: weko_search_ui/utils.py:2525 +#, python-brace-format msgid "{} is required item." msgstr "" -#: weko_search_ui/utils.py:2492 +#: weko_search_ui/utils.py:2528 +#, python-brace-format msgid "Please set \"public\" or \"private\" for {}." msgstr "" -#: weko_search_ui/utils.py:2533 +#: weko_search_ui/utils.py:2569 +#, python-brace-format msgid "The specified {} does not exist in system." msgstr "" -#: weko_search_ui/utils.py:2543 +#: weko_search_ui/utils.py:2579 +#, python-brace-format msgid "Specified {} does not match with existing index." msgstr "" -#: weko_search_ui/utils.py:2587 +#: weko_search_ui/utils.py:2623 msgid "Your role cannot register items in this index." msgstr "" -#: weko_search_ui/utils.py:2608 +#: weko_search_ui/utils.py:2644 msgid "Both of IndexID and POS_INDEX are not being set." msgstr "" -#: weko_search_ui/utils.py:2654 weko_search_ui/utils.py:2679 +#: weko_search_ui/utils.py:2690 weko_search_ui/utils.py:2715 +#, python-brace-format msgid "Specified {} is invalid." msgstr "" -#: weko_search_ui/utils.py:2721 weko_search_ui/utils.py:2754 -#: weko_search_ui/utils.py:2846 weko_search_ui/utils.py:2916 -#: weko_search_ui/utils.py:2920 weko_search_ui/utils.py:2942 -#: weko_search_ui/utils.py:2966 +#: weko_search_ui/utils.py:2757 weko_search_ui/utils.py:2790 +#: weko_search_ui/utils.py:2882 weko_search_ui/utils.py:2952 +#: weko_search_ui/utils.py:2956 weko_search_ui/utils.py:2978 +#: weko_search_ui/utils.py:3002 +#, python-brace-format msgid "Please specify {}." msgstr "" -#: weko_search_ui/utils.py:2724 weko_search_ui/utils.py:2923 +#: weko_search_ui/utils.py:2760 weko_search_ui/utils.py:2960 +#, python-brace-format msgid "The specified {} exceeds the maximum length." msgstr "" -#: weko_search_ui/utils.py:2739 weko_search_ui/utils.py:2897 -#: weko_search_ui/utils.py:2907 weko_search_ui/utils.py:2938 +#: weko_search_ui/utils.py:2775 weko_search_ui/utils.py:2933 +#: weko_search_ui/utils.py:2943 weko_search_ui/utils.py:2974 +#, python-brace-format msgid "Specified Prefix of {} is incorrect." msgstr "" -#: weko_search_ui/utils.py:2747 weko_search_ui/utils.py:2891 -#: weko_search_ui/utils.py:2900 +#: weko_search_ui/utils.py:2783 weko_search_ui/utils.py:2927 +#: weko_search_ui/utils.py:2936 +#, python-brace-format msgid "{} cannot be set." msgstr "" -#: weko_search_ui/utils.py:2756 weko_search_ui/utils.py:2760 -#: weko_search_ui/utils.py:2829 weko_search_ui/utils.py:2968 +#: weko_search_ui/utils.py:2793 weko_search_ui/utils.py:2797 +#: weko_search_ui/utils.py:2865 weko_search_ui/utils.py:3005 +#, python-brace-format msgid "Specified {} is different from existing {}." msgstr "" -#: weko_search_ui/utils.py:2780 +#: weko_search_ui/utils.py:2817 msgid "" "When assigning a DOI to an item, it must be associated with an index " "whose index status is \"Public\" and Harvest Publishing is \"Public\"." msgstr "" -#: weko_search_ui/utils.py:2785 +#: weko_search_ui/utils.py:2822 msgid "" "Since the item has a DOI, it must be associated with an index whose index" " status is \"Public\" and whose Harvest Publishing is \"Public\"." msgstr "" -#: weko_search_ui/utils.py:2796 +#: weko_search_ui/utils.py:2832 msgid "You cannot keep an item private because it has a DOI." msgstr "" -#: weko_search_ui/utils.py:2850 weko_search_ui/utils.py:4070 +#: weko_search_ui/utils.py:2887 weko_search_ui/utils.py:4106 msgid "DOI_RA should be set by one of JaLC, Crossref, DataCite, NDL JaLC." msgstr "" -#: weko_search_ui/utils.py:3001 +#: weko_search_ui/utils.py:3037 +#, python-brace-format msgid "Item Link type: '{}' is not one of {}." msgstr "" -#: weko_search_ui/utils.py:3005 +#: weko_search_ui/utils.py:3041 msgid "Please specify Item URL for item link." msgstr "" -#: weko_search_ui/utils.py:3019 +#: weko_search_ui/utils.py:3055 msgid "Specified Item Link URI and system URI do not match." msgstr "" -#: weko_search_ui/utils.py:3025 +#: weko_search_ui/utils.py:3061 msgid "Linking item does not exist in the system." msgstr "" -#: weko_search_ui/utils.py:3028 +#: weko_search_ui/utils.py:3064 msgid "Linking item already deleted in the system." msgstr "" -#: weko_search_ui/utils.py:3067 +#: weko_search_ui/utils.py:3103 msgid "It is not allowed to create links to the item itself." msgstr "" -#: weko_search_ui/utils.py:3072 +#: weko_search_ui/utils.py:3108 +#, python-brace-format msgid "It is not allowed to create links other than {} between split items." msgstr "" -#: weko_search_ui/utils.py:3108 +#: weko_search_ui/utils.py:3144 msgid "Duplicate Item Link." msgstr "" -#: weko_search_ui/utils.py:3447 +#: weko_search_ui/utils.py:3483 +#, python-brace-format msgid "" "One of the following required values ​​has not been " "registered.
{}
" msgstr "" -#: weko_search_ui/utils.py:3452 +#: weko_search_ui/utils.py:3489 +#, python-brace-format msgid "" "The mapping of required items for DOI validation is not set. Please " "recheck the following mapping settings.
{}" msgstr "" -#: weko_search_ui/utils.py:3462 +#: weko_search_ui/utils.py:3498 +#, python-brace-format msgid "The following metadata are required.
{}" msgstr "" -#: weko_search_ui/utils.py:3467 +#: weko_search_ui/utils.py:3504 +#, python-brace-format msgid "One of the following metadata is required.
{}
" msgstr "" -#: weko_search_ui/utils.py:3530 weko_search_ui/utils.py:3542 +#: weko_search_ui/utils.py:3567 weko_search_ui/utils.py:3579 msgid "Please specify the date with any format of YYYY-MM-DD, YYYY-MM, YYYY." msgstr "" -#: weko_search_ui/utils.py:3536 +#: weko_search_ui/utils.py:3572 +#, python-brace-format msgid "Replace value of {} from {} to {}." msgstr "" -#: weko_search_ui/utils.py:3559 +#: weko_search_ui/utils.py:3595 msgid "Please specify PubDate with YYYY-MM-DD." msgstr "" -#: weko_search_ui/utils.py:3589 +#: weko_search_ui/utils.py:3625 msgid "ID is specified for the newly registered item. Ignore the ID and register." msgstr "" -#: weko_search_ui/utils.py:3672 +#: weko_search_ui/utils.py:3708 msgid "Please specify Open Access Date with YYYY-MM-DD." msgstr "" -#: weko_search_ui/utils.py:3909 weko_search_ui/utils.py:4055 -#: weko_search_ui/utils.py:4061 +#: weko_search_ui/utils.py:3945 weko_search_ui/utils.py:4091 +#: weko_search_ui/utils.py:4097 msgid "Please specify DOI prefix/suffix." msgstr "" -#: weko_search_ui/utils.py:4047 +#: weko_search_ui/utils.py:4083 msgid "The specified DOI is wrong and fixed with the registered DOI." msgstr "" -#: weko_search_ui/utils.py:4050 +#: weko_search_ui/utils.py:4086 msgid "" "The specified DOI RA is wrong and fixed with the correct DOI RA of the " "registered DOI." msgstr "" -#: weko_search_ui/utils.py:4057 weko_search_ui/utils.py:4063 +#: weko_search_ui/utils.py:4093 weko_search_ui/utils.py:4099 msgid "Please specify DOI suffix." msgstr "" -#: weko_search_ui/utils.py:4066 +#: weko_search_ui/utils.py:4102 msgid "Do not specify DOI suffix." msgstr "" -#: weko_search_ui/utils.py:4072 +#: weko_search_ui/utils.py:4108 msgid "Specified Prefix of DOI is incorrect." msgstr "" -#: weko_search_ui/utils.py:4104 +#: weko_search_ui/utils.py:4141 msgid "" "Please specify the image file(gif, jpg, jpe, jpeg, png, bmp, tiff, tif) " "for the thumbnail." msgstr "" -#: weko_search_ui/utils.py:5220 +#: weko_search_ui/utils.py:5256 +#, python-brace-format msgid "The file specified in ({}) does not exist." msgstr "" -#: weko_search_ui/utils.py:5224 +#: weko_search_ui/utils.py:5261 +#, python-brace-format msgid "" "The file specified in ({}) does not exist.
The file will not be " "updated. Update only the metadata with csv/tsv contents." msgstr "" -#: weko_search_ui/utils.py:5511 +#: weko_search_ui/utils.py:5547 +#, python-brace-format msgid "The file name specified in {} and {} do not match." msgstr "" diff --git a/modules/weko-search-ui/weko_search_ui/utils.py b/modules/weko-search-ui/weko_search_ui/utils.py index dde229f916..2d22fb60a4 100644 --- a/modules/weko-search-ui/weko_search_ui/utils.py +++ b/modules/weko-search-ui/weko_search_ui/utils.py @@ -921,9 +921,14 @@ def check_jsonld_import_items( .format(item_type.item_type_name.name) ) + _json_ld = {} with open(f"{data_path}/{json_name}", "r") as f: - json_ld = json.load(f) + _json_ld = json.load(f) + mapper.data_path = data_path + # Temporary fix for pipe character + json_ld = json.loads((json.dumps(_json_ld, ensure_ascii=False)).replace("|", "|")) item_metadatas, _ = mapper.to_item_metadata(json_ld) + list_record = [ { "$schema": f"/items/jsonschema/{item_type.id}", @@ -1304,14 +1309,18 @@ def read_stats_file(file_path: str, file_name: str, file_format: str) -> dict: if isinstance(check_item_type, dict): item_type_name = check_item_type.get("name") item_type_id = check_item_type.get("item_type_id") - item_data = dict( + item_data = { **data_parse_metadata, **{ "item_type_name": item_type_name or "", "item_type_id": item_type_id or "", "$schema": schema if schema else "", + "metadata": { + **data_parse_metadata.get("metadata", {}), + "edit_mode": data_parse_metadata.get("edit_mode", "Keep"), + } } - ) + } else: item_data = dict(**data_parse_metadata) if item_path_not_existed: @@ -1428,6 +1437,7 @@ def handle_validate_item_import(list_record, schema) -> list: v2 = Draft4Validator(schema) if schema else None for record in list_record: errors = record.get("errors") or [] + warnings = [] record_id = record.get("id") if record_id and ( not represents_int(record_id) or re.search(r"([0-9])", record_id) @@ -1439,19 +1449,45 @@ def handle_validate_item_import(list_record, schema) -> list: if record.get("metadata"): if v2: a = v2.iter_errors(record.get("metadata")) + for error in a: + if ( + error.validator == "type" + and error.validator_value == "string" + and isinstance(error.instance, int) + ): + target = record["metadata"] + path_list = list(error.path) + for key in path_list[:-1]: + target = target[key] + last_key = path_list[-1] + target[last_key] = str(target[last_key]) + target_path = ".".join([str(p) for p in path_list[:-2]]) + warnings.append( + _("Replace value of %(target_path)s from %(old_value)s to '%(new_value)s'.", + target_path=target_path, old_value=target[last_key], new_value=str(target[last_key]) + ) + ) + b = v2.iter_errors(record.get("metadata")) if current_i18n.language == "ja": _errors = [] - for error in a: + for error in b: _errors.append(handle_convert_validate_msg_to_jp(error.message)) errors = errors + _errors else: - errors = errors + [error.message for error in a] + errors = errors + [error.message for error in b] else: errors = errors = errors + [_("Specified item type does not exist.")] - item_error = dict(**record) - item_error["errors"] = errors if len(errors) else None - result.append(item_error) + records = dict(**record) + records["errors"] = errors if len(errors) else None + if len(warnings) > 0: + warnings.append( + _("Specified %(type)s is different from existing %(existing_type)s.", + type="type:integer", existing_type="type:string" + ) + ) + records["warnings"] = warnings if len(warnings) else None + result.append(records) return result diff --git a/modules/weko-swordserver/tests/test_utils.py b/modules/weko-swordserver/tests/test_utils.py index 4dec54544c..6bd8eb38c9 100644 --- a/modules/weko-swordserver/tests/test_utils.py +++ b/modules/weko-swordserver/tests/test_utils.py @@ -225,9 +225,10 @@ def test_check_import_items(app, admin_settings, item_type, workflow, sword_clie mocker_jsonld_check.return_value = { "list_record": [{"item_type_id": item_type_id, "item_type_name": item_type_name, "metadata": {"title": "test"}}] } + app.config["WEKO_SWORDSERVER_BAGIT_VERIFICATION"] = False check_result = check_import_items(file_content, "JSON", False, -1, packaging="SimpleZip", client_id=client_id) - mocker_jsonld_check.assert_called_once_with(file_content, "SimpleZip", mapping_id, [], -1, is_change_identifier=False) + mocker_jsonld_check.assert_called_once_with(file_content, "SimpleZip", mapping_id, [], -1, validate_bagit=False, is_change_identifier=False) assert check_result["list_record"][0]["item_type_id"] == item_type_id assert check_result["list_record"][0]["item_type_name"] == item_type_name assert check_result["list_record"][0]["metadata"]["title"] == "test" @@ -242,8 +243,9 @@ def test_check_import_items(app, admin_settings, item_type, workflow, sword_clie mocker_jsonld_check.return_value = { "list_record": [{"item_type_id": item_type_id, "item_type_name": item_type_name, "metadata": {"title": "test"}}] } + app.config["WEKO_SWORDSERVER_BAGIT_VERIFICATION"] = True check_result = check_import_items(file_content, "JSON", True, 3, packaging="SimpleZip", client_id=client_id) - mocker_jsonld_check.assert_called_once_with(file_content, "SimpleZip", mapping_id, [], 3, is_change_identifier=True) + mocker_jsonld_check.assert_called_once_with(file_content, "SimpleZip", mapping_id, [], 3, validate_bagit=True, is_change_identifier=True) assert check_result["list_record"][0]["item_type_id"] == item_type_id assert check_result["list_record"][0]["item_type_name"] == item_type_name assert check_result["list_record"][0]["metadata"]["title"] == "test" diff --git a/modules/weko-swordserver/weko_swordserver/config.py b/modules/weko-swordserver/weko_swordserver/config.py index 2cc4f40bdd..66dad93c22 100644 --- a/modules/weko-swordserver/weko_swordserver/config.py +++ b/modules/weko-swordserver/weko_swordserver/config.py @@ -108,6 +108,9 @@ WEKO_SWORDSERVER_DIGEST_VERIFICATION = True """ Does the server require the client to send a digest? """ +WEKO_SWORDSERVER_BAGIT_VERIFICATION = True +""" Does the server require the client to send a BagIt package? """ + WEKO_SWORDSERVER_DEPOSIT_ROLE_ENABLE = [ "System Administrator", "Repository Administrator" diff --git a/modules/weko-swordserver/weko_swordserver/utils.py b/modules/weko-swordserver/weko_swordserver/utils.py index b815c8b24c..f460dd2be1 100644 --- a/modules/weko-swordserver/weko_swordserver/utils.py +++ b/modules/weko-swordserver/weko_swordserver/utils.py @@ -355,9 +355,11 @@ def check_import_items( errorType=ErrorType.BadRequest ) + validate_bagit = current_app.config["WEKO_SWORDSERVER_BAGIT_VERIFICATION"] check_result.update( check_jsonld_import_items( file, packaging, mapping_id, meta_data_api, shared_id, + validate_bagit=validate_bagit, is_change_identifier=is_change_identifier ) ) diff --git a/modules/weko-swordserver/weko_swordserver/views.py b/modules/weko-swordserver/weko_swordserver/views.py index 557d8e8c60..993511a4fa 100644 --- a/modules/weko-swordserver/weko_swordserver/views.py +++ b/modules/weko-swordserver/weko_swordserver/views.py @@ -171,7 +171,7 @@ def get_service_document(): @oauth2.require_oauth() @limiter.limit("") @require_oauth_scopes(write_scope.id, actions_scope.id) -@require_oauth_scopes(item_create_scope.id) +@require_oauth_scopes(item_update_scope.id) @roles_required(WEKO_SWORDSERVER_DEPOSIT_ROLE_ENABLE) @check_on_behalf_of() @check_package_contents() diff --git a/modules/weko-theme/weko_theme/templates/weko_theme/header_login.html b/modules/weko-theme/weko_theme/templates/weko_theme/header_login.html index e501bf437b..a23dd35850 100644 --- a/modules/weko-theme/weko_theme/templates/weko_theme/header_login.html +++ b/modules/weko-theme/weko_theme/templates/weko_theme/header_login.html @@ -40,8 +40,10 @@ {%- if config.USERPROFILES %}
+ {% if current_user and current_user.is_authenticated %} {{current_user.email}} + {% endif %}
@@ -70,6 +72,12 @@ diff --git a/nginx/ams/weko-frontend/components/common/SearchForm.vue b/nginx/ams/weko-frontend/components/common/SearchForm.vue index 166da2a572..2525739efb 100644 --- a/nginx/ams/weko-frontend/components/common/SearchForm.vue +++ b/nginx/ams/weko-frontend/components/common/SearchForm.vue @@ -200,7 +200,7 @@
@@ -325,6 +325,7 @@ const titleSearchType = reactive({ name: 'exactMatch' } }); +const appConf = useAppConfig(); /* /////////////////////////////////// // function @@ -414,7 +415,7 @@ function search() { emits('clickSearch'); } else { sessionStorage.setItem('conditions', JSON.stringify(conditions)); - navigateTo('/search'); + navigateTo(`${appConf.amsPath ?? ''}/search`); } } @@ -592,6 +593,7 @@ try { $fetch(useAppConfig().wekoApi + '/records', { timeout: useRuntimeConfig().public.apiTimeout, method: 'GET', + credentials: 'omit', headers: { 'Cache-Control': 'no-store', Pragma: 'no-cache', @@ -610,6 +612,7 @@ try { $fetch(useAppConfig().wekoApi + '/records', { timeout: useRuntimeConfig().public.apiTimeout, method: 'GET', + credentials: 'omit', headers: { 'Cache-Control': 'no-store', Pragma: 'no-cache', @@ -643,6 +646,7 @@ try { await $fetch(useAppConfig().wekoApi + '/tree/index', { timeout: useRuntimeConfig().public.apiTimeout, method: 'GET', + credentials: 'omit', headers: { 'Cache-Control': 'no-store', Pragma: 'no-cache', diff --git a/nginx/ams/weko-frontend/components/common/modal/CreaterInfo.vue b/nginx/ams/weko-frontend/components/common/modal/CreaterInfo.vue index 90488fe568..f623380bf7 100644 --- a/nginx/ams/weko-frontend/components/common/modal/CreaterInfo.vue +++ b/nginx/ams/weko-frontend/components/common/modal/CreaterInfo.vue @@ -7,7 +7,7 @@
@@ -55,6 +55,7 @@ defineExpose({ /////////////////////////////////// */ const modalShowFlag = ref(false); +const appConf = useAppConfig(); /* /////////////////////////////////// // function diff --git a/nginx/ams/weko-frontend/components/common/modal/DetailSearch.vue b/nginx/ams/weko-frontend/components/common/modal/DetailSearch.vue index 7f805c4ed4..29756cd2e6 100644 --- a/nginx/ams/weko-frontend/components/common/modal/DetailSearch.vue +++ b/nginx/ams/weko-frontend/components/common/modal/DetailSearch.vue @@ -10,7 +10,7 @@
@@ -196,6 +196,7 @@ const conditions = reactive({ type: '0', keyword: '', detail: {}, detailData: {} const columnList = ref([]); const selected = ref(selectorDefault); let selector = JSON.parse(JSON.stringify(DetailColumn)); +const appConf = useAppConfig(); /* /////////////////////////////////// // function @@ -280,7 +281,7 @@ function search() { closeModal(); } else { sessionStorage.setItem('conditions', JSON.stringify(conditions)); - navigateTo('/search'); + navigateTo(`${appConf.amsPath ?? ''}/search`); closeModal(); } } @@ -358,6 +359,7 @@ try { await $fetch(useAppConfig().wekoApi + '/tree/index', { timeout: useRuntimeConfig().public.apiTimeout, method: 'GET', + credentials: 'omit', headers: { 'Cache-Control': 'no-store', Pragma: 'no-cache', diff --git a/nginx/ams/weko-frontend/components/contact/modal/ContactConfirm.vue b/nginx/ams/weko-frontend/components/contact/modal/ContactConfirm.vue index e41ee1c1fb..96b706df0d 100644 --- a/nginx/ams/weko-frontend/components/contact/modal/ContactConfirm.vue +++ b/nginx/ams/weko-frontend/components/contact/modal/ContactConfirm.vue @@ -10,7 +10,7 @@
@@ -192,7 +192,7 @@ function getCaptcha() { dirtyAnswer.value = false; answeResult.value = false; - $fetch(appConf.wekoApi + '/captcha/image', { + $fetch(`${appConf.amsApi ?? '/api'}/captcha/image`, { timeout: useRuntimeConfig().public.apiTimeout, method: 'GET', headers: { @@ -227,7 +227,7 @@ async function send() { locale.value = String(localStorage.getItem('locale')); const contactType = t(props.type); - await useFetch('/api/mail/send', { + await useFetch(`${appConf.amsApi ?? '/api'}/mail/send`, { method: 'POST', body: JSON.stringify({ key: captchaKey, diff --git a/nginx/ams/weko-frontend/components/detail/DownloadRank.vue b/nginx/ams/weko-frontend/components/detail/DownloadRank.vue index e359364acf..f03228eec2 100644 --- a/nginx/ams/weko-frontend/components/detail/DownloadRank.vue +++ b/nginx/ams/weko-frontend/components/detail/DownloadRank.vue @@ -43,6 +43,7 @@ diff --git a/nginx/ams/weko-frontend/components/detail/ViewsNumber.vue b/nginx/ams/weko-frontend/components/detail/ViewsNumber.vue index 53e7251e78..b7c7ac3dd4 100644 --- a/nginx/ams/weko-frontend/components/detail/ViewsNumber.vue +++ b/nginx/ams/weko-frontend/components/detail/ViewsNumber.vue @@ -30,6 +30,7 @@ diff --git a/nginx/ams/weko-frontend/pages/files.vue b/nginx/ams/weko-frontend/pages/ams/files.vue similarity index 87% rename from nginx/ams/weko-frontend/pages/files.vue rename to nginx/ams/weko-frontend/pages/ams/files.vue index 8178895a53..30106651af 100644 --- a/nginx/ams/weko-frontend/pages/files.vue +++ b/nginx/ams/weko-frontend/pages/ams/files.vue @@ -170,7 +170,7 @@ {{ $t('comment') }} - + - Download + Download {{ $t('download') }} ({{ selectedFiles.length }} {{ $t('items') }}) @@ -209,18 +209,14 @@ - + diff --git a/nginx/ams/weko-frontend/pages/logout.vue b/nginx/ams/weko-frontend/pages/ams/logout.vue similarity index 86% rename from nginx/ams/weko-frontend/pages/logout.vue rename to nginx/ams/weko-frontend/pages/ams/logout.vue index 4f2fbeb864..cbfc15d303 100644 --- a/nginx/ams/weko-frontend/pages/logout.vue +++ b/nginx/ams/weko-frontend/pages/ams/logout.vue @@ -2,7 +2,10 @@
- AMS Logo + +

MoonshotG2 Database Catalog

- Logout + Logout {{ $t('logout') }}
@@ -49,13 +52,14 @@ /////////////////////////////////// */ const isLogout = ref(false); +const appConf = useAppConfig(); /* /////////////////////////////////// // function /////////////////////////////////// */ function logout() { - $fetch(useAppConfig().wekoApi + '/logout', { + $fetch(appConf.wekoApi + '/logout', { timeout: useRuntimeConfig().public.apiTimeout, method: 'POST', onResponse() { diff --git a/nginx/ams/weko-frontend/pages/preview.vue b/nginx/ams/weko-frontend/pages/ams/preview.vue similarity index 100% rename from nginx/ams/weko-frontend/pages/preview.vue rename to nginx/ams/weko-frontend/pages/ams/preview.vue diff --git a/nginx/ams/weko-frontend/pages/search/[id].vue b/nginx/ams/weko-frontend/pages/ams/search/[id].vue similarity index 76% rename from nginx/ams/weko-frontend/pages/search/[id].vue rename to nginx/ams/weko-frontend/pages/ams/search/[id].vue index d53bfbc6ef..d758860507 100644 --- a/nginx/ams/weko-frontend/pages/search/[id].vue +++ b/nginx/ams/weko-frontend/pages/ams/search/[id].vue @@ -6,16 +6,13 @@ @@ -109,16 +106,14 @@ - +