From 34cbb78c1f082753f9d8af6e5f23a846a43fb597 Mon Sep 17 00:00:00 2001 From: Didier Dupertuis Date: Thu, 28 Mar 2024 16:01:04 +0100 Subject: [PATCH 01/32] fixed missing button styles for project admin "refresh files list" --- .../templates/admin/project_files_widget.html | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docker-app/qfieldcloud/core/templates/admin/project_files_widget.html b/docker-app/qfieldcloud/core/templates/admin/project_files_widget.html index a14858198..ab233daac 100644 --- a/docker-app/qfieldcloud/core/templates/admin/project_files_widget.html +++ b/docker-app/qfieldcloud/core/templates/admin/project_files_widget.html @@ -4,10 +4,10 @@

 
-        
+        
     
- @@ -44,7 +44,7 @@ - + - - + + @@ -68,7 +68,7 @@ overflow-y: auto; } - .qfc-admin-projects-files-actions-reload-btn, + /*.qfc-admin-projects-files-actions-reload-btn, .qfc-admin-projects-files-actions-info-btn, .qfc-admin-projects-files-actions-delete-btn, .qfc-admin-projects-files-actions-download-btn { @@ -83,11 +83,11 @@ border: none; background: var(--button-bg); color: var(--button-fg); - } + }*/ - .qfc-admin-projects-files-actions-delete-btn { + /*.qfc-admin-projects-files-actions-delete-btn { background: var(--delete-button-bg); - } + }*/ .qfc-admin-project-files-modal { display: none; /* Hidden by default */ From b298d6c79b503fbb46a5cd7cf8b586fc0687ff40 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Thu, 28 Mar 2024 17:45:18 +0200 Subject: [PATCH 02/32] Do not remove broken WMS layers during packaging --- docker-qgis/requirements_libqfieldsync.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-qgis/requirements_libqfieldsync.txt b/docker-qgis/requirements_libqfieldsync.txt index e85c6479c..a711c479d 100644 --- a/docker-qgis/requirements_libqfieldsync.txt +++ b/docker-qgis/requirements_libqfieldsync.txt @@ -1 +1 @@ -libqfieldsync @ git+https://github.com/opengisch/libqfieldsync@5ed1f872f2449657dfb91a00e9da48469d62e226 +libqfieldsync @ git+https://github.com/opengisch/libqfieldsync@d4c275162dd39780cab23f7d977ca4bdd38f126e From e6a4114383dae8f280040143c032d1c1f32a654a Mon Sep 17 00:00:00 2001 From: Mathieu Pellerin Date: Thu, 4 Apr 2024 17:22:02 +0700 Subject: [PATCH 03/32] Fix localized path regression --- docker-qgis/qfc_worker/utils.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docker-qgis/qfc_worker/utils.py b/docker-qgis/qfc_worker/utils.py index 5b7f1986f..e3ee725c6 100644 --- a/docker-qgis/qfc_worker/utils.py +++ b/docker-qgis/qfc_worker/utils.py @@ -20,7 +20,10 @@ from typing import IO, Any, Callable, NamedTuple, Optional from libqfieldsync.layer import LayerSource -from libqfieldsync.utils.bad_layer_handler import set_bad_layer_handler +from libqfieldsync.utils.bad_layer_handler import ( + bad_layer_handler, + set_bad_layer_handler, +) from qfieldcloud_sdk import sdk from qgis.core import ( Qgis, @@ -186,6 +189,9 @@ def exitQgis(): logging.info("QGIS app started!") + # we set the `bad_layer_handler` and assume we always have only one single `QgsProject` instance within the job's life + QgsProject.instance().setBadLayerHandler(bad_layer_handler) + return QGISAPP From ee42887b99adb8854f4658c59354d144aa6243b2 Mon Sep 17 00:00:00 2001 From: Didier Dupertuis Date: Tue, 9 Apr 2024 11:41:17 +0200 Subject: [PATCH 04/32] remove commented code --- .../templates/admin/project_files_widget.html | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/docker-app/qfieldcloud/core/templates/admin/project_files_widget.html b/docker-app/qfieldcloud/core/templates/admin/project_files_widget.html index ab233daac..f07907519 100644 --- a/docker-app/qfieldcloud/core/templates/admin/project_files_widget.html +++ b/docker-app/qfieldcloud/core/templates/admin/project_files_widget.html @@ -68,27 +68,6 @@ overflow-y: auto; } - /*.qfc-admin-projects-files-actions-reload-btn, - .qfc-admin-projects-files-actions-info-btn, - .qfc-admin-projects-files-actions-delete-btn, - .qfc-admin-projects-files-actions-download-btn { - padding: 8px 15px; - border-radius: 4px; - cursor: pointer; - transition: background 0.15s; - vertical-align: middle; - font-family: "Roboto", "Lucida Grande", Verdana, Arial, sans-serif; - font-weight: normal; - font-size: 13px; - border: none; - background: var(--button-bg); - color: var(--button-fg); - }*/ - - /*.qfc-admin-projects-files-actions-delete-btn { - background: var(--delete-button-bg); - }*/ - .qfc-admin-project-files-modal { display: none; /* Hidden by default */ position: fixed; /* Stay in place */ From 4c4b856927c3d30a22b15a78d42adf7942801e01 Mon Sep 17 00:00:00 2001 From: Didier Dupertuis Date: Tue, 9 Apr 2024 11:47:49 +0200 Subject: [PATCH 05/32] add bootstrap select styling --- .../qfieldcloud/core/templates/admin/project_files_widget.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-app/qfieldcloud/core/templates/admin/project_files_widget.html b/docker-app/qfieldcloud/core/templates/admin/project_files_widget.html index f07907519..c6f7aa59d 100644 --- a/docker-app/qfieldcloud/core/templates/admin/project_files_widget.html +++ b/docker-app/qfieldcloud/core/templates/admin/project_files_widget.html @@ -47,7 +47,7 @@ - From 442f1308753f8ba4446558b9b1d10a404b8bffd3 Mon Sep 17 00:00:00 2001 From: Didier Dupertuis Date: Tue, 9 Apr 2024 17:40:25 +0200 Subject: [PATCH 06/32] fix init_letsencrypt.sh CONFIG_PATH --- .env.example | 2 +- scripts/init_letsencrypt.sh | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.env.example b/.env.example index 5787e6288..71b6f0977 100644 --- a/.env.example +++ b/.env.example @@ -165,4 +165,4 @@ DEBUG_DEBUGPY_WORKER_WRAPPER_PORT=5679 # Path to the nginx, letsencrypt, etc configuration files, used by script in `./scripts/`. # DEFAULT: ./conf -CONFIG_PATH=./conf +CONFIG_PATH=./ diff --git a/scripts/init_letsencrypt.sh b/scripts/init_letsencrypt.sh index 10f03705d..f0f9aa95d 100755 --- a/scripts/init_letsencrypt.sh +++ b/scripts/init_letsencrypt.sh @@ -7,12 +7,12 @@ set -o allexport source .env set +o allexport -CONFIG_PATH="${CONFIG_PATH:-'./conf'}" +CONFIG_PATH="${CONFIG_PATH:-'./'}" -if [ ! -e "docker-nginx/options-ssl-nginx.conf" ] || [ ! -e "docker-nginx/ssl-dhparams.pem" ]; then +if [ ! -e "${CONFIG_PATH}/docker-nginx/options-ssl-nginx.conf" ] || [ ! -e "${CONFIG_PATH}/docker-nginx/ssl-dhparams.pem" ]; then echo "### Downloading recommended TLS parameters ..." - curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf > "docker-nginx/options-ssl-nginx.conf" - curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem > "docker-nginx/ssl-dhparams.pem" + curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf > "${CONFIG_PATH}/docker-nginx/options-ssl-nginx.conf" + curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem > "${CONFIG_PATH}/docker-nginx/ssl-dhparams.pem" echo fi @@ -34,8 +34,8 @@ docker compose run --rm --entrypoint "\ echo echo "### Copy the certificate and key to their final destination ..." -cp ${CONFIG_PATH}/certbot/conf/live/${QFIELDCLOUD_HOST}/fullchain.pem docker-nginx/certs/${QFIELDCLOUD_HOST}.pem -cp ${CONFIG_PATH}/certbot/conf/live/${QFIELDCLOUD_HOST}/privkey.pem docker-nginx/certs/${QFIELDCLOUD_HOST}-key.pem +cp ${CONFIG_PATH}/conf/certbot/conf/live/${QFIELDCLOUD_HOST}/fullchain.pem ${CONFIG_PATH}/docker-nginx/certs/${QFIELDCLOUD_HOST}.pem +cp ${CONFIG_PATH}/conf/certbot/conf/live/${QFIELDCLOUD_HOST}/privkey.pem ${CONFIG_PATH}/docker-nginx/certs/${QFIELDCLOUD_HOST}-key.pem echo echo "### Reloading nginx ..." From f5702e03e48dec9cfbef2f8061d318ee7d60b5f6 Mon Sep 17 00:00:00 2001 From: Didier Dupertuis Date: Wed, 10 Apr 2024 12:00:09 +0200 Subject: [PATCH 07/32] removing CONFIG_PATH env var --- .env.example | 4 ---- scripts/check_envvars.sh | 2 +- scripts/init_letsencrypt.sh | 12 ++++++------ 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/.env.example b/.env.example index 71b6f0977..7e80852c0 100644 --- a/.env.example +++ b/.env.example @@ -162,7 +162,3 @@ DEBUG_DEBUGPY_APP_PORT=5678 # Debugpy port used for the `worker_wrapper` service # DEFAULT: 5679 DEBUG_DEBUGPY_WORKER_WRAPPER_PORT=5679 - -# Path to the nginx, letsencrypt, etc configuration files, used by script in `./scripts/`. -# DEFAULT: ./conf -CONFIG_PATH=./ diff --git a/scripts/check_envvars.sh b/scripts/check_envvars.sh index c5cc141bf..4a705af2b 100755 --- a/scripts/check_envvars.sh +++ b/scripts/check_envvars.sh @@ -1,3 +1,3 @@ #!/bin/bash -e -python3 scripts/check_envvars.py .env.example --docker-compose-dir . --ignored-varnames CONFIG_PATH DEBUG_DEBUGPY_APP_PORT DEBUG_DEBUGPY_WORKER_WRAPPER_PORT +python3 scripts/check_envvars.py .env.example --docker-compose-dir . --ignored-varnames DEBUG_DEBUGPY_APP_PORT DEBUG_DEBUGPY_WORKER_WRAPPER_PORT diff --git a/scripts/init_letsencrypt.sh b/scripts/init_letsencrypt.sh index f0f9aa95d..6ba59a906 100755 --- a/scripts/init_letsencrypt.sh +++ b/scripts/init_letsencrypt.sh @@ -7,12 +7,12 @@ set -o allexport source .env set +o allexport -CONFIG_PATH="${CONFIG_PATH:-'./'}" +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -if [ ! -e "${CONFIG_PATH}/docker-nginx/options-ssl-nginx.conf" ] || [ ! -e "${CONFIG_PATH}/docker-nginx/ssl-dhparams.pem" ]; then +if [ ! -e "${SCRIPT_DIR}/../docker-nginx/options-ssl-nginx.conf" ] || [ ! -e "${SCRIPT_DIR}/../docker-nginx/ssl-dhparams.pem" ]; then echo "### Downloading recommended TLS parameters ..." - curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf > "${CONFIG_PATH}/docker-nginx/options-ssl-nginx.conf" - curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem > "${CONFIG_PATH}/docker-nginx/ssl-dhparams.pem" + curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf > "${SCRIPT_DIR}/../docker-nginx/options-ssl-nginx.conf" + curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem > "${SCRIPT_DIR}/../docker-nginx/ssl-dhparams.pem" echo fi @@ -34,8 +34,8 @@ docker compose run --rm --entrypoint "\ echo echo "### Copy the certificate and key to their final destination ..." -cp ${CONFIG_PATH}/conf/certbot/conf/live/${QFIELDCLOUD_HOST}/fullchain.pem ${CONFIG_PATH}/docker-nginx/certs/${QFIELDCLOUD_HOST}.pem -cp ${CONFIG_PATH}/conf/certbot/conf/live/${QFIELDCLOUD_HOST}/privkey.pem ${CONFIG_PATH}/docker-nginx/certs/${QFIELDCLOUD_HOST}-key.pem +cp ${SCRIPT_DIR}/../conf/certbot/conf/live/${QFIELDCLOUD_HOST}/fullchain.pem ${SCRIPT_DIR}/../docker-nginx/certs/${QFIELDCLOUD_HOST}.pem +cp ${SCRIPT_DIR}/../conf/certbot/conf/live/${QFIELDCLOUD_HOST}/privkey.pem ${SCRIPT_DIR}/../docker-nginx/certs/${QFIELDCLOUD_HOST}-key.pem echo echo "### Reloading nginx ..." From bb0e145932b24e63f3c7ec75d23e30d9608b23a9 Mon Sep 17 00:00:00 2001 From: dddpt <2746311+dddpt@users.noreply.github.com> Date: Thu, 11 Apr 2024 10:49:44 +0200 Subject: [PATCH 08/32] beautiful shell code Co-authored-by: Fabian Binder --- scripts/init_letsencrypt.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/init_letsencrypt.sh b/scripts/init_letsencrypt.sh index 6ba59a906..188bafd65 100755 --- a/scripts/init_letsencrypt.sh +++ b/scripts/init_letsencrypt.sh @@ -7,7 +7,7 @@ set -o allexport source .env set +o allexport -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +SCRIPT_DIR=$(dirname "$(realpath "$0")") if [ ! -e "${SCRIPT_DIR}/../docker-nginx/options-ssl-nginx.conf" ] || [ ! -e "${SCRIPT_DIR}/../docker-nginx/ssl-dhparams.pem" ]; then echo "### Downloading recommended TLS parameters ..." From 5587598c5b2de8be5cafa6094ead3d237d736750 Mon Sep 17 00:00:00 2001 From: Didier Dupertuis Date: Thu, 11 Apr 2024 14:08:57 +0200 Subject: [PATCH 09/32] SCRIPT_DIR -> QFIELDCLOUD_DIR, better --- scripts/init_letsencrypt.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/init_letsencrypt.sh b/scripts/init_letsencrypt.sh index 6ba59a906..7d0c7bf56 100755 --- a/scripts/init_letsencrypt.sh +++ b/scripts/init_letsencrypt.sh @@ -7,12 +7,12 @@ set -o allexport source .env set +o allexport -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +QFIELDCLOUD_DIR="$(dirname "$(realpath "$0")")/.." -if [ ! -e "${SCRIPT_DIR}/../docker-nginx/options-ssl-nginx.conf" ] || [ ! -e "${SCRIPT_DIR}/../docker-nginx/ssl-dhparams.pem" ]; then +if [ ! -e "${QFIELDCLOUD_DIR}/docker-nginx/options-ssl-nginx.conf" ] || [ ! -e "${QFIELDCLOUD_DIR}/docker-nginx/ssl-dhparams.pem" ]; then echo "### Downloading recommended TLS parameters ..." - curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf > "${SCRIPT_DIR}/../docker-nginx/options-ssl-nginx.conf" - curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem > "${SCRIPT_DIR}/../docker-nginx/ssl-dhparams.pem" + curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf > "${QFIELDCLOUD_DIR}/docker-nginx/options-ssl-nginx.conf" + curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem > "${QFIELDCLOUD_DIR}/docker-nginx/ssl-dhparams.pem" echo fi @@ -34,8 +34,8 @@ docker compose run --rm --entrypoint "\ echo echo "### Copy the certificate and key to their final destination ..." -cp ${SCRIPT_DIR}/../conf/certbot/conf/live/${QFIELDCLOUD_HOST}/fullchain.pem ${SCRIPT_DIR}/../docker-nginx/certs/${QFIELDCLOUD_HOST}.pem -cp ${SCRIPT_DIR}/../conf/certbot/conf/live/${QFIELDCLOUD_HOST}/privkey.pem ${SCRIPT_DIR}/../docker-nginx/certs/${QFIELDCLOUD_HOST}-key.pem +cp ${QFIELDCLOUD_DIR}/conf/certbot/conf/live/${QFIELDCLOUD_HOST}/fullchain.pem ${QFIELDCLOUD_DIR}/docker-nginx/certs/${QFIELDCLOUD_HOST}.pem +cp ${QFIELDCLOUD_DIR}/conf/certbot/conf/live/${QFIELDCLOUD_HOST}/privkey.pem ${QFIELDCLOUD_DIR}/docker-nginx/certs/${QFIELDCLOUD_HOST}-key.pem echo echo "### Reloading nginx ..." From a8827061f2f611f35fd120a249cf87e795915be4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Apr 2024 02:24:38 +0000 Subject: [PATCH 10/32] Bump idna from 3.4 to 3.7 in /docker-app Bumps [idna](https://github.com/kjd/idna) from 3.4 to 3.7. - [Release notes](https://github.com/kjd/idna/releases) - [Changelog](https://github.com/kjd/idna/blob/master/HISTORY.rst) - [Commits](https://github.com/kjd/idna/compare/v3.4...v3.7) --- updated-dependencies: - dependency-name: idna dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- docker-app/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-app/requirements.txt b/docker-app/requirements.txt index 1b3b16c54..62dd1b9cf 100644 --- a/docker-app/requirements.txt +++ b/docker-app/requirements.txt @@ -45,7 +45,7 @@ django-tables2==2.7.0 django-timezone-field==6.1.0 djangorestframework==3.14.0 drf-spectacular==0.26.3 -idna==3.4 +idna==3.7 inflection==0.5.1 itypes==1.2.0 Jinja2==3.1.3 From 35870f41bdf935b5d55907c6ab44ab03c7f5019b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Apr 2024 01:46:31 +0000 Subject: [PATCH 11/32] Bump the pip group across 1 directory with 2 updates Bumps the pip group with 2 updates in the /docker-app directory: [sqlparse](https://github.com/andialbrecht/sqlparse) and [gunicorn](https://github.com/benoitc/gunicorn). Updates `sqlparse` from 0.4.4 to 0.5.0 - [Changelog](https://github.com/andialbrecht/sqlparse/blob/master/CHANGELOG) - [Commits](https://github.com/andialbrecht/sqlparse/compare/0.4.4...0.5.0) Updates `gunicorn` from 20.1.0 to 22.0.0 - [Release notes](https://github.com/benoitc/gunicorn/releases) - [Commits](https://github.com/benoitc/gunicorn/compare/20.1.0...22.0.0) --- updated-dependencies: - dependency-name: sqlparse dependency-type: direct:production dependency-group: pip - dependency-name: gunicorn dependency-type: direct:production dependency-group: pip ... Signed-off-by: dependabot[bot] --- docker-app/requirements.txt | 2 +- docker-app/requirements_runtime.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-app/requirements.txt b/docker-app/requirements.txt index 62dd1b9cf..915db4dc2 100644 --- a/docker-app/requirements.txt +++ b/docker-app/requirements.txt @@ -76,7 +76,7 @@ s3transfer==0.5.0 sentry-sdk==1.24.0 six==1.16.0 soupsieve==2.4.1 -sqlparse==0.4.4 +sqlparse==0.5.0 stripe==4.2.0 swapper==1.3.0 typing-extensions==4.6.0 diff --git a/docker-app/requirements_runtime.txt b/docker-app/requirements_runtime.txt index 9d41f264a..5389b4df8 100644 --- a/docker-app/requirements_runtime.txt +++ b/docker-app/requirements_runtime.txt @@ -1 +1 @@ -gunicorn==20.1.0 +gunicorn==22.0.0 From f732ff3cab20007bd9c7e2066d5482ffa6c49380 Mon Sep 17 00:00:00 2001 From: Didier Dupertuis Date: Thu, 18 Apr 2024 11:32:38 +0200 Subject: [PATCH 12/32] init_letsencrypt.sh remove download nginx ssl conf --- scripts/init_letsencrypt.sh | 7 ------- 1 file changed, 7 deletions(-) diff --git a/scripts/init_letsencrypt.sh b/scripts/init_letsencrypt.sh index 7d0c7bf56..d47ebb9ff 100755 --- a/scripts/init_letsencrypt.sh +++ b/scripts/init_letsencrypt.sh @@ -9,13 +9,6 @@ set +o allexport QFIELDCLOUD_DIR="$(dirname "$(realpath "$0")")/.." -if [ ! -e "${QFIELDCLOUD_DIR}/docker-nginx/options-ssl-nginx.conf" ] || [ ! -e "${QFIELDCLOUD_DIR}/docker-nginx/ssl-dhparams.pem" ]; then - echo "### Downloading recommended TLS parameters ..." - curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf > "${QFIELDCLOUD_DIR}/docker-nginx/options-ssl-nginx.conf" - curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem > "${QFIELDCLOUD_DIR}/docker-nginx/ssl-dhparams.pem" - echo -fi - echo "### Requesting Let's Encrypt certificate for $QFIELDCLOUD_HOST ..." domain_args="-d ${QFIELDCLOUD_HOST}" From e454ff0a3bd1162a8dd841ada90cfe667ed13124 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Tue, 23 Apr 2024 13:54:17 +0300 Subject: [PATCH 13/32] Fix test that was never working properly --- docker-app/qfieldcloud/core/tests/test_delta.py | 2 +- .../testdata/delta/deltas/nonspatial_geom_empty_str.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docker-app/qfieldcloud/core/tests/test_delta.py b/docker-app/qfieldcloud/core/tests/test_delta.py index c605195cc..b4ad03925 100644 --- a/docker-app/qfieldcloud/core/tests/test_delta.py +++ b/docker-app/qfieldcloud/core/tests/test_delta.py @@ -791,7 +791,7 @@ def test_non_spatial_geom_empty_str_delta(self): self.assertEqual( self.get_file_contents(project, "nonspatial.csv"), - b'fid,col1\n"1",foo\n"2",newfeature\n', + b'fid,col1\n"1",new_value\n', ) def test_special_data_types(self): diff --git a/docker-app/qfieldcloud/core/tests/testdata/delta/deltas/nonspatial_geom_empty_str.json b/docker-app/qfieldcloud/core/tests/testdata/delta/deltas/nonspatial_geom_empty_str.json index b9ee5e98d..070a21116 100644 --- a/docker-app/qfieldcloud/core/tests/testdata/delta/deltas/nonspatial_geom_empty_str.json +++ b/docker-app/qfieldcloud/core/tests/testdata/delta/deltas/nonspatial_geom_empty_str.json @@ -12,13 +12,13 @@ "new": { "geometry": "", "attributes": { - "col1": "foo" + "col1": "new_value" } }, "old": { "geometry": null, "attributes": { - "col1": "bar" + "col1": "foo" } } } From 68f24a444446b55acf293a9219b94c6dffaa5fa8 Mon Sep 17 00:00:00 2001 From: faebebin Date: Tue, 23 Apr 2024 15:38:25 +0200 Subject: [PATCH 14/32] Remove unnecessary QFIELDCLOUD_DEFAULT_NETWORK env --- .env.example | 2 -- README.md | 1 - docker-app/qfieldcloud/settings.py | 3 --- docker-app/worker_wrapper/wrapper.py | 1 - docker-compose.override.test.yml | 5 ----- docker-compose.yml | 1 - 6 files changed, 13 deletions(-) diff --git a/.env.example b/.env.example index 7e80852c0..9a1d1058a 100644 --- a/.env.example +++ b/.env.example @@ -88,8 +88,6 @@ EMAIL_HOST_USER=user EMAIL_HOST_PASSWORD=password DEFAULT_FROM_EMAIL="webmaster@localhost" -QFIELDCLOUD_DEFAULT_NETWORK=qfieldcloud_default - # Admin URI. Requires slash in the end. Please use something that is hard to guess. QFIELDCLOUD_ADMIN_URI=admin/ diff --git a/README.md b/README.md index e54e506d6..d6bab0b44 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,6 @@ Create an .env.test file with the following variables that override HOST_POSTGRES_PORT=8103 HOST_GEODB_PORT=8107 MEMCACHED_PORT=11212 - QFIELDCLOUD_DEFAULT_NETWORK=qfieldcloud_test_default QFIELDCLOUD_SUBSCRIPTION_MODEL=subscription.Subscription DJANGO_DEV_PORT=8111 SMTP4DEV_WEB_PORT=8112 diff --git a/docker-app/qfieldcloud/settings.py b/docker-app/qfieldcloud/settings.py index bac112323..86ab387a0 100644 --- a/docker-app/qfieldcloud/settings.py +++ b/docker-app/qfieldcloud/settings.py @@ -521,9 +521,6 @@ def before_send(event, hint): "QFIELDCLOUD_TRANSFORMATION_GRIDS_VOLUME_NAME" ) -# Name of the docker compose network to be used by the worker containers -QFIELDCLOUD_DEFAULT_NETWORK = os.environ.get("QFIELDCLOUD_DEFAULT_NETWORK") - # `django-auditlog` configurations, read more on https://django-auditlog.readthedocs.io/en/latest/usage.html AUDITLOG_INCLUDE_TRACKING_MODELS = [ # NOTE `Delta` and `Job` models are not being automatically audited, because their data changes very often and timestamps are available in their models. diff --git a/docker-app/worker_wrapper/wrapper.py b/docker-app/worker_wrapper/wrapper.py index d69bcff6c..4221b0522 100644 --- a/docker-app/worker_wrapper/wrapper.py +++ b/docker-app/worker_wrapper/wrapper.py @@ -309,7 +309,6 @@ def _run_docker( volumes=volumes, # TODO stream the logs to something like redis, so they can be streamed back in project jobs page to the user live # auto_remove=True, - network=settings.QFIELDCLOUD_DEFAULT_NETWORK, detach=True, mem_limit=config.WORKER_QGIS_MEMORY_LIMIT, cpu_shares=config.WORKER_QGIS_CPU_SHARES, diff --git a/docker-compose.override.test.yml b/docker-compose.override.test.yml index fb4c8823d..263309f05 100644 --- a/docker-compose.override.test.yml +++ b/docker-compose.override.test.yml @@ -22,11 +22,6 @@ services: environment: POSTGRES_DB: test_${POSTGRES_DB} -networks: - default: - # Use a custom driver - name: ${QFIELDCLOUD_DEFAULT_NETWORK} - volumes: # We use a different volume, just so that the test_ database # gets created in the entrypoint. diff --git a/docker-compose.yml b/docker-compose.yml index 479d5289e..868a0d8eb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -48,7 +48,6 @@ services: STORAGE_BUCKET_NAME: ${STORAGE_BUCKET_NAME} STORAGE_REGION_NAME: ${STORAGE_REGION_NAME} STORAGE_ENDPOINT_URL: ${STORAGE_ENDPOINT_URL} - QFIELDCLOUD_DEFAULT_NETWORK: ${QFIELDCLOUD_DEFAULT_NETWORK} GEODB_HOST: ${GEODB_HOST} GEODB_PORT: ${GEODB_PORT} GEODB_USER: ${GEODB_USER} From 15222c59188afb74e048bf1c3738b568cbf4c8e8 Mon Sep 17 00:00:00 2001 From: faebebin Date: Tue, 23 Apr 2024 15:41:21 +0200 Subject: [PATCH 15/32] Add back necessary worker network --- docker-app/qfieldcloud/settings.py | 3 +++ docker-app/worker_wrapper/wrapper.py | 1 + 2 files changed, 4 insertions(+) diff --git a/docker-app/qfieldcloud/settings.py b/docker-app/qfieldcloud/settings.py index 86ab387a0..989c0f53c 100644 --- a/docker-app/qfieldcloud/settings.py +++ b/docker-app/qfieldcloud/settings.py @@ -521,6 +521,9 @@ def before_send(event, hint): "QFIELDCLOUD_TRANSFORMATION_GRIDS_VOLUME_NAME" ) +# Name of the docker compose network to be used by the worker containers +QFIELDCLOUD_DEFAULT_NETWORK = f"{os.environ.get('COMPOSE_PROJECT_NAME')}_default" + # `django-auditlog` configurations, read more on https://django-auditlog.readthedocs.io/en/latest/usage.html AUDITLOG_INCLUDE_TRACKING_MODELS = [ # NOTE `Delta` and `Job` models are not being automatically audited, because their data changes very often and timestamps are available in their models. diff --git a/docker-app/worker_wrapper/wrapper.py b/docker-app/worker_wrapper/wrapper.py index 4221b0522..d69bcff6c 100644 --- a/docker-app/worker_wrapper/wrapper.py +++ b/docker-app/worker_wrapper/wrapper.py @@ -309,6 +309,7 @@ def _run_docker( volumes=volumes, # TODO stream the logs to something like redis, so they can be streamed back in project jobs page to the user live # auto_remove=True, + network=settings.QFIELDCLOUD_DEFAULT_NETWORK, detach=True, mem_limit=config.WORKER_QGIS_MEMORY_LIMIT, cpu_shares=config.WORKER_QGIS_CPU_SHARES, From 444f60b013461b279f3ce101d684e80def654df8 Mon Sep 17 00:00:00 2001 From: faebebin Date: Mon, 29 Apr 2024 10:31:49 +0200 Subject: [PATCH 16/32] Revert: complete remove env var QFIELD_DEFAULT_NETWORK --- .env.example | 2 ++ README.md | 1 + docker-app/qfieldcloud/settings.py | 2 +- docker-compose.override.test.yml | 5 +++++ docker-compose.yml | 1 + 5 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 9a1d1058a..7e80852c0 100644 --- a/.env.example +++ b/.env.example @@ -88,6 +88,8 @@ EMAIL_HOST_USER=user EMAIL_HOST_PASSWORD=password DEFAULT_FROM_EMAIL="webmaster@localhost" +QFIELDCLOUD_DEFAULT_NETWORK=qfieldcloud_default + # Admin URI. Requires slash in the end. Please use something that is hard to guess. QFIELDCLOUD_ADMIN_URI=admin/ diff --git a/README.md b/README.md index d6bab0b44..e54e506d6 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,7 @@ Create an .env.test file with the following variables that override HOST_POSTGRES_PORT=8103 HOST_GEODB_PORT=8107 MEMCACHED_PORT=11212 + QFIELDCLOUD_DEFAULT_NETWORK=qfieldcloud_test_default QFIELDCLOUD_SUBSCRIPTION_MODEL=subscription.Subscription DJANGO_DEV_PORT=8111 SMTP4DEV_WEB_PORT=8112 diff --git a/docker-app/qfieldcloud/settings.py b/docker-app/qfieldcloud/settings.py index 989c0f53c..bac112323 100644 --- a/docker-app/qfieldcloud/settings.py +++ b/docker-app/qfieldcloud/settings.py @@ -522,7 +522,7 @@ def before_send(event, hint): ) # Name of the docker compose network to be used by the worker containers -QFIELDCLOUD_DEFAULT_NETWORK = f"{os.environ.get('COMPOSE_PROJECT_NAME')}_default" +QFIELDCLOUD_DEFAULT_NETWORK = os.environ.get("QFIELDCLOUD_DEFAULT_NETWORK") # `django-auditlog` configurations, read more on https://django-auditlog.readthedocs.io/en/latest/usage.html AUDITLOG_INCLUDE_TRACKING_MODELS = [ diff --git a/docker-compose.override.test.yml b/docker-compose.override.test.yml index 263309f05..fb4c8823d 100644 --- a/docker-compose.override.test.yml +++ b/docker-compose.override.test.yml @@ -22,6 +22,11 @@ services: environment: POSTGRES_DB: test_${POSTGRES_DB} +networks: + default: + # Use a custom driver + name: ${QFIELDCLOUD_DEFAULT_NETWORK} + volumes: # We use a different volume, just so that the test_ database # gets created in the entrypoint. diff --git a/docker-compose.yml b/docker-compose.yml index 868a0d8eb..479d5289e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -48,6 +48,7 @@ services: STORAGE_BUCKET_NAME: ${STORAGE_BUCKET_NAME} STORAGE_REGION_NAME: ${STORAGE_REGION_NAME} STORAGE_ENDPOINT_URL: ${STORAGE_ENDPOINT_URL} + QFIELDCLOUD_DEFAULT_NETWORK: ${QFIELDCLOUD_DEFAULT_NETWORK} GEODB_HOST: ${GEODB_HOST} GEODB_PORT: ${GEODB_PORT} GEODB_USER: ${GEODB_USER} From 3d187649a4da64c5fcd127164efcaccf64965cd5 Mon Sep 17 00:00:00 2001 From: faebebin Date: Mon, 29 Apr 2024 10:42:56 +0200 Subject: [PATCH 17/32] User overridable default empty env instead --- .env.example | 4 +++- docker-compose.override.test.yml | 2 +- docker-compose.yml | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index 7e80852c0..e41377584 100644 --- a/.env.example +++ b/.env.example @@ -88,7 +88,9 @@ EMAIL_HOST_USER=user EMAIL_HOST_PASSWORD=password DEFAULT_FROM_EMAIL="webmaster@localhost" -QFIELDCLOUD_DEFAULT_NETWORK=qfieldcloud_default +# Docker compose default network also used by the docker in docker workers +# If empty value, a default name will be generated at build time, for example `qfieldcloud_default`. +# QFIELDCLOUD_DEFAULT_NETWORK="" # Admin URI. Requires slash in the end. Please use something that is hard to guess. QFIELDCLOUD_ADMIN_URI=admin/ diff --git a/docker-compose.override.test.yml b/docker-compose.override.test.yml index fb4c8823d..2a43a8008 100644 --- a/docker-compose.override.test.yml +++ b/docker-compose.override.test.yml @@ -25,7 +25,7 @@ services: networks: default: # Use a custom driver - name: ${QFIELDCLOUD_DEFAULT_NETWORK} + name: ${QFIELDCLOUD_DEFAULT_NETWORK:-${COMPOSE_PROJECT_NAME}_default} volumes: # We use a different volume, just so that the test_ database diff --git a/docker-compose.yml b/docker-compose.yml index 479d5289e..6898acbb5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -48,7 +48,7 @@ services: STORAGE_BUCKET_NAME: ${STORAGE_BUCKET_NAME} STORAGE_REGION_NAME: ${STORAGE_REGION_NAME} STORAGE_ENDPOINT_URL: ${STORAGE_ENDPOINT_URL} - QFIELDCLOUD_DEFAULT_NETWORK: ${QFIELDCLOUD_DEFAULT_NETWORK} + QFIELDCLOUD_DEFAULT_NETWORK: ${QFIELDCLOUD_DEFAULT_NETWORK:-${COMPOSE_PROJECT_NAME}_default} GEODB_HOST: ${GEODB_HOST} GEODB_PORT: ${GEODB_PORT} GEODB_USER: ${GEODB_USER} From 39e01178af38fa37020626253520d9d3fd2af7e7 Mon Sep 17 00:00:00 2001 From: Mathieu Pellerin Date: Wed, 1 May 2024 15:04:51 +0700 Subject: [PATCH 18/32] Update libqfieldsync revision to catch project plugins support --- docker-qgis/requirements_libqfieldsync.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-qgis/requirements_libqfieldsync.txt b/docker-qgis/requirements_libqfieldsync.txt index e85c6479c..075c2dd08 100644 --- a/docker-qgis/requirements_libqfieldsync.txt +++ b/docker-qgis/requirements_libqfieldsync.txt @@ -1 +1 @@ -libqfieldsync @ git+https://github.com/opengisch/libqfieldsync@5ed1f872f2449657dfb91a00e9da48469d62e226 +libqfieldsync @ git+https://github.com/opengisch/libqfieldsync@a449a7c8229f4984585a7ee4901af6767b5b9f19 From 7c671a7ef17e4acec380de90b35a48af9378b457 Mon Sep 17 00:00:00 2001 From: Didier Dupertuis Date: Tue, 7 May 2024 17:17:02 +0200 Subject: [PATCH 19/32] fix Delta Apply stuck in "Started" --- docker-app/qfieldcloud/core/cron.py | 27 ++++++++++++++++++++------- docker-app/worker_wrapper/wrapper.py | 26 ++++++++++---------------- 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/docker-app/qfieldcloud/core/cron.py b/docker-app/qfieldcloud/core/cron.py index b9a72374f..f0fd53c37 100644 --- a/docker-app/qfieldcloud/core/cron.py +++ b/docker-app/qfieldcloud/core/cron.py @@ -7,7 +7,7 @@ from invitations.utils import get_invitation_model from sentry_sdk import capture_message -from ..core.models import Job, Project +from ..core.models import ApplyJob, ApplyJobDelta, Delta, Job, Project from ..core.utils2 import storage from .invitations_utils import send_invitation @@ -56,20 +56,33 @@ def do(self): - timedelta(seconds=config.WORKER_TIMEOUT_S + 10), ) + feedback = { + "error_stack": "", + "error": "Job unexpectedly terminated.", + "error_origin": "worker_wrapper", + "container_exit_code": -2, + } + for job in jobs: capture_message( f'Job "{job.id}" was with status "{job.status}", but worker container no longer exists. Job unexpectedly terminated.' ) + if job.type == Job.Type.DELTA_APPLY: + ApplyJob.objects.get(id=job.id).deltas_to_apply.update( + last_status=Delta.Status.ERROR, + last_feedback=feedback, + last_apply_attempt_at=job.started_at, + last_apply_attempt_by=job.created_by, + ) + + ApplyJobDelta.objects.filter( + apply_job_id=job.id, + ).update(status=Delta.Status.ERROR, feedback=feedback) jobs.update( status=Job.Status.FAILED, finished_at=timezone.now(), - feedback={ - "error_stack": "", - "error": "Job unexpectedly terminated.", - "error_origin": "worker_wrapper", - "container_exit_code": -2, - }, + feedback=feedback, output="Job unexpectedly terminated.", ) diff --git a/docker-app/worker_wrapper/wrapper.py b/docker-app/worker_wrapper/wrapper.py index d69bcff6c..78a4616b8 100644 --- a/docker-app/worker_wrapper/wrapper.py +++ b/docker-app/worker_wrapper/wrapper.py @@ -156,21 +156,9 @@ def run(self): feedback["error_origin"] = "container" feedback["error_stack"] = "" - try: - self.job.output = output.decode("utf-8") - self.job.feedback = feedback - self.job.status = Job.Status.FAILED - self.job.save(update_fields=["output", "feedback", "status"]) - logger.info( - "Set job status to `failed` due to being killed by the docker engine.", - ) - except Exception as err: - logger.error( - "Failed to update job status, probably does not exist in the database.", - exc_info=err, - ) - # No further action required, probably received by wrapper's autoclean mechanism when the `Project` is deleted - return + logger.info( + "Set job status to `failed` due to being killed by the docker engine.", + ) elif exit_code == TIMEOUT_ERROR_EXIT_CODE: feedback["error"] = "Worker timeout error." feedback["error_type"] = "TIMEOUT" @@ -543,13 +531,19 @@ def after_docker_run(self) -> None: def after_docker_exception(self) -> None: Delta.objects.filter( id__in=self.delta_ids, - ).update(last_status=Delta.Status.ERROR) + ).update( + last_status=Delta.Status.ERROR, + last_feedback=self.job.feedback, + last_apply_attempt_at=self.job.started_at, + last_apply_attempt_by=self.job.created_by, + ) ApplyJobDelta.objects.filter( apply_job_id=self.job_id, delta_id__in=self.delta_ids, ).update( status=Delta.Status.ERROR, + feedback=self.job.feedback, ) From 92bf4211c8aad9f46e89a2a1287ff75bf2bf683f Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Wed, 15 May 2024 13:33:48 +0300 Subject: [PATCH 20/32] Allow development of the SDK alongside QFieldCloud --- .env.example | 5 +++++ .gitignore | 1 + docker-app/qfieldcloud/core/views/files_views.py | 2 ++ docker-app/qfieldcloud/settings.py | 5 +++++ docker-app/worker_wrapper/wrapper.py | 6 ++++++ docker-compose.override.local.yml | 3 +++ docker-qgis/Dockerfile | 1 + 7 files changed, 23 insertions(+) diff --git a/.env.example b/.env.example index 7e80852c0..bd81b68be 100644 --- a/.env.example +++ b/.env.example @@ -122,6 +122,11 @@ QFIELDCLOUD_DEFAULT_TIME_ZONE="Europe/Zurich" # DEFAULT: "" QFIELDCLOUD_LIBQFIELDSYNC_VOLUME_PATH="" +# QFieldCloud SDK volume path to be mounted by the `worker_wrapper` into `worker` containers. +# If empty value or invalid value, the pip installed version defined in `requirements_libqfieldsync.txt` will be used. +# DEFAULT: "" +QFIELDCLOUD_QFIELDCLOUD_SDK_VOLUME_PATH="" + # The Django development port. Not used in production. # DEFAULT: 8011 DJANGO_DEV_PORT=8011 diff --git a/.gitignore b/.gitignore index 23825ee50..23d81b37d 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ conf/certbot/* Pipfile* **/site-packages docker-qgis/libqfieldsync +docker-qgis/qfieldcloud-sdk-python diff --git a/docker-app/qfieldcloud/core/views/files_views.py b/docker-app/qfieldcloud/core/views/files_views.py index a9fcf49c5..c7a8fe4ea 100644 --- a/docker-app/qfieldcloud/core/views/files_views.py +++ b/docker-app/qfieldcloud/core/views/files_views.py @@ -83,6 +83,8 @@ def get(self, request: Request, projectid: str) -> Response: path = PurePath(version.key) filename = str(path.relative_to(*path.parts[:3])) last_modified = version.last_modified.strftime("%d.%m.%Y %H:%M:%S %Z") + # NOTE ETag is a MD5. But for the multipart uploaded files, the MD5 is computed from the concatenation of the MD5s of each uploaded part. + # TODO make sure when file metadata is in the DB, this is a real md5sum of the current file. md5sum = version.e_tag.replace('"', "") version_data = { diff --git a/docker-app/qfieldcloud/settings.py b/docker-app/qfieldcloud/settings.py index bac112323..d6ac6c4aa 100644 --- a/docker-app/qfieldcloud/settings.py +++ b/docker-app/qfieldcloud/settings.py @@ -516,6 +516,11 @@ def before_send(event, hint): "QFIELDCLOUD_LIBQFIELDSYNC_VOLUME_PATH" ) +# Absolute path on the docker host where `qfieldcloud-sdk-python` is mounted from for development +QFIELDCLOUD_QFIELDCLOUD_SDK_VOLUME_PATH = os.environ.get( + "QFIELDCLOUD_QFIELDCLOUD_SDK_VOLUME_PATH" +) + # Volume name where transformation grids required by `PROJ` are downloaded to QFIELDCLOUD_TRANSFORMATION_GRIDS_VOLUME_NAME = os.environ.get( "QFIELDCLOUD_TRANSFORMATION_GRIDS_VOLUME_NAME" diff --git a/docker-app/worker_wrapper/wrapper.py b/docker-app/worker_wrapper/wrapper.py index d69bcff6c..80c26755b 100644 --- a/docker-app/worker_wrapper/wrapper.py +++ b/docker-app/worker_wrapper/wrapper.py @@ -291,6 +291,12 @@ def _run_docker( f"{settings.QFIELDCLOUD_LIBQFIELDSYNC_VOLUME_PATH}:/libqfieldsync:ro" ) + # used for local development of QFieldCloud + if settings.QFIELDCLOUD_QFIELDCLOUD_SDK_VOLUME_PATH: + volumes.append( + f"{settings.QFIELDCLOUD_QFIELDCLOUD_SDK_VOLUME_PATH}:/qfieldcloud-sdk-python:ro" + ) + # `docker_started_at`/`docker_finished_at` tracks the time spent on docker only self.job.docker_started_at = timezone.now() self.job.save(update_fields=["docker_started_at"]) diff --git a/docker-compose.override.local.yml b/docker-compose.override.local.yml index 1bfd7911b..048efbcb6 100644 --- a/docker-compose.override.local.yml +++ b/docker-compose.override.local.yml @@ -26,6 +26,7 @@ services: - ${DEBUG_DEBUGPY_WORKER_WRAPPER_PORT:-5679}:5679 environment: QFIELDCLOUD_LIBQFIELDSYNC_VOLUME_PATH: ${QFIELDCLOUD_LIBQFIELDSYNC_VOLUME_PATH} + QFIELDCLOUD_QFIELDCLOUD_SDK_VOLUME_PATH: ${QFIELDCLOUD_QFIELDCLOUD_SDK_VOLUME_PATH} volumes: # mount the source for live reload - ./docker-app/qfieldcloud:/usr/src/app/qfieldcloud @@ -69,6 +70,8 @@ services: volumes: # allow local development for `libqfieldsync` if host directory present; requires `PYTHONPATH=/libqfieldsync:${PYTHONPATH}` - ./docker-qgis/libqfieldsync:/libqfieldsync:ro + # allow local development for `qfieldcloud-sdk-python` if host directory present; requires `PYTHONPATH=/qfieldcloud-sdk-python:${PYTHONPATH}` + - ./docker-qgis/qfieldcloud-sdk-python:/qfieldcloud-sdk-python:ro geodb: image: postgis/postgis:12-3.0 diff --git a/docker-qgis/Dockerfile b/docker-qgis/Dockerfile index 56df24dcc..2363fa979 100644 --- a/docker-qgis/Dockerfile +++ b/docker-qgis/Dockerfile @@ -63,6 +63,7 @@ ENV LANG=C.UTF-8 ENV XDG_RUNTIME_DIR=/root # allow local development for `libqfieldsync`, requires `/libqfieldsync` to be a mounted host directory ENV PYTHONPATH=/libqfieldsync:${PYTHONPATH} +ENV PYTHONPATH=/qfieldcloud-sdk-python:${PYTHONPATH} # upgrade `pip`. If not upgraded, installing from GitHub repo (needed for `libqfieldsync`) will result in UNKNOWN package. RUN pip3 install --upgrade pip From c1e2adec89a3546035a18d70dbd46974f5be4aeb Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Wed, 15 May 2024 17:32:04 +0300 Subject: [PATCH 21/32] Bump to latest Ubuntu 24.04 and QGIS 3.36.2 This will automagically bump `gdal` from 3.4.1 to 3.8.4. We actually care that the GPKG patch from 3.4.2 is present. --- docker-qgis/Dockerfile | 14 +++++++------- docker-qgis/requirements.txt | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docker-qgis/Dockerfile b/docker-qgis/Dockerfile index 56df24dcc..a7166fda8 100644 --- a/docker-qgis/Dockerfile +++ b/docker-qgis/Dockerfile @@ -1,5 +1,5 @@ # NOTE if the ubuntu version is changed, also change it in `Suites:` in apt sources, and in `QGIS_VERSION` -FROM ubuntu:jammy +FROM ubuntu:noble # Install dependencies needed to add QGIS repository RUN apt update \ @@ -13,7 +13,7 @@ RUN wget -O /etc/apt/keyrings/qgis-archive-keyring.gpg https://download.qgis.org COPY <=3.2.0,<3.3 -typing-extensions>=3.7.4.3,<3.7.5 -tabulate==v0.8.9 +jsonschema>=3.2.0 +typing-extensions>=3 +tabulate>=v0.8.9 sentry-sdk requests>=2.28.1 -qfieldcloud-sdk==0.8.2 +qfieldcloud-sdk==0.8.3 From 3885c4b1d5da70161c59bc0febd15237b4d3b456 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Wed, 15 May 2024 18:13:13 +0300 Subject: [PATCH 22/32] Bump QFieldCloud SDK to 0.8.4 Prevents re-uploading of 8+MB files when they have not changed at all --- docker-qgis/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-qgis/requirements.txt b/docker-qgis/requirements.txt index 1c2dcd36a..9073cb662 100644 --- a/docker-qgis/requirements.txt +++ b/docker-qgis/requirements.txt @@ -3,4 +3,4 @@ typing-extensions>=3.7.4.3,<3.7.5 tabulate==v0.8.9 sentry-sdk requests>=2.28.1 -qfieldcloud-sdk==0.8.2 +qfieldcloud-sdk==0.8.4 From bf89654008b515fe863498394d32adda895c276b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 May 2024 05:37:35 +0000 Subject: [PATCH 23/32] --- updated-dependencies: - dependency-name: requests dependency-type: direct:production dependency-group: pip ... Signed-off-by: dependabot[bot] --- docker-app/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-app/requirements.txt b/docker-app/requirements.txt index 915db4dc2..8773064be 100644 --- a/docker-app/requirements.txt +++ b/docker-app/requirements.txt @@ -68,7 +68,7 @@ pyrsistent==0.19.3 python-dateutil==2.8.2 python3-openid==3.2.0 pytz==2023.3 -requests==2.31.0 +requests==2.32.0 requests-oauthlib==1.3.1 ruamel.yaml==0.17.26 ruamel.yaml.clib==0.2.7 From 9e5e7fd67f4f9a6bd0d075186a0611b044c7dab4 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Tue, 21 May 2024 14:02:44 +0300 Subject: [PATCH 24/32] Update docker-app/qfieldcloud/core/views/files_views.py --- docker-app/qfieldcloud/core/views/files_views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-app/qfieldcloud/core/views/files_views.py b/docker-app/qfieldcloud/core/views/files_views.py index c7a8fe4ea..463f329a1 100644 --- a/docker-app/qfieldcloud/core/views/files_views.py +++ b/docker-app/qfieldcloud/core/views/files_views.py @@ -84,7 +84,7 @@ def get(self, request: Request, projectid: str) -> Response: filename = str(path.relative_to(*path.parts[:3])) last_modified = version.last_modified.strftime("%d.%m.%Y %H:%M:%S %Z") # NOTE ETag is a MD5. But for the multipart uploaded files, the MD5 is computed from the concatenation of the MD5s of each uploaded part. - # TODO make sure when file metadata is in the DB, this is a real md5sum of the current file. + # TODO make sure when file metadata is in the DB (QF-2760), this is a real md5sum of the current file. md5sum = version.e_tag.replace('"', "") version_data = { From c5ee209ccb6982131124592e377d501005cd0740 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Wed, 22 May 2024 14:18:00 +0300 Subject: [PATCH 25/32] Do not mix the delta and job feedback --- docker-app/qfieldcloud/core/cron.py | 23 +++++++++++++---------- docker-app/worker_wrapper/wrapper.py | 25 ++++++++++++++++++++----- 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/docker-app/qfieldcloud/core/cron.py b/docker-app/qfieldcloud/core/cron.py index f0fd53c37..9b8653763 100644 --- a/docker-app/qfieldcloud/core/cron.py +++ b/docker-app/qfieldcloud/core/cron.py @@ -56,13 +56,6 @@ def do(self): - timedelta(seconds=config.WORKER_TIMEOUT_S + 10), ) - feedback = { - "error_stack": "", - "error": "Job unexpectedly terminated.", - "error_origin": "worker_wrapper", - "container_exit_code": -2, - } - for job in jobs: capture_message( f'Job "{job.id}" was with status "{job.status}", but worker container no longer exists. Job unexpectedly terminated.' @@ -70,19 +63,29 @@ def do(self): if job.type == Job.Type.DELTA_APPLY: ApplyJob.objects.get(id=job.id).deltas_to_apply.update( last_status=Delta.Status.ERROR, - last_feedback=feedback, + last_feedback=None, + last_modified_pk=None, last_apply_attempt_at=job.started_at, last_apply_attempt_by=job.created_by, ) ApplyJobDelta.objects.filter( apply_job_id=job.id, - ).update(status=Delta.Status.ERROR, feedback=feedback) + ).update( + status=Delta.Status.ERROR, + feedback=None, + modified_pk=None, + ) jobs.update( status=Job.Status.FAILED, finished_at=timezone.now(), - feedback=feedback, + feedback={ + "error_stack": "", + "error": "Job unexpectedly terminated.", + "error_origin": "worker_wrapper", + "container_exit_code": -2, + }, output="Job unexpectedly terminated.", ) diff --git a/docker-app/worker_wrapper/wrapper.py b/docker-app/worker_wrapper/wrapper.py index 78a4616b8..e6189b674 100644 --- a/docker-app/worker_wrapper/wrapper.py +++ b/docker-app/worker_wrapper/wrapper.py @@ -156,9 +156,22 @@ def run(self): feedback["error_origin"] = "container" feedback["error_stack"] = "" - logger.info( - "Set job status to `failed` due to being killed by the docker engine.", - ) + try: + logger.info( + "Set job status to `failed` due to being killed by the docker engine.", + ) + + self.job.output = output.decode("utf-8") + self.job.feedback = feedback + self.job.status = Job.Status.FAILED + self.job.save(update_fields=["output", "feedback", "status"]) + except Exception as err: + logger.error( + "Failed to update job status, probably does not exist in the database.", + exc_info=err, + ) + # No further action required, probably received by wrapper's autoclean mechanism when the `Project` is deleted + return elif exit_code == TIMEOUT_ERROR_EXIT_CODE: feedback["error"] = "Worker timeout error." feedback["error_type"] = "TIMEOUT" @@ -533,7 +546,8 @@ def after_docker_exception(self) -> None: id__in=self.delta_ids, ).update( last_status=Delta.Status.ERROR, - last_feedback=self.job.feedback, + last_feedback=None, + last_modified_pk=None, last_apply_attempt_at=self.job.started_at, last_apply_attempt_by=self.job.created_by, ) @@ -543,7 +557,8 @@ def after_docker_exception(self) -> None: delta_id__in=self.delta_ids, ).update( status=Delta.Status.ERROR, - feedback=self.job.feedback, + feedback=None, + modified_pk=None, ) From dc965000e02f3288c3b09bf7735dde66fb2151a2 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Mon, 27 May 2024 13:36:37 +0300 Subject: [PATCH 26/32] Fix the failing `worker_wrapper` and tests by `requests==2.32.0` The CI tests returned error messages like: > Exception: Processing did no finish, did the worker hang ? This was cause because the `worker_wrapper` gets on start: > docker.errors.DockerException: Error while fetching server API version: Not supported URL scheme http+docker NOTE since we are using multi-stage builds in docker, the `requests` dependency was forced on the previous layer than the `worker_wrapper` itself. --- docker-app/requirements.txt | 2 +- docker-app/requirements_worker_wrapper.txt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docker-app/requirements.txt b/docker-app/requirements.txt index 8773064be..203da289f 100644 --- a/docker-app/requirements.txt +++ b/docker-app/requirements.txt @@ -68,7 +68,7 @@ pyrsistent==0.19.3 python-dateutil==2.8.2 python3-openid==3.2.0 pytz==2023.3 -requests==2.32.0 +requests==2.32.2 requests-oauthlib==1.3.1 ruamel.yaml==0.17.26 ruamel.yaml.clib==0.2.7 diff --git a/docker-app/requirements_worker_wrapper.txt b/docker-app/requirements_worker_wrapper.txt index d224175c7..ae0988ff3 100644 --- a/docker-app/requirements_worker_wrapper.txt +++ b/docker-app/requirements_worker_wrapper.txt @@ -1,2 +1,2 @@ -docker==4.2.2 -tenacity==8.1.0 +docker==7.1.0 +tenacity==8.3.0 From b93f21b7a269fcffed960765d284c16d5d5bedd4 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Mon, 27 May 2024 13:36:55 +0300 Subject: [PATCH 27/32] Fix typo "requrested" to "requested" --- docker-app/qfieldcloud/core/utils2/storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-app/qfieldcloud/core/utils2/storage.py b/docker-app/qfieldcloud/core/utils2/storage.py index ca3cc0d59..76f5a12d9 100644 --- a/docker-app/qfieldcloud/core/utils2/storage.py +++ b/docker-app/qfieldcloud/core/utils2/storage.py @@ -629,7 +629,7 @@ def get_project_file_storage_in_bytes(project_id: str) -> int: total_bytes = 0 prefix = f"projects/{project_id}/files/" - logger.info(f"Project file storage size requrested for {project_id=}") + logger.info(f"Project file storage size requested for {project_id=}") if not re.match(r"^projects/[\w]{8}(-[\w]{4}){3}-[\w]{12}/files/$", prefix): raise RuntimeError( From 06cdc9b8a90a1adfc1d6ffd08fd8b42d51d25679 Mon Sep 17 00:00:00 2001 From: Michael Schmuki Date: Mon, 27 May 2024 14:12:59 +0200 Subject: [PATCH 28/32] Execute "after_docker_exception" on DOCKER_ENGINE_SIGKILL (this ensures status of related deltas gets updated) --- docker-app/worker_wrapper/wrapper.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/docker-app/worker_wrapper/wrapper.py b/docker-app/worker_wrapper/wrapper.py index 3c727de48..6ca48a9ee 100644 --- a/docker-app/worker_wrapper/wrapper.py +++ b/docker-app/worker_wrapper/wrapper.py @@ -170,8 +170,6 @@ def run(self): "Failed to update job status, probably does not exist in the database.", exc_info=err, ) - # No further action required, probably received by wrapper's autoclean mechanism when the `Project` is deleted - return elif exit_code == TIMEOUT_ERROR_EXIT_CODE: feedback["error"] = "Worker timeout error." feedback["error_type"] = "TIMEOUT" From 46a29efeff5c9c014608c5b652c5258424e66225 Mon Sep 17 00:00:00 2001 From: Michael Schmuki Date: Mon, 27 May 2024 14:40:26 +0200 Subject: [PATCH 29/32] Create a link to the project jobs in the project admin view (#950) --- docker-app/qfieldcloud/core/admin.py | 3 +++ .../core/templates/admin/project_change_form.html | 10 ++++++++++ 2 files changed, 13 insertions(+) create mode 100644 docker-app/qfieldcloud/core/templates/admin/project_change_form.html diff --git a/docker-app/qfieldcloud/core/admin.py b/docker-app/qfieldcloud/core/admin.py index 91636c340..f9ea67476 100644 --- a/docker-app/qfieldcloud/core/admin.py +++ b/docker-app/qfieldcloud/core/admin.py @@ -652,6 +652,8 @@ class ProjectAdmin(QFieldCloudModelAdmin): ordering = ("-updated_at",) + change_form_template = "admin/project_change_form.html" + def get_form(self, *args, **kwargs): help_texts = { "file_storage_bytes": _( @@ -778,6 +780,7 @@ class JobAdmin(QFieldCloudModelAdmin): "project__name__iexact", "project__owner__username__iexact", "id", + "project__id__iexact", ) readonly_fields = ( "project", diff --git a/docker-app/qfieldcloud/core/templates/admin/project_change_form.html b/docker-app/qfieldcloud/core/templates/admin/project_change_form.html new file mode 100644 index 000000000..cb0194217 --- /dev/null +++ b/docker-app/qfieldcloud/core/templates/admin/project_change_form.html @@ -0,0 +1,10 @@ +{% extends 'admin/change_form.html' %} +{% load i18n %} + +{% block submit_buttons_bottom %} + {{ block.super }} + + +{% endblock %} From 1e9868a87387a93e381a606f1017bf72bfa01bcd Mon Sep 17 00:00:00 2001 From: Michael Schmuki Date: Mon, 27 May 2024 14:41:05 +0200 Subject: [PATCH 30/32] Add Secrets to the admin page (#939) * Add Secrets to the admin page * Apply suggestions from code review Co-authored-by: Ivan Ivanov * Fix syntax errors * Switch to regex based env var validation * Make the project field an autocomplete field (fixes performance of add form) * Fix linting errors * Remove regex based value validation * Add validate_pg_service_conf * Ensure project is the first field in the ProjectSecretForm * Apply suggestions from code review Co-authored-by: Ivan Ivanov * Apply suggestions from code review Co-authored-by: Ivan Ivanov * Rename secret util to pg_service_file * Fixes from review and ProjectSecretInline * Prefer using `admin` decorator * Hack a way to add extra HTML in `InlineTabular` admin templates * Secret Admin: Fix "Created by" column name * Fix linting errors --------- Co-authored-by: Ivan Ivanov Co-authored-by: Ivan Ivanov --- docker-app/qfieldcloud/core/admin.py | 146 +++++++++++++++++- .../admin/edit_inline/tabular_customized.html | 5 + .../admin/edit_inline/tabular_extended.html | 1 + .../core/utils2/pg_service_file.py | 24 +++ 4 files changed, 173 insertions(+), 3 deletions(-) create mode 100644 docker-app/qfieldcloud/core/templates/admin/edit_inline/tabular_customized.html create mode 100644 docker-app/qfieldcloud/core/templates/admin/edit_inline/tabular_extended.html create mode 100644 docker-app/qfieldcloud/core/utils2/pg_service_file.py diff --git a/docker-app/qfieldcloud/core/admin.py b/docker-app/qfieldcloud/core/admin.py index f9ea67476..ff6bd9a2f 100644 --- a/docker-app/qfieldcloud/core/admin.py +++ b/docker-app/qfieldcloud/core/admin.py @@ -19,7 +19,7 @@ from django.contrib import admin, messages from django.contrib.admin.templatetags.admin_urls import admin_urlname from django.contrib.admin.views.main import ChangeList -from django.core.exceptions import PermissionDenied +from django.core.exceptions import PermissionDenied, ValidationError from django.db.models import Q, QuerySet from django.db.models.fields.json import JSONField from django.db.models.functions import Lower @@ -31,6 +31,7 @@ from django.urls import path, reverse from django.utils.decorators import method_decorator from django.utils.html import escape, format_html +from django.utils.http import urlencode from django.utils.safestring import SafeText from django.utils.translation import gettext_lazy as _ from django.views.decorators.cache import never_cache @@ -48,6 +49,7 @@ Person, Project, ProjectCollaborator, + Secret, Team, TeamMember, User, @@ -55,7 +57,7 @@ ) from qfieldcloud.core.paginators import LargeTablePaginator from qfieldcloud.core.templatetags.filters import filesizeformat10 -from qfieldcloud.core.utils2 import delta_utils, jobs +from qfieldcloud.core.utils2 import delta_utils, jobs, pg_service_file from rest_framework.authtoken.models import TokenProxy admin.site.unregister(LogEntry) @@ -115,6 +117,15 @@ def has_delete_permission(self, request, obj=None): return super().has_delete_permission(request, obj) +class QFieldCloudInlineAdmin(admin.TabularInline): + template = "admin/edit_inline/tabular_customized.html" + + def get_formset(self, request, obj=None, **kwargs): + self.parent_obj = obj + + return super().get_formset(request, obj, **kwargs) + + def admin_urlname_by_obj(value, arg): if isinstance(value, User): if value.is_person: @@ -584,6 +595,134 @@ def queryset(self, request, queryset): return queryset.filter(owner__type=value) +class ProjectSecretForm(ModelForm): + class Meta: + model = Secret + fields = ("project", "name", "type", "value", "created_by") + + name = fields.CharField(widget=widgets.TextInput) + value = fields.CharField(widget=widgets.Textarea) + + def get_initial_for_field(self, field, field_name): + if self.instance.pk and field_name == "value": + return "" + return super().get_initial_for_field(field, field_name) + + def clean(self): + cleaned_data = super().clean() + + if self.instance.pk: + type = self.instance.type + else: + type = cleaned_data.get("type") + if type == Secret.Type.PGSERVICE: + # validate the pg_service.conf + value = cleaned_data.get("value") + if value: + try: + pg_service_file.validate_pg_service_conf(value) + except ValidationError as err: + raise ValidationError({"value": err.message}) + + # ensure name with PGSERVICE_SECRET_NAME_PREFIX + name = cleaned_data.get("name") + if name and not name.startswith( + pg_service_file.PGSERVICE_SECRET_NAME_PREFIX + ): + cleaned_data[ + "name" + ] = f"{pg_service_file.PGSERVICE_SECRET_NAME_PREFIX}{name}" + + return cleaned_data + + +class SecretAdmin(QFieldCloudModelAdmin): + model = Secret + form = ProjectSecretForm + fields = ("project", "name", "type", "value", "created_by") + readonly_fields = ("created_by",) + list_display = ("name", "type", "created_by__link", "project__name") + autocomplete_fields = ("project",) + + search_fields = ( + "name__icontains", + "project__name__icontains", + ) + + @admin.display(ordering="created_by") + def created_by__link(self, instance): + return model_admin_url(instance.created_by) + + created_by__link.short_description = "Created by" # type: ignore + + @admin.display(ordering="project__name") + def project__name(self, instance): + return model_admin_url(instance.project, instance.project.name) + + def get_readonly_fields(self, request, obj=None): + readonly_fields = super().get_readonly_fields(request, obj) + + if obj: + return (*readonly_fields, "name", "type", "project") + + return readonly_fields + + def save_model(self, request, obj, form, change): + # only set created_by during the first save + if not change: + obj.created_by = request.user + super().save_model(request, obj, form, change) + + def get_changeform_initial_data(self, request): + project_id = request.GET.get("project_id") + + if project_id: + project = Project.objects.get(id=project_id) + else: + project = None + + return {"project": project} + + +class ProjectSecretInline(QFieldCloudInlineAdmin): + model = Secret + fields = ("link_to_secret", "type", "created_by") + readonly_fields = ("link_to_secret",) + max_num = 0 + extra = 0 + + @admin.display(description=_("Name")) + def link_to_secret(self, obj): + url = reverse("admin:core_secret_change", args=[obj.pk]) + return format_html('{}', url, obj.name) + + def has_add_permission(self, request, obj=None): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False + + @property + def bottom_html(self): + if self.parent_obj: + return format_html( + """ + + + {text} + + """, + url=reverse("admin:core_secret_add"), + query_params=urlencode({"project_id": self.parent_obj.pk}), + text="Add Secret", + ) + else: + return "" + + class ProjectForm(ModelForm): project_files = fields.CharField( disabled=True, required=False, widget=ProjectFilesWidget @@ -642,7 +781,7 @@ class ProjectAdmin(QFieldCloudModelAdmin): "data_last_packaged_at", "project_details__pre", ) - inlines = (ProjectCollaboratorInline,) + inlines = (ProjectCollaboratorInline, ProjectSecretInline) search_fields = ( "id", "name__icontains", @@ -1368,6 +1507,7 @@ class LogEntryAdmin( admin.site.register(Organization, OrganizationAdmin) admin.site.register(Team, TeamAdmin) admin.site.register(Project, ProjectAdmin) +admin.site.register(Secret, SecretAdmin) admin.site.register(Delta, DeltaAdmin) admin.site.register(Job, JobAdmin) admin.site.register(Geodb, GeodbAdmin) diff --git a/docker-app/qfieldcloud/core/templates/admin/edit_inline/tabular_customized.html b/docker-app/qfieldcloud/core/templates/admin/edit_inline/tabular_customized.html new file mode 100644 index 000000000..e678f8459 --- /dev/null +++ b/docker-app/qfieldcloud/core/templates/admin/edit_inline/tabular_customized.html @@ -0,0 +1,5 @@ +{% if qfc_admin_inline_included != 1 %} + {% include "admin/edit_inline/tabular_extended.html" with qfc_admin_inline_included=1 %} +{% endif %} + +{{ fieldset.opts.bottom_html }} diff --git a/docker-app/qfieldcloud/core/templates/admin/edit_inline/tabular_extended.html b/docker-app/qfieldcloud/core/templates/admin/edit_inline/tabular_extended.html new file mode 100644 index 000000000..012e71e9e --- /dev/null +++ b/docker-app/qfieldcloud/core/templates/admin/edit_inline/tabular_extended.html @@ -0,0 +1 @@ +{% extends "admin/edit_inline/tabular.html" %} diff --git a/docker-app/qfieldcloud/core/utils2/pg_service_file.py b/docker-app/qfieldcloud/core/utils2/pg_service_file.py new file mode 100644 index 000000000..609c3cf39 --- /dev/null +++ b/docker-app/qfieldcloud/core/utils2/pg_service_file.py @@ -0,0 +1,24 @@ +import configparser +import io + +from django.core.exceptions import ValidationError +from django.utils.translation import gettext as _ + +PGSERVICE_SECRET_NAME_PREFIX = "PG_SERVICE_" + + +def validate_pg_service_conf(value: str) -> None: + """Checks if a string is a valid `pg_service.conf` file contents, otherwise throws a `ValueError`""" + try: + buffer = io.StringIO(value) + config = configparser.ConfigParser() + config.readfp(buffer) + + if len(config.sections()) != 1: + raise ValidationError( + _("The `.pg_service.conf` must have exactly one service definition.") + ) + except ValidationError as err: + raise err + except Exception: + raise ValidationError(_("Failed to parse the `.pg_service.conf` file.")) From 190cb6af54d1f5e84dc936133cf2116710e6551f Mon Sep 17 00:00:00 2001 From: Michael Schmuki Date: Mon, 27 May 2024 15:52:40 +0200 Subject: [PATCH 31/32] Ensure no "after_docker_exception" when a SIGKILLed job doesn't exist in the db (anymore) --- docker-app/worker_wrapper/wrapper.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/docker-app/worker_wrapper/wrapper.py b/docker-app/worker_wrapper/wrapper.py index 6ca48a9ee..4f0108226 100644 --- a/docker-app/worker_wrapper/wrapper.py +++ b/docker-app/worker_wrapper/wrapper.py @@ -157,19 +157,14 @@ def run(self): feedback["error_stack"] = "" try: - logger.info( - "Set job status to `failed` due to being killed by the docker engine.", - ) - - self.job.output = output.decode("utf-8") - self.job.feedback = feedback - self.job.status = Job.Status.FAILED - self.job.save(update_fields=["output", "feedback", "status"]) + self.job.refresh_from_db() except Exception as err: logger.error( "Failed to update job status, probably does not exist in the database.", exc_info=err, ) + # No further action required, probably received by wrapper's autoclean mechanism when the `Project` is deleted + return elif exit_code == TIMEOUT_ERROR_EXIT_CODE: feedback["error"] = "Worker timeout error." feedback["error_type"] = "TIMEOUT" From ba9c5c2e94ada61b1e3acb9677f36c3cc7cb04c0 Mon Sep 17 00:00:00 2001 From: Michael Schmuki Date: Mon, 27 May 2024 15:53:21 +0200 Subject: [PATCH 32/32] Use @admin.display desc for SecretAdmin.created_by__link (#954) --- docker-app/qfieldcloud/core/admin.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docker-app/qfieldcloud/core/admin.py b/docker-app/qfieldcloud/core/admin.py index ff6bd9a2f..74874c3e8 100644 --- a/docker-app/qfieldcloud/core/admin.py +++ b/docker-app/qfieldcloud/core/admin.py @@ -649,12 +649,10 @@ class SecretAdmin(QFieldCloudModelAdmin): "project__name__icontains", ) - @admin.display(ordering="created_by") + @admin.display(ordering="created_by", description=_("Created by")) def created_by__link(self, instance): return model_admin_url(instance.created_by) - created_by__link.short_description = "Created by" # type: ignore - @admin.display(ordering="project__name") def project__name(self, instance): return model_admin_url(instance.project, instance.project.name)