From 661bd7da21e5a7b6fd7447eb6a92e7a831173978 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jun 2023 12:01:31 +0200 Subject: [PATCH 001/111] Bump deprecated from 1.2.13 to 1.2.14 (#11128) * Bump deprecated from 1.2.13 to 1.2.14 Bumps [deprecated](https://github.com/tantale/deprecated) from 1.2.13 to 1.2.14. - [Release notes](https://github.com/tantale/deprecated/releases) - [Changelog](https://github.com/tantale/deprecated/blob/master/CHANGELOG.rst) - [Commits](https://github.com/tantale/deprecated/compare/v1.2.13...v1.2.14) --- updated-dependencies: - dependency-name: deprecated dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index a911b3db9e2..6b27ee9f92d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,7 +18,7 @@ celery==5.2.7 kombu==5.2.4 vine==5.0.0 tqdm==4.65.0 -Deprecated==1.2.13 +Deprecated==1.2.14 wrapt==1.15.0 jsonschema==4.17.3 zipstream-new==1.1.8 diff --git a/setup.cfg b/setup.cfg index 46cefd95511..d8deda410a9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -44,7 +44,7 @@ install_requires = kombu==5.2.4 vine==5.0.0 tqdm==4.65.0 - Deprecated==1.2.13 + Deprecated==1.2.14 wrapt==1.15.0 jsonschema==4.17.3 zipstream-new==1.1.8 From cdfcbc960f26433339a6eeb56751e5ddd3a04c69 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jun 2023 12:01:57 +0200 Subject: [PATCH 002/111] Bump django-cors-headers from 4.0.0 to 4.1.0 (#11177) * Bump django-cors-headers from 4.0.0 to 4.1.0 Bumps [django-cors-headers](https://github.com/adamchainz/django-cors-headers) from 4.0.0 to 4.1.0. - [Changelog](https://github.com/adamchainz/django-cors-headers/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamchainz/django-cors-headers/compare/4.0.0...4.1.0) --- updated-dependencies: - dependency-name: django-cors-headers dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 6b27ee9f92d..6d3fa4788d7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -134,7 +134,7 @@ sherlock==0.4.1 # required by monitoring psutil==5.9.5 -django-cors-headers==4.0.0 +django-cors-headers==4.1.0 user-agents django-user-agents xmljson diff --git a/setup.cfg b/setup.cfg index d8deda410a9..7d989d43bf5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -159,7 +159,7 @@ install_requires = # required by monitoring psutil==5.9.5 - django-cors-headers==4.0.0 + django-cors-headers==4.1.0 user-agents django-user-agents xmljson From 89ba02a54237cc995ff40d02ada4fd2450a4bdc5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jun 2023 12:02:41 +0200 Subject: [PATCH 003/111] Bump django-select2 from 8.1.1 to 8.1.2 (#11127) * Bump django-select2 from 8.1.1 to 8.1.2 Bumps [django-select2](https://github.com/codingjoe/django-select2) from 8.1.1 to 8.1.2. - [Release notes](https://github.com/codingjoe/django-select2/releases) - [Commits](https://github.com/codingjoe/django-select2/compare/8.1.1...8.1.2) --- updated-dependencies: - dependency-name: django-select2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 6d3fa4788d7..46958d03b1c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -67,7 +67,7 @@ numpy==1.24.* # Django Apps dj-database-url==2.0.0 dj-pagination==2.5.0 -django-select2==8.1.1 +django-select2==8.1.2 django-floppyforms<1.10.0 django-forms-bootstrap<=3.1.0 django-autocomplete-light==3.5.1 diff --git a/setup.cfg b/setup.cfg index 7d989d43bf5..4d72c0dc104 100644 --- a/setup.cfg +++ b/setup.cfg @@ -93,7 +93,7 @@ install_requires = # Django Apps dj-database-url==2.0.0 dj-pagination==2.5.0 - django-select2==8.1.1 + django-select2==8.1.2 django-floppyforms<1.10.0 django-forms-bootstrap<=3.1.0 django-autocomplete-light==3.5.1 From cfacef8a25d97ad3028538ff907e908eff548145 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jun 2023 12:05:09 +0200 Subject: [PATCH 004/111] Bump drf-spectacular from 0.26.2 to 0.26.3 (#11196) * Bump drf-spectacular from 0.26.2 to 0.26.3 Bumps [drf-spectacular](https://github.com/tfranzel/drf-spectacular) from 0.26.2 to 0.26.3. - [Release notes](https://github.com/tfranzel/drf-spectacular/releases) - [Changelog](https://github.com/tfranzel/drf-spectacular/blob/master/CHANGELOG.rst) - [Commits](https://github.com/tfranzel/drf-spectacular/compare/0.26.2...0.26.3) --- updated-dependencies: - dependency-name: drf-spectacular dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 46958d03b1c..3221c3d01f2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -80,7 +80,7 @@ djangorestframework-gis==1.0 djangorestframework-guardian==0.3.0 drf-extensions==0.7.1 drf-writable-nested==0.7.0 -drf-spectacular==0.26.2 +drf-spectacular==0.26.3 dynamic-rest==2.1.2 Markdown==3.4.3 diff --git a/setup.cfg b/setup.cfg index 4d72c0dc104..3eaab96ce80 100644 --- a/setup.cfg +++ b/setup.cfg @@ -106,7 +106,7 @@ install_requires = djangorestframework-guardian==0.3.0 drf-extensions==0.7.1 drf-writable-nested==0.7.0 - drf-spectacular==0.26.2 + drf-spectacular==0.26.3 dynamic-rest==2.1.2 Markdown==3.4.3 From 1a09fd032d5d5198000218cc9c70ae48ad1d6884 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jun 2023 12:07:39 +0200 Subject: [PATCH 005/111] Bump coverage from 7.2.5 to 7.2.7 (#11144) * Bump coverage from 7.2.5 to 7.2.7 Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.2.5 to 7.2.7. - [Release notes](https://github.com/nedbat/coveragepy/releases) - [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst) - [Commits](https://github.com/nedbat/coveragepy/compare/7.2.5...7.2.7) --- updated-dependencies: - dependency-name: coverage dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 3221c3d01f2..57faa8cb5ea 100644 --- a/requirements.txt +++ b/requirements.txt @@ -150,7 +150,7 @@ docker==6.1.2 invoke==2.1.2 # tests -coverage==7.2.5 +coverage==7.2.7 requests-toolbelt==1.0.0 flake8==6.0.0 black==23.3.0 diff --git a/setup.cfg b/setup.cfg index 3eaab96ce80..4e676f6761e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -175,7 +175,7 @@ install_requires = invoke==2.1.2 # tests - coverage==7.2.5 + coverage==7.2.7 requests-toolbelt==1.0.0 flake8==6.0.0 black==23.3.0 From 6000205c41a631b6a85582b6f3797b99b58c021f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jun 2023 12:08:05 +0200 Subject: [PATCH 006/111] Bump docker from 6.1.2 to 6.1.3 (#11142) * Bump docker from 6.1.2 to 6.1.3 Bumps [docker](https://github.com/docker/docker-py) from 6.1.2 to 6.1.3. - [Release notes](https://github.com/docker/docker-py/releases) - [Commits](https://github.com/docker/docker-py/compare/6.1.2...6.1.3) --- updated-dependencies: - dependency-name: docker dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 57faa8cb5ea..b2764ed97a4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -146,7 +146,7 @@ pycountry uWSGI==2.0.21 gunicorn==20.1.0 ipython==8.13.2 -docker==6.1.2 +docker==6.1.3 invoke==2.1.2 # tests diff --git a/setup.cfg b/setup.cfg index 4e676f6761e..6479b690cb6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -171,7 +171,7 @@ install_requires = uWSGI==2.0.21 gunicorn==20.1.0 ipython==8.13.2 - docker==6.1.2 + docker==6.1.3 invoke==2.1.2 # tests From 0e19108f5d3428545431b16a8079658cf7caebfc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jun 2023 12:29:14 +0200 Subject: [PATCH 007/111] Bump pathvalidate from 2.5.2 to 3.0.0 (#11125) * Bump pathvalidate from 2.5.2 to 3.0.0 Bumps [pathvalidate](https://github.com/thombashi/pathvalidate) from 2.5.2 to 3.0.0. - [Release notes](https://github.com/thombashi/pathvalidate/releases) - [Commits](https://github.com/thombashi/pathvalidate/compare/v2.5.2...v3.0.0) --- updated-dependencies: - dependency-name: pathvalidate dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index b2764ed97a4..cc517bee3d1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -26,7 +26,7 @@ schema==0.7.5 rdflib==6.3.2 smart_open==6.3.0 PyMuPDF==1.22.3 -pathvalidate==2.5.2 +pathvalidate==3.0.0 # Django Apps django-allauth==0.54.0 diff --git a/setup.cfg b/setup.cfg index 6479b690cb6..af6dbcd1714 100644 --- a/setup.cfg +++ b/setup.cfg @@ -52,7 +52,7 @@ install_requires = rdflib==6.3.2 smart_open==6.3.0 PyMuPDF==1.22.3 - pathvalidate==2.5.2 + pathvalidate==3.0.0 # Django Apps django-allauth==0.54.0 From d6069c2c9a46c19204660146d6edde971b134d06 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jun 2023 12:30:13 +0200 Subject: [PATCH 008/111] Bump mistune from 2.0.5 to 3.0.1 (#11158) * Bump mistune from 2.0.5 to 3.0.1 Bumps [mistune](https://github.com/lepture/mistune) from 2.0.5 to 3.0.1. - [Release notes](https://github.com/lepture/mistune/releases) - [Changelog](https://github.com/lepture/mistune/blob/master/docs/changes.rst) - [Commits](https://github.com/lepture/mistune/compare/v2.0.5...v3.0.1) --- updated-dependencies: - dependency-name: mistune dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index cc517bee3d1..e4b9200b14b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -170,7 +170,7 @@ selenium-requests==2.0.3 webdriver_manager==3.8.6 # Security and audit -mistune==2.0.5 +mistune==3.0.1 wandb==0.15.3 protobuf==3.20.3 mako==1.2.4 diff --git a/setup.cfg b/setup.cfg index af6dbcd1714..f372baa9f5d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -195,7 +195,7 @@ install_requires = webdriver_manager==3.8.6 # Security and audit - mistune==2.0.5 + mistune==3.0.1 wandb==0.15.3 protobuf==3.20.3 mako==1.2.4 From 1be6ce462905a8a01cde686369683c4dbeefc712 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jun 2023 12:31:17 +0200 Subject: [PATCH 009/111] Bump pyopenssl from 23.1.1 to 23.2.0 (#11141) * Bump pyopenssl from 23.1.1 to 23.2.0 Bumps [pyopenssl](https://github.com/pyca/pyopenssl) from 23.1.1 to 23.2.0. - [Changelog](https://github.com/pyca/pyopenssl/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/pyopenssl/compare/23.1.1...23.2.0) --- updated-dependencies: - dependency-name: pyopenssl dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index e4b9200b14b..9f77d2541a9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -49,7 +49,7 @@ django-uuid-upload-path==1.0.0 django-widget-tweaks==1.4.12 django-sequences==2.7 oauthlib==3.2.2 -pyopenssl==23.1.1 +pyopenssl==23.2.0 pyjwt==2.7.0 # geopython dependencies diff --git a/setup.cfg b/setup.cfg index f372baa9f5d..4bebe0a333e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -75,7 +75,7 @@ install_requires = django-widget-tweaks==1.4.12 django-sequences==2.7 oauthlib==3.2.2 - pyopenssl==23.1.1 + pyopenssl==23.2.0 pyjwt==2.7.0 # geopython dependencies From 46feab2272fa050e03573ca7a0b18f2abe654d82 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jun 2023 12:32:01 +0200 Subject: [PATCH 010/111] Update numpy requirement from ==1.24.* to ==1.25.* (#11176) * Update numpy requirement from ==1.24.* to ==1.25.* Updates the requirements on [numpy](https://github.com/numpy/numpy) to permit the latest version. - [Release notes](https://github.com/numpy/numpy/releases) - [Changelog](https://github.com/numpy/numpy/blob/main/doc/RELEASE_WALKTHROUGH.rst) - [Commits](https://github.com/numpy/numpy/compare/v1.24.0rc1...v1.25.0) --- updated-dependencies: - dependency-name: numpy dependency-type: direct:production ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 9f77d2541a9..6254a0fd423 100644 --- a/requirements.txt +++ b/requirements.txt @@ -60,7 +60,7 @@ SQLAlchemy==2.0.15 # required by PyCSW Shapely==1.8.5.post1 mercantile==1.2.1 geoip2==4.7.0 -numpy==1.24.* +numpy==1.25.* # # Apps with packages provided in GeoNode's PPA on Launchpad. diff --git a/setup.cfg b/setup.cfg index 4bebe0a333e..d53cee5d1e4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -86,7 +86,7 @@ install_requires = Shapely==1.8.5.post1 mercantile==1.2.1 geoip2==4.7.0 - numpy==1.24.* + numpy==1.25.* # # Apps with packages provided in GeoNode's PPA on Launchpad. From c271e5049d30f5b9ec703b0bc01a559d6f6cc920 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jun 2023 12:36:21 +0200 Subject: [PATCH 011/111] Update pyproj requirement from <3.6.0 to <3.7.0 (#11179) * Update pyproj requirement from <3.6.0 to <3.7.0 Updates the requirements on [pyproj](https://github.com/pyproj4/pyproj) to permit the latest version. - [Release notes](https://github.com/pyproj4/pyproj/releases) - [Changelog](https://github.com/pyproj4/pyproj/blob/main/docs/history.rst) - [Commits](https://github.com/pyproj4/pyproj/compare/v1.9.4rel...3.6.0) --- updated-dependencies: - dependency-name: pyproj dependency-type: direct:production ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 6254a0fd423..6aa39029b90 100644 --- a/requirements.txt +++ b/requirements.txt @@ -53,7 +53,7 @@ pyopenssl==23.2.0 pyjwt==2.7.0 # geopython dependencies -pyproj<3.6.0 +pyproj<3.7.0 OWSLib==0.29.2 pycsw==2.6.1 SQLAlchemy==2.0.15 # required by PyCSW diff --git a/setup.cfg b/setup.cfg index d53cee5d1e4..d6d3874483b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -79,7 +79,7 @@ install_requires = pyjwt==2.7.0 # geopython dependencies - pyproj<3.6.0 + pyproj<3.7.0 OWSLib==0.29.2 pycsw==2.6.1 SQLAlchemy==2.0.15 # required by PyCSW From 4f55b1c2d201f070810f52e09ccaed1e51491a43 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jun 2023 13:50:12 +0200 Subject: [PATCH 012/111] Bump ipython from 8.13.2 to 8.14.0 (#11146) * Bump ipython from 8.13.2 to 8.14.0 Bumps [ipython](https://github.com/ipython/ipython) from 8.13.2 to 8.14.0. - [Release notes](https://github.com/ipython/ipython/releases) - [Commits](https://github.com/ipython/ipython/compare/8.13.2...8.14.0) --- updated-dependencies: - dependency-name: ipython dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 6aa39029b90..68249b4fedc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -145,7 +145,7 @@ pycountry # production uWSGI==2.0.21 gunicorn==20.1.0 -ipython==8.13.2 +ipython==8.14.0 docker==6.1.3 invoke==2.1.2 diff --git a/setup.cfg b/setup.cfg index d6d3874483b..cfa7855b47f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -170,7 +170,7 @@ install_requires = # production uWSGI==2.0.21 gunicorn==20.1.0 - ipython==8.13.2 + ipython==8.14.0 docker==6.1.3 invoke==2.1.2 From 0e56e76c0afbba0e607f4fe1ff841e120670ccca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jun 2023 13:51:33 +0200 Subject: [PATCH 013/111] Bump pytest from 7.3.1 to 7.4.0 (#11195) * Bump pytest from 7.3.1 to 7.4.0 Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.3.1 to 7.4.0. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/7.3.1...7.4.0) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 68249b4fedc..50856965940 100644 --- a/requirements.txt +++ b/requirements.txt @@ -154,7 +154,7 @@ coverage==7.2.7 requests-toolbelt==1.0.0 flake8==6.0.0 black==23.3.0 -pytest==7.3.1 +pytest==7.4.0 pytest-bdd==6.1.1 splinter==0.19.0 pytest-splinter==3.3.2 diff --git a/setup.cfg b/setup.cfg index cfa7855b47f..132df60ac06 100644 --- a/setup.cfg +++ b/setup.cfg @@ -179,7 +179,7 @@ install_requires = requests-toolbelt==1.0.0 flake8==6.0.0 black==23.3.0 - pytest==7.3.1 + pytest==7.4.0 pytest-bdd==6.1.1 splinter==0.19.0 pytest-splinter==3.3.2 From 1327ca71fd8e20e7ac493e1beddadd6efdfe0bcf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jun 2023 13:51:59 +0200 Subject: [PATCH 014/111] Bump whitenoise from 6.4.0 to 6.5.0 (#11181) * Bump whitenoise from 6.4.0 to 6.5.0 Bumps [whitenoise](https://github.com/evansd/whitenoise) from 6.4.0 to 6.5.0. - [Changelog](https://github.com/evansd/whitenoise/blob/main/docs/changelog.rst) - [Commits](https://github.com/evansd/whitenoise/compare/6.4.0...6.5.0) --- updated-dependencies: - dependency-name: whitenoise dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 50856965940..d5f84d2dfe3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -117,7 +117,7 @@ boto3==1.26.137 # Django Caches python-memcached<=1.59 -whitenoise==6.4.0 +whitenoise==6.5.0 Brotli==1.0.9 # Contribs diff --git a/setup.cfg b/setup.cfg index 132df60ac06..7ef6cdcd078 100644 --- a/setup.cfg +++ b/setup.cfg @@ -142,7 +142,7 @@ install_requires = # Django Caches python-memcached<=1.59 - whitenoise==6.4.0 + whitenoise==6.5.0 Brotli==1.0.9 # Contribs From 73130a5e2dc623cb9f1bdb5042bed1dccc0fe8ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jun 2023 13:52:19 +0200 Subject: [PATCH 015/111] Bump sqlalchemy from 2.0.15 to 2.0.17 (#11193) * Bump sqlalchemy from 2.0.15 to 2.0.17 Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 2.0.15 to 2.0.17. - [Release notes](https://github.com/sqlalchemy/sqlalchemy/releases) - [Changelog](https://github.com/sqlalchemy/sqlalchemy/blob/main/CHANGES.rst) - [Commits](https://github.com/sqlalchemy/sqlalchemy/commits) --- updated-dependencies: - dependency-name: sqlalchemy dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index d5f84d2dfe3..f875b3b64ca 100644 --- a/requirements.txt +++ b/requirements.txt @@ -56,7 +56,7 @@ pyjwt==2.7.0 pyproj<3.7.0 OWSLib==0.29.2 pycsw==2.6.1 -SQLAlchemy==2.0.15 # required by PyCSW +SQLAlchemy==2.0.17 # required by PyCSW Shapely==1.8.5.post1 mercantile==1.2.1 geoip2==4.7.0 diff --git a/setup.cfg b/setup.cfg index 7ef6cdcd078..535c416998e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -82,7 +82,7 @@ install_requires = pyproj<3.7.0 OWSLib==0.29.2 pycsw==2.6.1 - SQLAlchemy==2.0.15 # required by PyCSW + SQLAlchemy==2.0.17 # required by PyCSW Shapely==1.8.5.post1 mercantile==1.2.1 geoip2==4.7.0 From 0808fbfa74c35cee3fc54b9fa142fec40e2b7992 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jun 2023 13:53:51 +0200 Subject: [PATCH 016/111] Bump pymupdf from 1.22.3 to 1.22.5 (#11197) * Bump pymupdf from 1.22.3 to 1.22.5 Bumps [pymupdf](https://github.com/pymupdf/pymupdf) from 1.22.3 to 1.22.5. - [Release notes](https://github.com/pymupdf/pymupdf/releases) - [Changelog](https://github.com/pymupdf/PyMuPDF/blob/main/changes.txt) - [Commits](https://github.com/pymupdf/pymupdf/compare/1.22.3...1.22.5) --- updated-dependencies: - dependency-name: pymupdf dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index f875b3b64ca..b6c77a2f555 100644 --- a/requirements.txt +++ b/requirements.txt @@ -25,7 +25,7 @@ zipstream-new==1.1.8 schema==0.7.5 rdflib==6.3.2 smart_open==6.3.0 -PyMuPDF==1.22.3 +PyMuPDF==1.22.5 pathvalidate==3.0.0 # Django Apps diff --git a/setup.cfg b/setup.cfg index 535c416998e..38835e416d2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -51,7 +51,7 @@ install_requires = schema==0.7.5 rdflib==6.3.2 smart_open==6.3.0 - PyMuPDF==1.22.3 + PyMuPDF==1.22.5 pathvalidate==3.0.0 # Django Apps From a12ce8f8a308591b2c6d1f89ea7a30aca8697080 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jun 2023 13:59:39 +0200 Subject: [PATCH 017/111] Update setuptools requirement from <67.9.0,>=59.1.1 to >=59.1.1,<68.1.0 (#11192) * Update setuptools requirement from <67.9.0,>=59.1.1 to >=59.1.1,<68.1.0 Updates the requirements on [setuptools](https://github.com/pypa/setuptools) to permit the latest version. - [Release notes](https://github.com/pypa/setuptools/releases) - [Changelog](https://github.com/pypa/setuptools/blob/main/NEWS.rst) - [Commits](https://github.com/pypa/setuptools/compare/v65.5.1...v68.0.0) --- updated-dependencies: - dependency-name: setuptools dependency-type: direct:production ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index b6c77a2f555..f40e35e6621 100644 --- a/requirements.txt +++ b/requirements.txt @@ -159,7 +159,7 @@ pytest-bdd==6.1.1 splinter==0.19.0 pytest-splinter==3.3.2 pytest-django==4.5.2 -setuptools>=59.1.1,<67.9.0 +setuptools>=59.1.1,<68.1.0 pip==23.1.2 Twisted==22.10.0 pixelmatch==0.3.0 diff --git a/setup.cfg b/setup.cfg index 38835e416d2..044abd669fa 100644 --- a/setup.cfg +++ b/setup.cfg @@ -184,7 +184,7 @@ install_requires = splinter==0.19.0 pytest-splinter==3.3.2 pytest-django==4.5.2 - setuptools>=59.1.1,<67.9.0 + setuptools>=59.1.1,<68.1.0 pip==23.1.2 Twisted==22.10.0 pixelmatch==0.3.0 From 200d62edfa7790a00cdabf3b07d72d3d7e6169bc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jun 2023 14:00:00 +0200 Subject: [PATCH 018/111] Bump dropbox from 11.36.0 to 11.36.2 (#11178) * Bump dropbox from 11.36.0 to 11.36.2 Bumps [dropbox](https://github.com/dropbox/dropbox-sdk-python) from 11.36.0 to 11.36.2. - [Release notes](https://github.com/dropbox/dropbox-sdk-python/releases) - [Commits](https://github.com/dropbox/dropbox-sdk-python/compare/v11.36.0...v11.36.2) --- updated-dependencies: - dependency-name: dropbox dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index f40e35e6621..21065525d4f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -110,7 +110,7 @@ django-bootstrap3-datetimepicker-2==2.8.3 # storage manager dependencies django-storages==1.13.2 -dropbox==11.36.0 +dropbox==11.36.2 google-cloud-storage==2.9.0 google-cloud-core==2.3.2 boto3==1.26.137 diff --git a/setup.cfg b/setup.cfg index 044abd669fa..b775835b8cc 100644 --- a/setup.cfg +++ b/setup.cfg @@ -135,7 +135,7 @@ install_requires = # storage manager dependencies django-storages==1.13.2 - dropbox==11.36.0 + dropbox==11.36.2 google-cloud-storage==2.9.0 google-cloud-core==2.3.2 boto3==1.26.137 From 57525248d4e8949d30be28c7ccf5020ea7c85f50 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jun 2023 14:00:27 +0200 Subject: [PATCH 019/111] Bump boto3 from 1.26.137 to 1.26.160 (#11194) * Bump boto3 from 1.26.137 to 1.26.160 Bumps [boto3](https://github.com/boto/boto3) from 1.26.137 to 1.26.160. - [Release notes](https://github.com/boto/boto3/releases) - [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/boto3/compare/1.26.137...1.26.160) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 21065525d4f..a4a51cce085 100644 --- a/requirements.txt +++ b/requirements.txt @@ -113,7 +113,7 @@ django-storages==1.13.2 dropbox==11.36.2 google-cloud-storage==2.9.0 google-cloud-core==2.3.2 -boto3==1.26.137 +boto3==1.26.160 # Django Caches python-memcached<=1.59 diff --git a/setup.cfg b/setup.cfg index b775835b8cc..c9385198996 100644 --- a/setup.cfg +++ b/setup.cfg @@ -138,7 +138,7 @@ install_requires = dropbox==11.36.2 google-cloud-storage==2.9.0 google-cloud-core==2.3.2 - boto3==1.26.137 + boto3==1.26.160 # Django Caches python-memcached<=1.59 From d5334dfedd0b17bcaedc31e534173f4756978e87 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jun 2023 14:48:30 +0200 Subject: [PATCH 020/111] Bump celery from 5.2.7 to 5.3.1 (#11180) * Bump celery from 5.2.7 to 5.3.1 Bumps [celery](https://github.com/celery/celery) from 5.2.7 to 5.3.1. - [Release notes](https://github.com/celery/celery/releases) - [Changelog](https://github.com/celery/celery/blob/main/Changelog.rst) - [Commits](https://github.com/celery/celery/compare/v5.2.7...v5.3.1) --- updated-dependencies: - dependency-name: celery dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" * - Align "setup.cfg" to "requirements.txt" * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 4 ++-- setup.cfg | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index a4a51cce085..a35d6e10545 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,8 +14,8 @@ urllib3==1.26.15 Paver==1.3.4 python-slugify==8.0.1 decorator==5.1.1 -celery==5.2.7 -kombu==5.2.4 +celery==5.3.1 +kombu==5.3.1 vine==5.0.0 tqdm==4.65.0 Deprecated==1.2.14 diff --git a/setup.cfg b/setup.cfg index c9385198996..682ab135d92 100644 --- a/setup.cfg +++ b/setup.cfg @@ -40,8 +40,8 @@ install_requires = Paver==1.3.4 python-slugify==8.0.1 decorator==5.1.1 - celery==5.2.7 - kombu==5.2.4 + celery==5.3.1 + kombu==5.3.1 vine==5.0.0 tqdm==4.65.0 Deprecated==1.2.14 From ea7fd7cbd4418111a7ccfab0656c404ad385a36c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jun 2023 14:48:50 +0200 Subject: [PATCH 021/111] Bump invoke from 2.1.2 to 2.1.3 (#11174) * Bump invoke from 2.1.2 to 2.1.3 Bumps [invoke](https://github.com/pyinvoke/invoke) from 2.1.2 to 2.1.3. - [Commits](https://github.com/pyinvoke/invoke/compare/2.1.2...2.1.3) --- updated-dependencies: - dependency-name: invoke dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index a35d6e10545..179ad47a106 100644 --- a/requirements.txt +++ b/requirements.txt @@ -147,7 +147,7 @@ uWSGI==2.0.21 gunicorn==20.1.0 ipython==8.14.0 docker==6.1.3 -invoke==2.1.2 +invoke==2.1.3 # tests coverage==7.2.7 diff --git a/setup.cfg b/setup.cfg index 682ab135d92..647745361fd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -172,7 +172,7 @@ install_requires = gunicorn==20.1.0 ipython==8.14.0 docker==6.1.3 - invoke==2.1.2 + invoke==2.1.3 # tests coverage==7.2.7 From 7954bf13f11d84cd4c2a6c5b11d57b97d37c850e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jun 2023 14:49:09 +0200 Subject: [PATCH 022/111] Bump wandb from 0.15.3 to 0.15.4 (#11163) * Bump wandb from 0.15.3 to 0.15.4 Bumps [wandb](https://github.com/wandb/wandb) from 0.15.3 to 0.15.4. - [Release notes](https://github.com/wandb/wandb/releases) - [Changelog](https://github.com/wandb/wandb/blob/v0.15.4/CHANGELOG.md) - [Commits](https://github.com/wandb/wandb/compare/v0.15.3...v0.15.4) --- updated-dependencies: - dependency-name: wandb dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 179ad47a106..9f96ca8f1c8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -171,7 +171,7 @@ webdriver_manager==3.8.6 # Security and audit mistune==3.0.1 -wandb==0.15.3 +wandb==0.15.4 protobuf==3.20.3 mako==1.2.4 certifi>=2022.12.7 # not directly required, pinned by Snyk to avoid a vulnerability diff --git a/setup.cfg b/setup.cfg index 647745361fd..c140c4bc986 100644 --- a/setup.cfg +++ b/setup.cfg @@ -196,7 +196,7 @@ install_requires = # Security and audit mistune==3.0.1 - wandb==0.15.3 + wandb==0.15.4 protobuf==3.20.3 mako==1.2.4 certifi>=2022.12.7 # not directly required, pinned by Snyk to avoid a vulnerability From 156d58bab6d9bd66b66796cb6f3e0b5a01969d6f Mon Sep 17 00:00:00 2001 From: Alessio Fabiani Date: Mon, 26 Jun 2023 14:50:17 +0200 Subject: [PATCH 023/111] fix: requirements.txt to reduce vulnerabilities (#11138) The following vulnerabilities are fixed by pinning transitive dependencies: - https://snyk.io/vuln/SNYK-PYTHON-CRYPTOGRAPHY-5663682 Co-authored-by: snyk-bot --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 9f96ca8f1c8..cd162f8c08a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -176,3 +176,4 @@ protobuf==3.20.3 mako==1.2.4 certifi>=2022.12.7 # not directly required, pinned by Snyk to avoid a vulnerability jwcrypto>=1.4 # not directly required, pinned by Snyk to avoid a vulnerability +cryptography>=41.0.0 # not directly required, pinned by Snyk to avoid a vulnerability From e103d06a391f250218cfbdc35bf6311a2b6e5204 Mon Sep 17 00:00:00 2001 From: mattiagiupponi <51856725+mattiagiupponi@users.noreply.github.com> Date: Mon, 26 Jun 2023 15:22:12 +0200 Subject: [PATCH 024/111] Direct download on preview on dataset list is not working #11156 (#11165) * FIx download_url for WFS url * FIx download_url for WFS url * FIx download_url for WFS url, change settings * FIx download_url for WFS url, change settings * Fix tests * [Fixes #11156] Approach change and code rollback * [Fixes #11156] Approach change and code rollback * [Fixes #11156] Approach change and code rollback * [Fixes #11156] Change delete query for original links --------- Co-authored-by: Giovanni Allegri --- geonode/layers/tests.py | 1 - geonode/utils.py | 14 +++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/geonode/layers/tests.py b/geonode/layers/tests.py index a24fb52ee01..fbe14c340ba 100644 --- a/geonode/layers/tests.py +++ b/geonode/layers/tests.py @@ -373,7 +373,6 @@ def test_dataset_links(self): links = Link.objects.filter(resource=lyr.resourcebase_ptr, link_type="image") self.assertIsNotNone(links) - # get and update original link to external Link.objects.filter(resource=lyr.resourcebase_ptr, link_type="original").update( url="http://google.com/test" ) diff --git a/geonode/utils.py b/geonode/utils.py index 3c83c824db8..6a2cee2708b 100755 --- a/geonode/utils.py +++ b/geonode/utils.py @@ -1412,6 +1412,8 @@ def set_resource_default_links(instance, layer, prune=False, **kwargs): from geonode.base.models import Link from django.urls import reverse from django.utils.translation import ugettext + from geonode.layers.models import Dataset + from geonode.documents.models import Document # Prune old links if prune: @@ -1481,9 +1483,15 @@ def set_resource_default_links(instance, layer, prune=False, **kwargs): # Create Raw Data download link if settings.DISPLAY_ORIGINAL_DATASET_LINK: logger.debug(" -- Resource Links[Create Raw Data download link]...") - download_url = urljoin(settings.SITEURL, reverse("download", args=[instance.id])) - while Link.objects.filter(resource=instance.resourcebase_ptr, url=download_url).count() > 1: - Link.objects.filter(resource=instance.resourcebase_ptr, url=download_url).first().delete() + if isinstance(instance, Dataset): + download_url = build_absolute_uri(reverse("dataset_download", args=(instance.alternate,))) + elif isinstance(instance, Document): + download_url = build_absolute_uri(reverse("document_download", args=(instance.id,))) + else: + download_url = None + + while Link.objects.filter(resource=instance.resourcebase_ptr, link_type="original").exists(): + Link.objects.filter(resource=instance.resourcebase_ptr, link_type="original").delete() Link.objects.update_or_create( resource=instance.resourcebase_ptr, url=download_url, From 5811a92469d9347b5f89ed77bbb855a5fca74f12 Mon Sep 17 00:00:00 2001 From: etj Date: Wed, 24 May 2023 16:48:58 +0200 Subject: [PATCH 025/111] #10995 Faceting - fixes --- geonode/facets/providers/category.py | 6 ++++-- geonode/facets/providers/users.py | 1 - geonode/facets/tests.py | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/geonode/facets/providers/category.py b/geonode/facets/providers/category.py index 3db78eb4d03..aac8b1c7cfe 100644 --- a/geonode/facets/providers/category.py +++ b/geonode/facets/providers/category.py @@ -38,7 +38,7 @@ def name(self) -> str: def get_info(self, lang="en") -> dict: return { "name": self.name, - "key": "filter{category__identifier}", + "key": "filter{category.identifier}", "label": "Category", "type": FACET_TYPE_CATEGORY, "hierarchical": False, @@ -55,7 +55,9 @@ def get_facet_items( ) -> (int, list): logger.debug("Retrieving facets for %s", self.name) - q = queryset.values("category__identifier", "category__gn_description", "category__fa_class") + q = queryset.values("category__identifier", "category__gn_description", "category__fa_class").filter( + category__isnull=False + ) if topic_contains: q = q.filter(category__gn_description=topic_contains) q = q.annotate(count=Count("owner")).order_by("-count") diff --git a/geonode/facets/providers/users.py b/geonode/facets/providers/users.py index 8a1e5c3effe..4358a3292a1 100644 --- a/geonode/facets/providers/users.py +++ b/geonode/facets/providers/users.py @@ -70,7 +70,6 @@ def get_facet_items( { "key": r["owner"], "label": r["owner__username"], - "localized_label": r["owner__username"], "count": r["count"], } for r in q[start:end] diff --git a/geonode/facets/tests.py b/geonode/facets/tests.py index 67fcf27e11e..6c7fb45780e 100644 --- a/geonode/facets/tests.py +++ b/geonode/facets/tests.py @@ -193,7 +193,7 @@ def test_facets_rich(self): { "name": "category", "topics": { - "total": 1, + "total": 0, }, }, { From b33f8697d6e73b18e86fcd1a2b5d251c197f9a1a Mon Sep 17 00:00:00 2001 From: etj Date: Thu, 25 May 2023 13:50:56 +0200 Subject: [PATCH 026/111] #10995 Faceting - fix thesauri localization --- geonode/facets/providers/thesaurus.py | 18 ++++++++++++------ geonode/facets/tests.py | 24 +++++++++++++++++++++--- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/geonode/facets/providers/thesaurus.py b/geonode/facets/providers/thesaurus.py index 5674635b6bd..8961aef5467 100644 --- a/geonode/facets/providers/thesaurus.py +++ b/geonode/facets/providers/thesaurus.py @@ -19,8 +19,9 @@ import logging -from django.db.models import Count +from django.db.models import Count, OuterRef, Subquery +from geonode.base.models import ThesaurusKeywordLabel from geonode.facets.models import FacetProvider, DEFAULT_FACET_PAGE_SIZE, FACET_TYPE_THESAURUS logger = logging.getLogger(__name__) @@ -65,7 +66,6 @@ def get_facet_items( filter = { "tkeywords__thesaurus__identifier": self._name, - "tkeywords__keyword__lang": lang, } if topic_contains: @@ -73,22 +73,28 @@ def get_facet_items( q = ( queryset.filter(**filter) - .values("tkeywords", "tkeywords__keyword__label", "tkeywords__alt_label") + .values("tkeywords", "tkeywords__alt_label") .annotate(count=Count("tkeywords")) + .annotate( + localized_label=Subquery( + ThesaurusKeywordLabel.objects.filter(keyword=OuterRef("tkeywords"), lang=lang).values("label") + ) + ) .order_by("-count") ) + logger.debug(" ---> %s\n\n", q.query) + cnt = q.count() logger.info("Found %d facets for %s", cnt, self._name) - logger.debug(" ---> %s\n\n", q.query) logger.debug(" ---> %r\n\n", q.all()) topics = [ { "key": r["tkeywords"], - "label": r["tkeywords__keyword__label"] or r["tkeywords__alt_label"], - "is_localized": r["tkeywords__keyword__label"] is not None, + "label": r["localized_label"] or r["tkeywords__alt_label"], + "is_localized": r["localized_label"] is not None, "count": r["count"], } for r in q[start:end].all() diff --git a/geonode/facets/tests.py b/geonode/facets/tests.py index 6c7fb45780e..49f199d2af7 100644 --- a/geonode/facets/tests.py +++ b/geonode/facets/tests.py @@ -76,7 +76,7 @@ def _create_thesauri(cls): ThesaurusLabel.objects.create(thesaurus=t, lang=tl, label=f"TLabel {tn} {tl}") for tkn in range(10): - tk = ThesaurusKeyword.objects.create(thesaurus=t, alt_label=f"alt_tkn{tkn}_t{tn}") + tk = ThesaurusKeyword.objects.create(thesaurus=t, alt_label=f"T{tn}_K{tkn}_ALT") cls.thesauri_k[f"{tn}_{tkn}"] = tk for tkl in ( "en", @@ -261,8 +261,26 @@ def test_facets_rich(self): def test_bad_lang(self): # for thesauri, make sure that by requesting a non-existent language the faceting is still working, # using the default labels - # TODO impl+test - pass + + # run the request with a valid language + req = self.rf.get(reverse("get_facet", args=["t_0"]), data={"lang": "en"}) + res: JsonResponse = views.get_facet(req, "t_0") + obj = json.loads(res.content) + + self.assertEqual(2, obj["topics"]["total"]) + self.assertEqual(10, obj["topics"]["items"][0]["count"]) + self.assertEqual("T0_K0_en", obj["topics"]["items"][0]["label"]) + self.assertTrue(obj["topics"]["items"][0]["is_localized"]) + + # run the request with an INVALID language + req = self.rf.get(reverse("get_facet", args=["t_0"]), data={"lang": "ZZ"}) + res: JsonResponse = views.get_facet(req, "t_0") + obj = json.loads(res.content) + + self.assertEqual(2, obj["topics"]["total"]) + self.assertEqual(10, obj["topics"]["items"][0]["count"]) # make sure the count is still there + self.assertEqual("T0_K0_ALT", obj["topics"]["items"][0]["label"]) # check for the alternate label + self.assertFalse(obj["topics"]["items"][0]["is_localized"]) # check for the localization flag def test_user_auth(self): # make sure the user authorization pre-filters the visible resources From 8838b8e9c69302f2c1b9df12a34cebbed29eae5f Mon Sep 17 00:00:00 2001 From: etj Date: Fri, 23 Jun 2023 19:10:46 +0200 Subject: [PATCH 027/111] [Fixes #11105] Facet topics --- geonode/facets/models.py | 15 +++++++++++++++ geonode/facets/providers/category.py | 16 ++++++++++++++++ geonode/facets/providers/region.py | 15 +++++++++++++++ geonode/facets/providers/thesaurus.py | 25 ++++++++++++++++++++++++- geonode/facets/providers/users.py | 15 +++++++++++++++ geonode/facets/tests.py | 12 ++++++++++++ geonode/facets/urls.py | 1 + geonode/facets/views.py | 21 ++++++++++++++++++++- 8 files changed, 118 insertions(+), 2 deletions(-) diff --git a/geonode/facets/models.py b/geonode/facets/models.py index d271c188c9d..07b1b4c7643 100644 --- a/geonode/facets/models.py +++ b/geonode/facets/models.py @@ -28,6 +28,7 @@ FACET_TYPE_USER = "user" FACET_TYPE_THESAURUS = "thesaurus" FACET_TYPE_CATEGORY = "category" +FACET_TYPE_RESOURCETYPE = "resourcetype" logger = logging.getLogger(__name__) @@ -86,6 +87,20 @@ def get_facet_items( """ pass + def get_topics(self, keys: list, lang="en", **kwargs) -> list: + """ + Return the topics with the requested ids as a list + - list, topic records. A topic record is a dict having these keys: + - key: the key of the items that should be used for filtering + - label: a generic label for the item; the client should try and localize it whenever possible + - localized_label: a localized label for the item + - other facet specific keys + :param keys: the list of the keys of the topics, as returned by the get_facet_items() method + :param lang: the preferred language for the labels + :return: list of items + """ + pass + @classmethod def register(cls, registry, **kwargs) -> None: """ diff --git a/geonode/facets/providers/category.py b/geonode/facets/providers/category.py index aac8b1c7cfe..e0f24e29044 100644 --- a/geonode/facets/providers/category.py +++ b/geonode/facets/providers/category.py @@ -21,6 +21,7 @@ from django.db.models import Count +from geonode.base.models import TopicCategory from geonode.facets.models import FacetProvider, DEFAULT_FACET_PAGE_SIZE, FACET_TYPE_CATEGORY logger = logging.getLogger(__name__) @@ -80,6 +81,21 @@ def get_facet_items( return cnt, topics + def get_topics(self, keys: list, lang="en", **kwargs) -> list: + q = TopicCategory.objects.filter(identifier__in=keys) + + logger.debug(" ---> %s\n\n", q.query) + logger.debug(" ---> %r\n\n", q.all()) + + return [ + { + "key": r.identifier, + "label": r.gn_description, + "fa_class": r.fa_class, + } + for r in q.all() + ] + @classmethod def register(cls, registry, **kwargs) -> None: registry.register_facet_provider(CategoryFacetProvider()) diff --git a/geonode/facets/providers/region.py b/geonode/facets/providers/region.py index b0cd5d45ce8..a0df25b2ea6 100644 --- a/geonode/facets/providers/region.py +++ b/geonode/facets/providers/region.py @@ -21,6 +21,7 @@ from django.db.models import Count +from geonode.base.models import Region from geonode.facets.models import FacetProvider, DEFAULT_FACET_PAGE_SIZE, FACET_TYPE_PLACE logger = logging.getLogger(__name__) @@ -77,6 +78,20 @@ def get_facet_items( return cnt, topics + def get_topics(self, keys: list, lang="en", **kwargs) -> list: + q = Region.objects.filter(code__in=keys).values("code", "name") + + logger.debug(" ---> %s\n\n", q.query) + logger.debug(" ---> %r\n\n", q.all()) + + return [ + { + "key": r["code"], + "label": r["name"], + } + for r in q.all() + ] + @classmethod def register(cls, registry, **kwargs) -> None: registry.register_facet_provider(RegionFacetProvider()) diff --git a/geonode/facets/providers/thesaurus.py b/geonode/facets/providers/thesaurus.py index 8961aef5467..d9897f6139d 100644 --- a/geonode/facets/providers/thesaurus.py +++ b/geonode/facets/providers/thesaurus.py @@ -21,7 +21,7 @@ from django.db.models import Count, OuterRef, Subquery -from geonode.base.models import ThesaurusKeywordLabel +from geonode.base.models import ThesaurusKeyword, ThesaurusKeywordLabel from geonode.facets.models import FacetProvider, DEFAULT_FACET_PAGE_SIZE, FACET_TYPE_THESAURUS logger = logging.getLogger(__name__) @@ -102,6 +102,29 @@ def get_facet_items( return cnt, topics + def get_topics(self, keys: list, lang="en", **kwargs) -> list: + q = ( + ThesaurusKeyword.objects.filter(id__in=keys) + .values("id", "alt_label") + .annotate( + localized_label=Subquery( + ThesaurusKeywordLabel.objects.filter(keyword=OuterRef("id"), lang=lang).values("label") + ) + ) + ) + + logger.debug(" ---> %s\n\n", q.query) + logger.debug(" ---> %r\n\n", q.all()) + + return [ + { + "key": r["id"], + "label": r["localized_label"] or r["alt_label"], + "is_localized": r["localized_label"] is not None, + } + for r in q.all() + ] + @classmethod def register(cls, registry, **kwargs) -> None: # registry.register_facet_provider(CategoryFacetProvider()) diff --git a/geonode/facets/providers/users.py b/geonode/facets/providers/users.py index 4358a3292a1..aedb2fc06f2 100644 --- a/geonode/facets/providers/users.py +++ b/geonode/facets/providers/users.py @@ -19,6 +19,7 @@ import logging +from django.contrib.auth import get_user_model from django.db.models import Count from geonode.facets.models import FacetProvider, DEFAULT_FACET_PAGE_SIZE, FACET_TYPE_USER @@ -77,6 +78,20 @@ def get_facet_items( return cnt, topics + def get_topics(self, keys: list, lang="en", **kwargs) -> list: + q = get_user_model().objects.filter(id__in=keys).values("id", "username") + + logger.debug(" ---> %s\n\n", q.query) + logger.debug(" ---> %r\n\n", q.all()) + + return [ + { + "key": r["id"], + "label": r["username"], + } + for r in q.all() + ] + @classmethod def register(cls, registry, **kwargs) -> None: registry.register_facet_provider(OwnerFacetProvider()) diff --git a/geonode/facets/tests.py b/geonode/facets/tests.py index 49f199d2af7..d582f4e2041 100644 --- a/geonode/facets/tests.py +++ b/geonode/facets/tests.py @@ -282,6 +282,18 @@ def test_bad_lang(self): self.assertEqual("T0_K0_ALT", obj["topics"]["items"][0]["label"]) # check for the alternate label self.assertFalse(obj["topics"]["items"][0]["is_localized"]) # check for the localization flag + def test_topics(self): + for facet, keys, exp in ( + ("t_0", [self.thesauri_k["0_0"].id, self.thesauri_k["0_1"].id, -999], 2), + ("category", ["C1", "C2", "nomatch"], 0), + ("owner", [self.user.id, -100], 1), + ("region", ["R0", "R1", "nomatch"], 2), + ): + req = self.rf.get(reverse("get_facet_topics", args=[facet]), data={"lang": "en", "key": keys}) + res: JsonResponse = views.get_facet_topics(req, facet) + obj = json.loads(res.content) + self.assertEqual(exp, len(obj["topics"]["items"]), f"Unexpected topic count {exp} for facet {facet}") + def test_user_auth(self): # make sure the user authorization pre-filters the visible resources # TODO test diff --git a/geonode/facets/urls.py b/geonode/facets/urls.py index 82aef71e751..42bf08de85b 100644 --- a/geonode/facets/urls.py +++ b/geonode/facets/urls.py @@ -23,4 +23,5 @@ urlpatterns = [ path("facets", views.list_facets, name="list_facets"), path("facets/", views.get_facet, name="get_facet"), + path("facets//topics", views.get_facet_topics, name="get_facet_topics"), ] diff --git a/geonode/facets/views.py b/geonode/facets/views.py index 31961bb3b6d..8aa18439f83 100644 --- a/geonode/facets/views.py +++ b/geonode/facets/views.py @@ -23,7 +23,7 @@ from rest_framework.authentication import SessionAuthentication, BasicAuthentication from rest_framework.decorators import api_view, authentication_classes -from django.http import HttpResponseNotFound, JsonResponse +from django.http import HttpResponseNotFound, JsonResponse, HttpResponseBadRequest from django.urls import reverse from django.conf import settings @@ -116,6 +116,25 @@ def get_facet(request, facet): return JsonResponse(info) +@api_view(["GET"]) +def get_facet_topics(request, facet): + logger.debug("get_facet_topics -> %r", facet) + + # retrieve provider for the requested facet + provider: FacetProvider = facet_registry.get_provider(facet) + if not provider: + return HttpResponseNotFound("Facet not found") + + # parse some query params + lang, lang_requested = _resolve_language(request) + keys = request.query_params.getlist("key") + if not keys: + return HttpResponseBadRequest("Missing key parameter") + + ret = {"topics": {"items": provider.get_topics(keys, lang=lang)}} + return JsonResponse(ret) + + def _get_topics( provider, queryset, From f3a490a2c2b38975351a887af720e7b7282b385e Mon Sep 17 00:00:00 2001 From: Emanuele Tajariol Date: Thu, 29 Jun 2023 16:29:28 +0200 Subject: [PATCH 028/111] [Fixes #11102] Faceting: prefiltering (#11205) --- geonode/base/api/filters.py | 15 ++++++++++----- geonode/facets/tests.py | 23 +++++++++++++++++++++++ geonode/facets/views.py | 13 +++++++++++-- 3 files changed, 44 insertions(+), 7 deletions(-) diff --git a/geonode/base/api/filters.py b/geonode/base/api/filters.py index f8691e0178d..a2428bfdc65 100644 --- a/geonode/base/api/filters.py +++ b/geonode/base/api/filters.py @@ -56,11 +56,16 @@ class TKeywordsFilter(BaseFilterBackend): """ def filter_queryset(self, request, queryset, view): - return ( - self.filter_queryset_GROUP(request, queryset, view) - if "force_and" not in request.GET - else self.filter_queryset_AND(request, queryset, view) - ) + # we must make the GET mutable since in the filters, some queryparams are popped + request.GET._mutable = True + try: + return ( + self.filter_queryset_GROUP(request, queryset, view) + if "force_and" not in request.GET + else self.filter_queryset_AND(request, queryset, view) + ) + finally: + request.GET._mutable = False def filter_queryset_AND(self, request, queryset, view): """ diff --git a/geonode/facets/tests.py b/geonode/facets/tests.py index d582f4e2041..b5ae7f7541f 100644 --- a/geonode/facets/tests.py +++ b/geonode/facets/tests.py @@ -28,6 +28,8 @@ from django.urls import reverse from geonode.base.models import Thesaurus, ThesaurusLabel, ThesaurusKeyword, ThesaurusKeywordLabel, ResourceBase, Region +from geonode.facets.models import facet_registry +from geonode.facets.providers.region import RegionFacetProvider from geonode.tests.base import GeoNodeBaseTestSupport import geonode.facets.views as views @@ -294,6 +296,27 @@ def test_topics(self): obj = json.loads(res.content) self.assertEqual(exp, len(obj["topics"]["items"]), f"Unexpected topic count {exp} for facet {facet}") + def test_prefiltering(self): + reginfo = RegionFacetProvider().get_info() + t0info = facet_registry.get_provider("t_0").get_info() + t1info = facet_registry.get_provider("t_1").get_info() + + for facet, filters, totals, count0 in ( + ("t_0", {}, 2, 10), + ("t_0", {reginfo["key"]: "R0"}, 1, 1), + ("t_1", {}, 2, 10), + ("t_1", {reginfo["key"]: "R0"}, 1, 2), + ("t_1", {reginfo["key"]: "R1"}, 2, 3), + (reginfo["name"], {}, 2, 4), + (reginfo["name"], {t0info["key"]: self.thesauri_k["0_0"].id}, 2, 1), + (reginfo["name"], {t1info["key"]: self.thesauri_k["1_0"].id}, 2, 3), + ): + req = self.rf.get(reverse("get_facet", args=[facet]), data=filters) + res: JsonResponse = views.get_facet(req, facet) + obj = json.loads(res.content) + self.assertEqual(totals, obj["topics"]["total"], f"Bad totals for facet '{facet} and filter {filters}") + self.assertEqual(count0, obj["topics"]["items"][0]["count"], f"Bad count0 for facet '{facet}") + def test_user_auth(self): # make sure the user authorization pre-filters the visible resources # TODO test diff --git a/geonode/facets/views.py b/geonode/facets/views.py index 8aa18439f83..717eabfc57a 100644 --- a/geonode/facets/views.py +++ b/geonode/facets/views.py @@ -27,6 +27,8 @@ from django.urls import reverse from django.conf import settings + +from geonode.base.api.views import ResourceBaseViewSet from geonode.base.models import ResourceBase from geonode.facets.models import FacetProvider, DEFAULT_FACET_PAGE_SIZE, facet_registry from geonode.security.utils import get_visible_resources @@ -160,8 +162,15 @@ def _prefilter_topics(request): :return: a QuerySet on ResourceBase """ logger.debug("Filtering by user '%s'", request.user) - # return ResourceBase.objects - return get_visible_resources(ResourceBase.objects, request.user) + filters = {k: vlist for k, vlist in request.query_params.lists() if k.startswith("filter{")} + + if filters: + viewset = ResourceBaseViewSet(request=request, format_kwarg={}, kwargs=filters) + viewset.initial(request) + return get_visible_resources(queryset=viewset.filter_queryset(viewset.get_queryset()), user=request.user) + else: + # return ResourceBase.objects + return get_visible_resources(ResourceBase.objects, request.user) def _resolve_language(request) -> (str, bool): From ad68f2ed7c697345fd0fda644d0e77f49af158f0 Mon Sep 17 00:00:00 2001 From: Francisco Vicent Date: Mon, 3 Jul 2023 05:35:48 -0300 Subject: [PATCH 029/111] Fix #11154 - Wrong login URL in notification (#11155) * Fix #11154 - Wrong login URL in notification * fix single quote --- geonode/people/models.py | 1 + .../notifications/account_active/account_active_message.txt | 2 +- geonode/templates/pinax/notifications/account_active/full.txt | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/geonode/people/models.py b/geonode/people/models.py index 37d4c21631c..c07b6f5e81f 100644 --- a/geonode/people/models.py +++ b/geonode/people/models.py @@ -246,6 +246,7 @@ def _notify_account_activated(self): "site_name": current_site.name, "email": self.email, "inviter": self, + "LOGIN_URL": settings.LOGIN_URL, } email_template = "pinax/notifications/account_active/account_active" diff --git a/geonode/templates/pinax/notifications/account_active/account_active_message.txt b/geonode/templates/pinax/notifications/account_active/account_active_message.txt index 02237b540e6..f9ddbc0c091 100644 --- a/geonode/templates/pinax/notifications/account_active/account_active_message.txt +++ b/geonode/templates/pinax/notifications/account_active/account_active_message.txt @@ -1,3 +1,3 @@ {% load i18n %} {% trans "Your account has been approved and is now active." %} ({{ username }})
-{% trans "You can use the login form at" %}: http://{{ current_site.name }} +{% trans "You can use the login form at" %}: {{ LOGIN_URL }} diff --git a/geonode/templates/pinax/notifications/account_active/full.txt b/geonode/templates/pinax/notifications/account_active/full.txt index aac4b9cc507..8344ed13d4b 100644 --- a/geonode/templates/pinax/notifications/account_active/full.txt +++ b/geonode/templates/pinax/notifications/account_active/full.txt @@ -1,3 +1,3 @@ {% load i18n %} {% trans "Your account has been approved and is now active." %}
-{% trans "You can use the login form at" %}: http://{{ current_site }} +{% trans "You can use the login form at" %}: {{ LOGIN_URL }} From ec3868ac044e24959032a32dc3f2d53e0ff44fae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Jul 2023 11:45:55 +0200 Subject: [PATCH 030/111] Bump google-cloud-storage from 2.9.0 to 2.10.0 (#11212) * Bump google-cloud-storage from 2.9.0 to 2.10.0 Bumps [google-cloud-storage](https://github.com/googleapis/python-storage) from 2.9.0 to 2.10.0. - [Release notes](https://github.com/googleapis/python-storage/releases) - [Changelog](https://github.com/googleapis/python-storage/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/python-storage/compare/v2.9.0...v2.10.0) --- updated-dependencies: - dependency-name: google-cloud-storage dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index cd162f8c08a..b99ca9afde2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -111,7 +111,7 @@ django-bootstrap3-datetimepicker-2==2.8.3 # storage manager dependencies django-storages==1.13.2 dropbox==11.36.2 -google-cloud-storage==2.9.0 +google-cloud-storage==2.10.0 google-cloud-core==2.3.2 boto3==1.26.160 diff --git a/setup.cfg b/setup.cfg index c140c4bc986..295dc72d6b2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -136,7 +136,7 @@ install_requires = # storage manager dependencies django-storages==1.13.2 dropbox==11.36.2 - google-cloud-storage==2.9.0 + google-cloud-storage==2.10.0 google-cloud-core==2.3.2 boto3==1.26.160 From c75073f8b653c63b5dec21b0e4de07b85a668fd3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Jul 2023 11:46:13 +0200 Subject: [PATCH 031/111] Bump boto3 from 1.26.160 to 1.26.165 (#11210) * Bump boto3 from 1.26.160 to 1.26.165 Bumps [boto3](https://github.com/boto/boto3) from 1.26.160 to 1.26.165. - [Release notes](https://github.com/boto/boto3/releases) - [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/boto3/compare/1.26.160...1.26.165) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index b99ca9afde2..4df657af224 100644 --- a/requirements.txt +++ b/requirements.txt @@ -113,7 +113,7 @@ django-storages==1.13.2 dropbox==11.36.2 google-cloud-storage==2.10.0 google-cloud-core==2.3.2 -boto3==1.26.160 +boto3==1.26.165 # Django Caches python-memcached<=1.59 diff --git a/setup.cfg b/setup.cfg index 295dc72d6b2..1806bbc4910 100644 --- a/setup.cfg +++ b/setup.cfg @@ -138,7 +138,7 @@ install_requires = dropbox==11.36.2 google-cloud-storage==2.10.0 google-cloud-core==2.3.2 - boto3==1.26.160 + boto3==1.26.165 # Django Caches python-memcached<=1.59 From dcc119ab9e6ae400bf8c662a07a8d770aa28a235 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Jul 2023 14:19:22 +0200 Subject: [PATCH 032/111] Bump pillow from 9.5.0 to 10.0.0 (#11211) * Bump pillow from 9.5.0 to 10.0.0 Bumps [pillow](https://github.com/python-pillow/Pillow) from 9.5.0 to 10.0.0. - [Release notes](https://github.com/python-pillow/Pillow/releases) - [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst) - [Commits](https://github.com/python-pillow/Pillow/compare/9.5.0...10.0.0) --- updated-dependencies: - dependency-name: pillow dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" * - Bump geonode-avatar 5.0.8 to django-avatar==7.1.1 * - Bump geonode-avatar 5.0.8 to django-avatar==7.1.1 --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 4 ++-- setup.cfg | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index 4df657af224..f44b6c465b9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ # native dependencies -Pillow==9.5.0 +Pillow==10.0.0 lxml==4.9.2 psycopg2==2.9.6 Django==3.2.19 @@ -91,7 +91,7 @@ pinax-ratings==4.0.0 # django-geonode-mapstore-client==4.0.5 -e git+https://github.com/GeoNode/geonode-mapstore-client.git@master#egg=django_geonode_mapstore_client -e git+https://github.com/GeoNode/geonode-importer.git@master#egg=geonode-importer -geonode-avatar==5.0.8 +django-avatar==7.1.1 geonode-oauth-toolkit==2.2.2 geonode-user-messages==2.0.2 geonode-announcements==2.0.2 diff --git a/setup.cfg b/setup.cfg index 1806bbc4910..fdc1c61ab21 100644 --- a/setup.cfg +++ b/setup.cfg @@ -25,7 +25,7 @@ setup_requires = setuptools install_requires = # native dependencies - Pillow==9.5.0 + Pillow==10.0.0 lxml==4.9.2 psycopg2==2.9.6 Django==3.2.19 @@ -116,7 +116,7 @@ install_requires = # GeoNode org maintained apps. django-geonode-mapstore-client>=4.0.5,<5.0.0 geonode-importer>=1.0.2 - geonode-avatar==5.0.8 + django-avatar==7.1.1 geonode-oauth-toolkit==2.2.2 geonode-user-messages==2.0.2 geonode-announcements==2.0.2 @@ -201,6 +201,7 @@ install_requires = mako==1.2.4 certifi>=2022.12.7 # not directly required, pinned by Snyk to avoid a vulnerability jwcrypto>=1.4 # not directly required, pinned by Snyk to avoid a vulnerability + cryptography>=41.0.0 # not directly required, pinned by Snyk to avoid a vulnerability [options.packages.find] exclude = tests From de59bced4d56479eae55febb2c527132a7a4e2c6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Jul 2023 16:06:46 +0200 Subject: [PATCH 033/111] Bump django from 3.2.19 to 3.2.20 (#11216) * Bump django from 3.2.19 to 3.2.20 Bumps [django](https://github.com/django/django) from 3.2.19 to 3.2.20. - [Commits](https://github.com/django/django/compare/3.2.19...3.2.20) --- updated-dependencies: - dependency-name: django dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Alessio Fabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index f44b6c465b9..022221a3a3a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ Pillow==10.0.0 lxml==4.9.2 psycopg2==2.9.6 -Django==3.2.19 +Django==3.2.20 # Other amqp==5.1.1 diff --git a/setup.cfg b/setup.cfg index fdc1c61ab21..d30ae2d8c54 100644 --- a/setup.cfg +++ b/setup.cfg @@ -28,7 +28,7 @@ install_requires = Pillow==10.0.0 lxml==4.9.2 psycopg2==2.9.6 - Django==3.2.19 + Django==3.2.20 # Other amqp==5.1.1 From 543d751d8c5d98184008b175d06d97b4b8131db2 Mon Sep 17 00:00:00 2001 From: Alessio Fabiani Date: Wed, 5 Jul 2023 09:55:46 +0200 Subject: [PATCH 034/111] fix: Dockerfile to reduce vulnerabilities (#11223) The following vulnerabilities are fixed with an upgrade: - https://snyk.io/vuln/SNYK-UBUNTU2204-BASH-3098342 - https://snyk.io/vuln/SNYK-UBUNTU2204-COREUTILS-2801226 - https://snyk.io/vuln/SNYK-UBUNTU2204-GLIBC-2801292 - https://snyk.io/vuln/SNYK-UBUNTU2204-LIBCAP2-5538282 - https://snyk.io/vuln/SNYK-UBUNTU2204-LIBCAP2-5538296 Co-authored-by: snyk-bot --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 6c62736737b..5d76bb6f25d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:22.04 +FROM ubuntu:22.10 LABEL GeoNode development team RUN mkdir -p /usr/src/geonode From f839d394af4f313b66482a2b2544b556969a1f7b Mon Sep 17 00:00:00 2001 From: etj Date: Tue, 4 Jul 2023 18:27:36 +0200 Subject: [PATCH 035/111] [Fixes #11097] Faceting: resource type --- geonode/facets/models.py | 2 +- geonode/facets/providers/baseinfo.py | 147 +++++++++++++++++++++++++++ geonode/facets/tests.py | 60 +++++++---- geonode/settings.py | 2 + 4 files changed, 189 insertions(+), 22 deletions(-) create mode 100644 geonode/facets/providers/baseinfo.py diff --git a/geonode/facets/models.py b/geonode/facets/models.py index 07b1b4c7643..c7f9fbd0813 100644 --- a/geonode/facets/models.py +++ b/geonode/facets/models.py @@ -28,7 +28,7 @@ FACET_TYPE_USER = "user" FACET_TYPE_THESAURUS = "thesaurus" FACET_TYPE_CATEGORY = "category" -FACET_TYPE_RESOURCETYPE = "resourcetype" +FACET_TYPE_BASE = "base" logger = logging.getLogger(__name__) diff --git a/geonode/facets/providers/baseinfo.py b/geonode/facets/providers/baseinfo.py new file mode 100644 index 00000000000..b1f55129be3 --- /dev/null +++ b/geonode/facets/providers/baseinfo.py @@ -0,0 +1,147 @@ +######################################################################### +# +# Copyright (C) 2023 Open Source Geospatial Foundation - all rights reserved +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### + +import logging + +from django.db.models import Count + +from geonode.facets.models import FacetProvider, DEFAULT_FACET_PAGE_SIZE, FACET_TYPE_BASE + +logger = logging.getLogger(__name__) + + +class ResourceTypeFacetProvider(FacetProvider): + """ + Implements faceting for resources' type and subtype + """ + + @property + def name(self) -> str: + return "resourcetype" + + def get_info(self, lang="en") -> dict: + return { + "name": self.name, + "key": "filter{resource_type.in}", + "label": "Resource type", + "type": FACET_TYPE_BASE, + "hierarchical": True, + "order": 0, + } + + def get_facet_items( + self, + queryset=None, + start: int = 0, + end: int = DEFAULT_FACET_PAGE_SIZE, + lang="en", + topic_contains: str = None, + ) -> (int, list): + logger.debug("Retrieving facets for %s", self.name) + + if topic_contains: + logger.warning(f"Facet {self.name} does not support topic_contains filtering") + + q = queryset.values("resource_type", "subtype") + q = q.annotate(ctype=Count("resource_type"), csub=Count("subtype")) + q = q.order_by() + + # aggregate subtypes into rtypes + tree = {} + for r in q.all(): + res_type = r["resource_type"] + t = tree.get(res_type, {"cnt": 0, "sub": {}}) + t["cnt"] += r["ctype"] + if sub := r["subtype"]: + t["sub"][sub] = {"cnt": r["ctype"]} + tree[res_type] = t + + logger.info("Found %d main facets for %s", len(tree), self.name) + logger.debug(" ---> %s\n\n", q.query) + logger.debug(" ---> %r\n\n", q.all()) + + topics = [] + for rtype, info in tree.items(): + t = {"key": rtype, "label": rtype, "count": info["cnt"]} + if sub := info["sub"]: + children = [] + for stype, sinfo in sub.items(): + children.append({"key": stype, "label": stype, "count": sinfo["cnt"]}) + t["filter"] = "filter{subtype.in}" + t["items"] = sorted(children, reverse=True, key=lambda x: x["count"]) + topics.append(t) + + return len(topics), sorted(topics, reverse=True, key=lambda x: x["count"]) + + @classmethod + def register(cls, registry, **kwargs) -> None: + registry.register_facet_provider(ResourceTypeFacetProvider()) + + +class FeaturedFacetProvider(FacetProvider): + """ + Implements faceting for resources flagged as featured + """ + + @property + def name(self) -> str: + return "featured" + + def get_info(self, lang="en") -> dict: + return { + "name": self.name, + "key": "filter{featured}", + "label": "Featured", + "type": FACET_TYPE_BASE, + "hierarchical": False, + "order": 0, + } + + def get_facet_items( + self, + queryset=None, + start: int = 0, + end: int = DEFAULT_FACET_PAGE_SIZE, + lang="en", + topic_contains: str = None, + ) -> (int, list): + logger.debug("Retrieving facets for %s", self.name) + + if topic_contains: + logger.warning(f"Facet {self.name} does not support topic_contains filtering") + + q = queryset.values("featured").annotate(cnt=Count("featured")).order_by() + + logger.debug(" ---> %s\n\n", q.query) + logger.debug(" ---> %r\n\n", q.all()) + + topics = [ + { + "key": r["featured"], + "label": str(r["featured"]), + "count": r["cnt"], + } + for r in q[start:end] + ] + + return 2, topics + + @classmethod + def register(cls, registry, **kwargs) -> None: + registry.register_facet_provider(FeaturedFacetProvider()) diff --git a/geonode/facets/tests.py b/geonode/facets/tests.py index b5ae7f7541f..156c4908e50 100644 --- a/geonode/facets/tests.py +++ b/geonode/facets/tests.py @@ -114,49 +114,46 @@ def _create_resources(self): # These are the assigned keywords to the Resources - # RB00 -> T1K0 R0,R1 - # RB01 -> T0K0 T1K0 R0 - # RB02 -> T1K0 R1 + # RB00 -> T1K0 R0,R1 FEAT + # RB01 -> T0K0 T1K0 R0 FEAT + # RB02 -> T1K0 R1 FEAT # RB03 -> T0K0 T1K0 # RB04 -> T1K0 # RB05 -> T0K0 T1K0 - # RB06 -> T1K0 - # RB07 -> T0K0 T1K0 - # RB08 -> T1K0 T1K1 R1 + # RB06 -> T1K0 FEAT + # RB07 -> T0K0 T1K0 FEAT + # RB08 -> T1K0 T1K1 R1 FEAT # RB09 -> T0K0 T1K0 T1K1 # RB10 -> T1K1 # RB11 -> T0K0 T0K1 T1K1 - # RB12 -> T1K1 - # RB13 -> T0K0 T0K1 R1 - # RB14 -> + # RB12 -> T1K1 FEAT + # RB13 -> T0K0 T0K1 R1 FEAT + # RB14 -> FEAT # RB15 -> T0K0 T0K1 # RB16 -> # RB17 -> T0K0 T0K1 - # RB18 -> - # RB19 -> T0K0 T0K1 + # RB18 -> FEAT + # RB19 -> T0K0 T0K1 FEAT if x % 2 == 1: print(f"ADDING KEYWORDS {self.thesauri_k['0_0']} to RB {d}") d.tkeywords.add(self.thesauri_k["0_0"]) - d.save() if x % 2 == 1 and x > 10: print(f"ADDING KEYWORDS {self.thesauri_k['0_1']} to RB {d}") d.tkeywords.add(self.thesauri_k["0_1"]) - d.save() if x < 10: print(f"ADDING KEYWORDS {self.thesauri_k['1_0']} to RB {d}") d.tkeywords.add(self.thesauri_k["1_0"]) - d.save() if 7 < x < 13: d.tkeywords.add(self.thesauri_k["1_1"]) - d.save() if x in (0, 1): d.regions.add(self.regions["R0"]) - d.save() if x in (0, 2, 8, 13): d.regions.add(self.regions["R1"]) - d.save() + if (x % 6) in (0, 1, 2): + d.featured = True + d.save() d.set_permissions(public_perm_spec) @staticmethod @@ -169,9 +166,9 @@ def test_facets_base(self): obj = json.loads(res.content) self.assertIn("facets", obj) facets_list = obj["facets"] - self.assertEqual(5, len(facets_list)) + self.assertEqual(7, len(facets_list)) fmap = self._facets_to_map(facets_list) - for name in ("category", "owner", "t_0", "t_1"): + for name in ("category", "owner", "t_0", "t_1", "featured", "resourcetype"): self.assertIn(name, fmap) def test_facets_rich(self): @@ -189,7 +186,7 @@ def test_facets_rich(self): obj = json.loads(res.content) facets_list = obj["facets"] - self.assertEqual(5, len(facets_list)) + self.assertEqual(7, len(facets_list)) fmap = self._facets_to_map(facets_list) for expected in ( { @@ -233,6 +230,25 @@ def test_facets_rich(self): ], }, }, + { + "name": "featured", + "topics": { + "total": 2, + "items": [ + {"label": "True", "key": True, "count": 11}, + {"label": "False", "key": False, "count": 9}, + ], + }, + }, + { + "name": "resourcetype", + "topics": { + "total": 1, + "items": [ + {"label": "resourcebase", "key": "resourcebase", "count": 20}, + ], + }, + }, ): name = expected["name"] self.assertIn(name, fmap) @@ -254,7 +270,9 @@ def test_facets_rich(self): found = item break - self.assertIsNotNone(item, f"topic not found '{exp_label}'") + self.assertIsNotNone( + found, f"topic not found '{exp_label}' for facet '{name}' -- found items {items}" + ) for exp_field in exp_item: self.assertEqual( exp_item[exp_field], found[exp_field], f"Mismatch item key:{exp_field} facet:{name}" diff --git a/geonode/settings.py b/geonode/settings.py index d408093575b..9d6a270f138 100644 --- a/geonode/settings.py +++ b/geonode/settings.py @@ -2320,6 +2320,8 @@ def get_geonode_catalogue_service(): GEONODE_APPS += ("geonode.facets",) FACET_PROVIDERS = ( + "geonode.facets.providers.baseinfo.ResourceTypeFacetProvider", + "geonode.facets.providers.baseinfo.FeaturedFacetProvider", "geonode.facets.providers.category.CategoryFacetProvider", "geonode.facets.providers.users.OwnerFacetProvider", "geonode.facets.providers.thesaurus.ThesaurusFacetProvider", From 9c7ba9abd6f41cafc26217f37033b1c25435f7f7 Mon Sep 17 00:00:00 2001 From: Giovanni Allegri Date: Thu, 6 Jul 2023 18:11:12 +0200 Subject: [PATCH 036/111] [Fixes #11228] Return supported formats grouped and ordered (#11229) * Return supported formats grouped and ordered * a more compct solution * dropped redundant variable * fix typo * dropped redundant variable --------- Co-authored-by: mattiagiupponi <51856725+mattiagiupponi@users.noreply.github.com> --- geonode/utils.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/geonode/utils.py b/geonode/utils.py index 6a2cee2708b..fc27b9b5647 100755 --- a/geonode/utils.py +++ b/geonode/utils.py @@ -1963,7 +1963,24 @@ def get_supported_datasets_file_types(): supported_types[default_types_id.index(_type.get("id"))] = _type else: supported_types.extend([_type]) - return supported_types + + # Order the formats (to support their visualization) + formats_order = [("vector", 0), ("raster", 1), ("archive", 2)] + ordered_payload = ( + (weight[1], resource_type) + for resource_type in supported_types + for weight in formats_order + if resource_type.get("format") in weight[0] + ) + + # Flatten the list + ordered_resource_types = [x[1] for x in sorted(ordered_payload, key=lambda x: x[0])] + other_resource_types = [ + resource_type + for resource_type in supported_types + if resource_type.get("format") is None or resource_type.get("format") not in [f[0] for f in formats_order] + ] + return ordered_resource_types + other_resource_types def get_allowed_extensions(): From 4a1255fb6a1537838f8d2c626f5c5196d0be32dd Mon Sep 17 00:00:00 2001 From: Giovanni Allegri Date: Fri, 7 Jul 2023 11:49:58 +0200 Subject: [PATCH 037/111] [Fixes #11230] Split and tag base Docker image (#11232) * Aplti and tag base Docker image * renamed image from master to latest --- Dockerfile | 51 ++++----------------------- scripts/docker/base/ubuntu/Dockerfile | 40 +++++++++++++++++++++ 2 files changed, 46 insertions(+), 45 deletions(-) create mode 100644 scripts/docker/base/ubuntu/Dockerfile diff --git a/Dockerfile b/Dockerfile index 5d76bb6f25d..c09545fcf3b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,51 +1,15 @@ -FROM ubuntu:22.10 +FROM geonode/geonode-base:latest-ubuntu-22.10 LABEL GeoNode development team -RUN mkdir -p /usr/src/geonode - -## Enable postgresql-client-13 -RUN apt-get update -y && apt-get install curl wget unzip gnupg2 -y -RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - -# will install python3.10 -RUN apt-get install lsb-core -y -RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ `lsb_release -cs`-pgdg main" |tee /etc/apt/sources.list.d/pgdg.list - -# Prepraing dependencies -RUN apt-get install -y \ - libgdal-dev libpq-dev libxml2-dev \ - libxml2 libxslt1-dev zlib1g-dev libjpeg-dev \ - libmemcached-dev libldap2-dev libsasl2-dev libffi-dev - -RUN apt-get update -y && apt-get install -y --no-install-recommends \ - gcc zip gettext geoip-bin cron \ - postgresql-client-13 \ - python3-all-dev python3-dev \ - python3-gdal python3-psycopg2 python3-ldap \ - python3-pip python3-pil python3-lxml \ - uwsgi uwsgi-plugin-python3 python3-gdbm python-is-python3 gdal-bin - -RUN apt-get install -y devscripts build-essential debhelper pkg-kde-tools sharutils -# RUN git clone https://salsa.debian.org/debian-gis-team/proj.git /tmp/proj -# RUN cd /tmp/proj && debuild -i -us -uc -b && dpkg -i ../*.deb - -# Install pip packages -RUN pip3 install uwsgi \ - && pip install pip --upgrade \ - && pip install pygdal==$(gdal-config --version).* flower==0.9.4 - -# Activate "memcached" -RUN apt-get install -y memcached -RUN pip install sherlock - # add bower and grunt command COPY . /usr/src/geonode/ WORKDIR /usr/src/geonode -COPY monitoring-cron /etc/cron.d/monitoring-cron -RUN chmod 0644 /etc/cron.d/monitoring-cron -RUN crontab /etc/cron.d/monitoring-cron -RUN touch /var/log/cron.log -RUN service cron start +#COPY monitoring-cron /etc/cron.d/monitoring-cron +#RUN chmod 0644 /etc/cron.d/monitoring-cron +#RUN crontab /etc/cron.d/monitoring-cron +#RUN touch /var/log/cron.log +#RUN service cron start COPY wait-for-databases.sh /usr/bin/wait-for-databases RUN chmod +x /usr/bin/wait-for-databases @@ -67,9 +31,6 @@ RUN chmod +x /usr/bin/celery-cmd RUN pip install --upgrade --no-cache-dir --src /usr/src -r requirements.txt RUN pip install --upgrade -e . -# Cleanup apt update lists -RUN rm -rf /var/lib/apt/lists/* - # Export ports EXPOSE 8000 diff --git a/scripts/docker/base/ubuntu/Dockerfile b/scripts/docker/base/ubuntu/Dockerfile new file mode 100644 index 00000000000..3b36cb6236f --- /dev/null +++ b/scripts/docker/base/ubuntu/Dockerfile @@ -0,0 +1,40 @@ +FROM ubuntu:22.10 + +RUN mkdir -p /usr/src/geonode + +## Enable postgresql-client-13 +RUN apt-get update -y && apt-get install curl wget unzip gnupg2 -y +RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - +# will install python3.10 +RUN apt-get install lsb-core -y +RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ `lsb_release -cs`-pgdg main" |tee /etc/apt/sources.list.d/pgdg.list + +# Prepraing dependencies +RUN apt-get install -y \ + libgdal-dev libpq-dev libxml2-dev \ + libxml2 libxslt1-dev zlib1g-dev libjpeg-dev \ + libmemcached-dev libldap2-dev libsasl2-dev libffi-dev + +RUN apt-get update -y && apt-get install -y --no-install-recommends \ + gcc vim zip gettext geoip-bin cron \ + postgresql-client-13 \ + python3-all-dev python3-dev \ + python3-gdal python3-psycopg2 python3-ldap \ + python3-pip python3-pil python3-lxml \ + uwsgi uwsgi-plugin-python3 python3-gdbm python-is-python3 gdal-bin + +RUN apt-get install -y devscripts build-essential debhelper pkg-kde-tools sharutils +# RUN git clone https://salsa.debian.org/debian-gis-team/proj.git /tmp/proj +# RUN cd /tmp/proj && debuild -i -us -uc -b && dpkg -i ../*.deb + +# Install pip packages +RUN pip3 install uwsgi \ + && pip install pip --upgrade \ + && pip install pygdal==$(gdal-config --version).* flower==0.9.4 + +# Activate "memcached" +RUN apt-get install -y memcached +RUN pip install sherlock + +# Cleanup apt update lists +RUN rm -rf /var/lib/apt/lists/* From 0bb04fec4b26ac0b45ddc84d1e65d20489d19a57 Mon Sep 17 00:00:00 2001 From: etj Date: Thu, 6 Jul 2023 17:49:15 +0200 Subject: [PATCH 038/111] [Fixes #11101] Faceting: keywords --- geonode/facets/models.py | 1 + geonode/facets/providers/keyword.py | 96 +++++++++++++++++++++++++++++ geonode/facets/tests.py | 6 +- geonode/settings.py | 1 + 4 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 geonode/facets/providers/keyword.py diff --git a/geonode/facets/models.py b/geonode/facets/models.py index c7f9fbd0813..4beffbb1ac6 100644 --- a/geonode/facets/models.py +++ b/geonode/facets/models.py @@ -29,6 +29,7 @@ FACET_TYPE_THESAURUS = "thesaurus" FACET_TYPE_CATEGORY = "category" FACET_TYPE_BASE = "base" +FACET_TYPE_KEYWORD = "keyword" logger = logging.getLogger(__name__) diff --git a/geonode/facets/providers/keyword.py b/geonode/facets/providers/keyword.py new file mode 100644 index 00000000000..4a82e9a3dbc --- /dev/null +++ b/geonode/facets/providers/keyword.py @@ -0,0 +1,96 @@ +######################################################################### +# +# Copyright (C) 2023 Open Source Geospatial Foundation - all rights reserved +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### + +import logging + +from django.db.models import Count + +from geonode.base.models import HierarchicalKeyword +from geonode.facets.models import FacetProvider, DEFAULT_FACET_PAGE_SIZE, FACET_TYPE_KEYWORD + +logger = logging.getLogger(__name__) + + +class KeywordFacetProvider(FacetProvider): + """ + Implements faceting for resource's keywords + """ + + @property + def name(self) -> str: + return "keyword" + + def get_info(self, lang="en") -> dict: + return { + "name": self.name, + "key": "filter{keywords.slug.in}", + "label": "Keyword", + "type": FACET_TYPE_KEYWORD, + "order": 2, + } + + def get_facet_items( + self, + queryset=None, + start: int = 0, + end: int = DEFAULT_FACET_PAGE_SIZE, + lang="en", + topic_contains: str = None, + ) -> (int, list): + logger.debug("Retrieving facets for %s", self.name) + + q = queryset.values("keywords__slug", "keywords__name").filter(keywords__isnull=False) + if topic_contains: + q = q.filter(keywords__name=topic_contains) + q = q.annotate(count=Count("keywords__slug")).order_by("-count") + + cnt = q.count() + + logger.info("Found %d facets for %s", cnt, self.name) + logger.debug(" ---> %s\n\n", q.query) + logger.debug(" ---> %r\n\n", q.all()) + + topics = [ + { + "key": r["keywords__slug"], + "label": r["keywords__name"], + "count": r["count"], + } + for r in q[start:end].all() + ] + + return cnt, topics + + def get_topics(self, keys: list, lang="en", **kwargs) -> list: + q = HierarchicalKeyword.objects.filter(slug__in=keys).values("slug", "name") + + logger.debug(" ---> %s\n\n", q.query) + logger.debug(" ---> %r\n\n", q.all()) + + return [ + { + "key": r["slug"], + "label": r["name"], + } + for r in q.all() + ] + + @classmethod + def register(cls, registry, **kwargs) -> None: + registry.register_facet_provider(KeywordFacetProvider()) diff --git a/geonode/facets/tests.py b/geonode/facets/tests.py index 156c4908e50..cc6d9a8e0ec 100644 --- a/geonode/facets/tests.py +++ b/geonode/facets/tests.py @@ -166,9 +166,9 @@ def test_facets_base(self): obj = json.loads(res.content) self.assertIn("facets", obj) facets_list = obj["facets"] - self.assertEqual(7, len(facets_list)) + self.assertEqual(8, len(facets_list)) fmap = self._facets_to_map(facets_list) - for name in ("category", "owner", "t_0", "t_1", "featured", "resourcetype"): + for name in ("category", "owner", "t_0", "t_1", "featured", "resourcetype", "keyword"): self.assertIn(name, fmap) def test_facets_rich(self): @@ -186,7 +186,7 @@ def test_facets_rich(self): obj = json.loads(res.content) facets_list = obj["facets"] - self.assertEqual(7, len(facets_list)) + self.assertEqual(8, len(facets_list)) fmap = self._facets_to_map(facets_list) for expected in ( { diff --git a/geonode/settings.py b/geonode/settings.py index 9d6a270f138..8afedc72c17 100644 --- a/geonode/settings.py +++ b/geonode/settings.py @@ -2323,6 +2323,7 @@ def get_geonode_catalogue_service(): "geonode.facets.providers.baseinfo.ResourceTypeFacetProvider", "geonode.facets.providers.baseinfo.FeaturedFacetProvider", "geonode.facets.providers.category.CategoryFacetProvider", + "geonode.facets.providers.keyword.KeywordFacetProvider", "geonode.facets.providers.users.OwnerFacetProvider", "geonode.facets.providers.thesaurus.ThesaurusFacetProvider", "geonode.facets.providers.region.RegionFacetProvider", From 7ba0ed203c89f0baa092f96f464e1df902fc86f5 Mon Sep 17 00:00:00 2001 From: Alessio Fabiani Date: Mon, 10 Jul 2023 11:57:37 +0200 Subject: [PATCH 039/111] GNIP 94: Generic and pluggable OIDC SocialAccount Provider for GeoNode (#11218) Documentation available here: https://github.com/GeoNode/documentation/pull/277 --- .circleci/config.yml | 2 +- .env_test | 3 + geonode/groups/models.py | 40 ++- geonode/messaging/notifications.py | 15 +- geonode/people/adapters.py | 75 ++++++ geonode/people/profileextractors.py | 14 +- geonode/people/socialaccount/__init__.py | 18 ++ .../socialaccount/providers/__init__.py | 18 ++ .../geonode_openid_connect/__init__.py | 18 ++ .../geonode_openid_connect/provider.py | 94 +++++++ .../providers/geonode_openid_connect/tests.py | 230 ++++++++++++++++++ .../providers/geonode_openid_connect/urls.py | 24 ++ .../providers/geonode_openid_connect/views.py | 30 +++ geonode/settings.py | 102 ++++---- geonode/utils.py | 19 +- pavement.py | 4 +- 16 files changed, 639 insertions(+), 67 deletions(-) create mode 100644 geonode/people/socialaccount/__init__.py create mode 100644 geonode/people/socialaccount/providers/__init__.py create mode 100644 geonode/people/socialaccount/providers/geonode_openid_connect/__init__.py create mode 100644 geonode/people/socialaccount/providers/geonode_openid_connect/provider.py create mode 100644 geonode/people/socialaccount/providers/geonode_openid_connect/tests.py create mode 100644 geonode/people/socialaccount/providers/geonode_openid_connect/urls.py create mode 100644 geonode/people/socialaccount/providers/geonode_openid_connect/views.py diff --git a/.circleci/config.yml b/.circleci/config.yml index 79ebd63c91b..60cfd1c921e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -108,7 +108,7 @@ workflows: name: geonode_test_suite load_docker_cache: false save_docker_cache: false - test_suite: ./test.sh $(python -c "import sys;from geonode import settings;sys.stdout.write('\'' '\''.join([a+'\''.tests'\'' for a in settings.GEONODE_APPS if '\''security'\'' not in a and '\''geoserver'\'' not in a]))") geonode.thumbs.tests geonode.people.tests + test_suite: ./test.sh $(python -c "import sys;from geonode import settings;sys.stdout.write('\'' '\''.join([a+'\''.tests'\'' for a in settings.GEONODE_APPS if '\''security'\'' not in a and '\''geoserver'\'' not in a]))") geonode.thumbs.tests geonode.people.tests geonode.people.socialaccount.providers.geonode_openid_connect.tests - build: name: geonode_test_security load_docker_cache: false diff --git a/.env_test b/.env_test index 78eebee94cb..a728f8fd4d1 100644 --- a/.env_test +++ b/.env_test @@ -157,6 +157,9 @@ OAUTH2_API_KEY= OAUTH2_CLIENT_ID=Jrchz2oPY3akmzndmgUTYrs9gczlgoV20YPSvqaV OAUTH2_CLIENT_SECRET=rCnp5txobUo83EpQEblM8fVj3QT5zb5qRfxNsuPzCqZaiRyIoxM4jdgMiZKFfePBHYXCLd7B8NlkfDBY9HKeIQPcy5Cp08KQNpRHQbjpLItDHv12GvkSeXp6OxaUETv3 +SOCIALACCOUNT_OIDC_PROVIDER_ENABLED=True +SOCIALACCOUNT_PROVIDER=google + # GeoNode APIs API_LOCKDOWN=False TASTYPIE_APIKEY= diff --git a/geonode/groups/models.py b/geonode/groups/models.py index 373d683d40c..59ae6f61392 100644 --- a/geonode/groups/models.py +++ b/geonode/groups/models.py @@ -32,6 +32,7 @@ from django.contrib.auth.models import Group from django.templatetags.static import static from django.contrib.auth import get_user_model +from django.core.exceptions import ObjectDoesNotExist from django.utils.translation import ugettext_lazy as _ from geonode.utils import build_absolute_uri @@ -190,26 +191,43 @@ def can_view(self, user): else: return True - def join(self, user, **kwargs): + def validate_user(self, user): if not user or user.is_anonymous or user == user.get_anonymous(): raise ValueError("The invited user cannot be anonymous") - _members = GroupMember.objects.filter(group=self, user=user) - if not _members.count(): - GroupMember.objects.get_or_create(group=self, user=user, defaults=kwargs) - else: + + def join(self, user, **kwargs): + self.validate_user(user) + try: + GroupMember.objects.get(group=self, user=user) logger.warning(f'The invited user "{user.username}" is already a member') + except ObjectDoesNotExist: + GroupMember.objects.create(group=self, user=user, **kwargs) def leave(self, user, **kwargs): - if not user or user.is_anonymous or user == user.get_anonymous(): - raise ValueError("The invited user cannot be anonymous") + self.validate_user(user) _members = GroupMember.objects.filter(group=self, user=user) - if _members.count(): - for _member in _members: - _member.delete() - user.groups.remove(self.group) + if _members.exists(): + _members.delete() + user.groups.remove(self.group) else: logger.warning(f'The invited user "{user.username}" is not a member') + def promote(self, user, **kwargs): + self.validate_user(user) + try: + _member = GroupMember.objects.get(group=self, user=user) + _member.promote() + except ObjectDoesNotExist: + logger.warning(f'The invited user "{user.username}" is not a member') + + def demote(self, user, **kwargs): + self.validate_user(user) + try: + _member = GroupMember.objects.get(group=self, user=user) + _member.demote() + except ObjectDoesNotExist: + logger.warning(f'The invited user "{user.username}" is not a member') + def get_absolute_url(self): return reverse( "group_detail", diff --git a/geonode/messaging/notifications.py b/geonode/messaging/notifications.py index 763cf05371e..00d3135c1a1 100644 --- a/geonode/messaging/notifications.py +++ b/geonode/messaging/notifications.py @@ -38,13 +38,14 @@ def message_received_notification(**kwargs): recipients = _get_user_to_notify(message) # Enable email notifications for reciepients - for user in recipients: - notifications.models.NoticeSetting.objects.get_or_create( - notice_type=notifications.models.NoticeType.objects.get(label=notice_type_label), - send=True, - user=user, - medium=0, - ) + if notifications: + for user in recipients: + notifications.models.NoticeSetting.objects.get_or_create( + notice_type=notifications.models.NoticeType.objects.get(label=notice_type_label), + send=True, + user=user, + medium=0, + ) ctx = { "message": message.content, diff --git a/geonode/people/adapters.py b/geonode/people/adapters.py index bb290e11feb..a322562d19a 100644 --- a/geonode/people/adapters.py +++ b/geonode/people/adapters.py @@ -25,12 +25,15 @@ """ import logging +import jwt +import requests from allauth.account.adapter import DefaultAccountAdapter from allauth.account.utils import user_field from allauth.account.utils import user_email from allauth.account.utils import user_username from allauth.socialaccount.adapter import DefaultSocialAccountAdapter +from allauth.socialaccount.providers.oauth2.views import OAuth2Adapter, OAuth2Error from invitations.adapters import BaseInvitationsAdapter @@ -222,3 +225,75 @@ def _site_allows_signup(django_request): def _respond_inactive_user(user): return HttpResponseRedirect(reverse("moderator_contacted", kwargs={"inactive_user": user.id})) + + +PROVIDER_ID = getattr(settings, "SOCIALACCOUNT_OIDC_PROVIDER", "geonode_openid_connect") + +ACCESS_TOKEN_URL = getattr(settings, "SOCIALACCOUNT_PROVIDERS", {}).get(PROVIDER_ID, {}).get("ACCESS_TOKEN_URL", "") + +AUTHORIZE_URL = getattr(settings, "SOCIALACCOUNT_PROVIDERS", {}).get(PROVIDER_ID, {}).get("AUTHORIZE_URL", "") + +PROFILE_URL = getattr(settings, "SOCIALACCOUNT_PROVIDERS", {}).get(PROVIDER_ID, {}).get("PROFILE_URL", "") + +ID_TOKEN_ISSUER = getattr(settings, "SOCIALACCOUNT_PROVIDERS", {}).get(PROVIDER_ID, {}).get("ID_TOKEN_ISSUER", "") + + +class GenericOpenIDConnectAdapter(OAuth2Adapter, SocialAccountAdapter): + provider_id = PROVIDER_ID + access_token_url = ACCESS_TOKEN_URL + authorize_url = AUTHORIZE_URL + profile_url = PROFILE_URL + id_token_issuer = ID_TOKEN_ISSUER + + def complete_login(self, request, app, token, response, **kwargs): + extra_data = {} + if self.profile_url: + headers = {"Authorization": "Bearer {0}".format(token.token)} + resp = requests.get(self.profile_url, headers=headers) + profile_data = resp.json() + extra_data.update(profile_data) + elif "id_token" in response: + try: + extra_data = jwt.decode( + response["id_token"], + # Since the token was received by direct communication + # protected by TLS between this library and Google, we + # are allowed to skip checking the token signature + # according to the OpenID Connect Core 1.0 + # specification. + # https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation + options={ + "verify_signature": False, + "verify_iss": True, + "verify_aud": True, + "verify_exp": True, + }, + issuer=self.id_token_issuer, + audience=app.client_id, + ) + except jwt.PyJWTError as e: + raise OAuth2Error("Invalid id_token") from e + login = self.get_provider().sociallogin_from_response(request, extra_data) + return login + + def save_user(self, request, sociallogin, form=None): + user = super(SocialAccountAdapter, self).save_user(request, sociallogin, form=form) + extractor = get_data_extractor(sociallogin.account.provider) + try: + groups = extractor.extract_groups(sociallogin.account.extra_data) or extractor.extract_roles( + sociallogin.account.extra_data + ) + is_manager = extractor.extract_is_manager(sociallogin.account.extra_data) + + # check here if user is member already of other groups and remove it form the ones that are not declared here... + for groupprofile in user.group_list_all(): + groupprofile.leave(user) + for group_name in groups: + groupprofile = GroupProfile.objects.filter(slug=group_name).first() + if groupprofile: + groupprofile.join(user) + if is_manager: + groupprofile.promote() + except (AttributeError, NotImplementedError): + pass # extractor doesn't define a method for extracting field + return user diff --git a/geonode/people/profileextractors.py b/geonode/people/profileextractors.py index e6411c3d267..2bf7fa12e51 100644 --- a/geonode/people/profileextractors.py +++ b/geonode/people/profileextractors.py @@ -127,6 +127,13 @@ def _extract_field(self, name, data): return result +PROVIDER_ID = getattr(settings, "SOCIALACCOUNT_OIDC_PROVIDER", "geonode_openid_connect") + +IS_MANAGER_FIELD = ( + getattr(settings, "SOCIALACCOUNT_PROVIDERS", {}).get(PROVIDER_ID, {}).get("IS_MANAGER_FIELD", "is_manager") +) + + class OpenIDExtractor(BaseExtractor): def extract_email(self, data): return data.get("email", "") @@ -182,14 +189,17 @@ def extract_organization(self, data): def extract_voice(self, data): return data.get("phone", "") + def extract_keywords(self, data): + return data.get("keywords", "") + def extract_groups(self, data): return data.get("groups", "") def extract_roles(self, data): return data.get("roles", "") - def extract_keywords(self, data): - return data.get("keywords", "") + def extract_is_manager(self, data): + return data.get(IS_MANAGER_FIELD, "") def _get_latest_position(data): diff --git a/geonode/people/socialaccount/__init__.py b/geonode/people/socialaccount/__init__.py new file mode 100644 index 00000000000..da86ef5219a --- /dev/null +++ b/geonode/people/socialaccount/__init__.py @@ -0,0 +1,18 @@ +######################################################################### +# +# Copyright (C) 2023 OSGeo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### diff --git a/geonode/people/socialaccount/providers/__init__.py b/geonode/people/socialaccount/providers/__init__.py new file mode 100644 index 00000000000..da86ef5219a --- /dev/null +++ b/geonode/people/socialaccount/providers/__init__.py @@ -0,0 +1,18 @@ +######################################################################### +# +# Copyright (C) 2023 OSGeo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### diff --git a/geonode/people/socialaccount/providers/geonode_openid_connect/__init__.py b/geonode/people/socialaccount/providers/geonode_openid_connect/__init__.py new file mode 100644 index 00000000000..da86ef5219a --- /dev/null +++ b/geonode/people/socialaccount/providers/geonode_openid_connect/__init__.py @@ -0,0 +1,18 @@ +######################################################################### +# +# Copyright (C) 2023 OSGeo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### diff --git a/geonode/people/socialaccount/providers/geonode_openid_connect/provider.py b/geonode/people/socialaccount/providers/geonode_openid_connect/provider.py new file mode 100644 index 00000000000..329ba3c4bf3 --- /dev/null +++ b/geonode/people/socialaccount/providers/geonode_openid_connect/provider.py @@ -0,0 +1,94 @@ +######################################################################### +# +# Copyright (C) 2023 OSGeo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### + +"""Custom account providers for django-allauth. + +These are used in order to extend the default authorization provided by +django-allauth. + +""" +from django.conf import settings + +from geonode.utils import import_class_module + +from allauth.account.models import EmailAddress +from allauth.socialaccount.providers.base import AuthAction, ProviderAccount +from allauth.socialaccount.providers.oauth2.provider import OAuth2Provider + +PROVIDER_ID = getattr(settings, "SOCIALACCOUNT_OIDC_PROVIDER", "geonode_openid_connect") + + +class GenericOpenIDConnectProviderAccount(ProviderAccount): + def to_str(self): + dflt = super(GenericOpenIDConnectProviderAccount, self).to_str() + return self.account.extra_data.get("name", dflt) + + +class GenericOpenIDConnectProvider(OAuth2Provider): + id = "geonode_openid_connect" + name = getattr(settings, "SOCIALACCOUNT_PROVIDERS", {}).get(PROVIDER_ID, {}).get("NAME", "GeoNode OpenIDConnect") + account_class = import_class_module( + getattr(settings, "SOCIALACCOUNT_PROVIDERS", {}) + .get(PROVIDER_ID, {}) + .get( + "ACCOUNT_CLASS", + "geonode.people.socialaccount.providers.geonode_openid_connect.provider.GenericOpenIDConnectProviderAccount", + ) + ) + + def get_default_scope(self): + scope = getattr(settings, "SOCIALACCOUNT_PROVIDERS", {}).get(PROVIDER_ID, {}).get("SCOPE", "") + return scope + + def get_auth_params(self, request, action): + ret = super(GenericOpenIDConnectProvider, self).get_auth_params(request, action) + if action == AuthAction.REAUTHENTICATE: + ret["prompt"] = ( + getattr(settings, "SOCIALACCOUNT_PROVIDERS", {}) + .get(PROVIDER_ID, {}) + .get("AUTH_PARAMS", {}) + .get("prompt", "") + ) + return ret + + def extract_uid(self, data): + return data.get("sub", data.get("id")) + + def extract_common_fields(self, data): + _common_fields = getattr(settings, "SOCIALACCOUNT_PROVIDERS", {}).get(PROVIDER_ID, {}).get("COMMON_FIELDS", {}) + __common_fields_data = {} + for _common_field in _common_fields: + __common_fields_data[_common_field] = data.get(_common_fields.get(_common_field), "") + return __common_fields_data + + def extract_email_addresses(self, data): + addresses = [] + email = data.get("email") + if email: + addresses.append( + EmailAddress( + email=email, + verified=data.get("email_verified", False), + primary=True, + ) + ) + return addresses + + +provider_classes = [GenericOpenIDConnectProvider] diff --git a/geonode/people/socialaccount/providers/geonode_openid_connect/tests.py b/geonode/people/socialaccount/providers/geonode_openid_connect/tests.py new file mode 100644 index 00000000000..29373c2fd93 --- /dev/null +++ b/geonode/people/socialaccount/providers/geonode_openid_connect/tests.py @@ -0,0 +1,230 @@ +######################################################################### +# +# Copyright (C) 2023 OSGeo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### +from __future__ import absolute_import, unicode_literals + +import json +from datetime import datetime, timedelta +from importlib import import_module + +from django.conf import settings +from django.core import mail +from django.test.client import RequestFactory +from django.test.utils import override_settings +from django.urls import reverse +from django.contrib.auth import get_user_model + +from allauth.account import app_settings as account_settings +from allauth.account.adapter import get_adapter +from allauth.account.models import EmailAddress, EmailConfirmation +from allauth.account.signals import user_signed_up + +# from allauth.socialaccount.models import SocialAccount +from allauth.socialaccount.providers.apple.client import jwt_encode +from allauth.socialaccount.tests import OAuth2TestsMixin +from allauth.tests import TestCase + + +@override_settings( + SOCIALACCOUNT_OIDC_PROVIDER_ENABLED=True, + SOCIALACCOUNT_AUTO_SIGNUP=True, + ACCOUNT_SIGNUP_FORM_CLASS=None, + ACCOUNT_EMAIL_VERIFICATION=account_settings.EmailVerificationMethod.MANDATORY, +) +class GoogleTests(OAuth2TestsMixin, TestCase): + provider_id = "geonode_openid_connect" + + def setUp(self): + super().setUp() + self.email = "raymond.penners@example.com" + self.identity_overwrites = {} + + def get_google_id_token_payload(self): + now = datetime.utcnow() + client_id = "app123id" # Matches `setup_app` + payload = { + "iss": "https://accounts.google.com", + "azp": client_id, + "aud": client_id, + "sub": "108204268033311374519", + "hd": "example.com", + "email": self.email, + "email_verified": True, + "at_hash": "HK6E_P6Dh8Y93mRNtsDB1Q", + "name": "Raymond Penners", + "picture": "https://lh5.googleusercontent.com/photo.jpg", + "given_name": "Raymond", + "family_name": "Penners", + "locale": "en", + "iat": now, + "exp": now + timedelta(hours=1), + } + payload.update(self.identity_overwrites) + return payload + + def get_login_response_json(self, with_refresh_token=True): + data = { + "access_token": "testac", + "expires_in": 3600, + "scope": "https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile openid", + "token_type": "Bearer", + "id_token": jwt_encode(self.get_google_id_token_payload(), "secret"), + } + return json.dumps(data) + + @override_settings(SOCIALACCOUNT_AUTO_SIGNUP=False) + def test_login(self): + resp = self.login(resp_mock=None) + self.assertRedirects(resp, reverse("socialaccount_signup")) + + def test_wrong_id_token_claim_values(self): + wrong_claim_values = { + "iss": "not-google", + "exp": datetime.utcnow() - timedelta(seconds=1), + "aud": "foo", + } + for key, value in wrong_claim_values.items(): + with self.subTest(key): + self.identity_overwrites = {key: value} + resp = self.login(resp_mock=None) + self.assertTemplateUsed( + resp, + "socialaccount/authentication_error.%s" % getattr(settings, "ACCOUNT_TEMPLATE_EXTENSION", "html"), + ) + + def test_username_based_on_email(self): + self.identity_overwrites = {"given_name": "明", "family_name": "小"} + self.login(resp_mock=None) + user = get_user_model().objects.get(email=self.email) + self.assertEqual(user.username, "raymond.penners") + + def test_email_verified(self): + self.identity_overwrites = {"email_verified": True} + self.login(resp_mock=None) + email_address = EmailAddress.objects.get(email=self.email, verified=True) + self.assertFalse(EmailConfirmation.objects.filter(email_address__email=self.email).exists()) + account = email_address.user.socialaccount_set.all()[0] + self.assertEqual(account.extra_data["given_name"], "Raymond") + + def test_user_signed_up_signal(self): + sent_signals = [] + + def on_signed_up(sender, request, user, **kwargs): + sociallogin = kwargs["sociallogin"] + self.assertEqual(sociallogin.account.provider, "geonode_openid_connect") + self.assertEqual(sociallogin.account.user, user) + sent_signals.append(sender) + + user_signed_up.connect(on_signed_up) + self.login(resp_mock=None) + self.assertTrue(len(sent_signals) > 0) + + @override_settings(ACCOUNT_EMAIL_CONFIRMATION_HMAC=False) + def test_email_unverified(self): + self.identity_overwrites = {"email_verified": False} + resp = self.login(resp_mock=None) + email_address = EmailAddress.objects.get(email=self.email) + self.assertFalse(email_address.verified) + self.assertTrue(EmailConfirmation.objects.filter(email_address__email=self.email).exists()) + self.assertTemplateUsed(resp, "account/email/email_confirmation_signup_subject.txt") + + def test_email_verified_stashed(self): + # http://slacy.com/blog/2012/01/how-to-set-session-variables-in-django-unit-tests/ + engine = import_module(settings.SESSION_ENGINE) + store = engine.SessionStore() + store.save() + self.client.cookies[settings.SESSION_COOKIE_NAME] = store.session_key + request = RequestFactory().get("/") + request.session = self.client.session + adapter = get_adapter(request) + adapter.stash_verified_email(request, self.email) + request.session.save() + + self.identity_overwrites = {"email_verified": False} + self.login(resp_mock=None) + email_address = EmailAddress.objects.get(email=self.email) + self.assertTrue(email_address.verified) + self.assertFalse(EmailConfirmation.objects.filter(email_address__email=self.email).exists()) + + def test_account_connect(self): + email = "user@example.com" + user = get_user_model().objects.create(username="user", is_active=True, email=email) + user.set_password("test") + user.save() + EmailAddress.objects.create(user=user, email=email, primary=True, verified=True) + self.client.login(username=user.username, password="test") + self.identity_overwrites = {"email": email, "email_verified": True} + self.login(resp_mock=None, process="connect") + # Check if we connected... + # self.assertTrue(SocialAccount.objects.filter(user=user, provider="geonode_openid_connect").exists()) + # For now, we do not pick up any new e-mail addresses on connect + self.assertEqual(EmailAddress.objects.filter(user=user).count(), 1) + self.assertEqual(EmailAddress.objects.filter(user=user, email=email).count(), 1) + + @override_settings( + ACCOUNT_EMAIL_VERIFICATION=account_settings.EmailVerificationMethod.MANDATORY, + SOCIALACCOUNT_EMAIL_VERIFICATION=account_settings.EmailVerificationMethod.NONE, + ) + def test_social_email_verification_skipped(self): + self.identity_overwrites = {"email_verified": False} + self.login(resp_mock=None) + email_address = EmailAddress.objects.get(email=self.email) + self.assertFalse(email_address.verified) + self.assertFalse(EmailConfirmation.objects.filter(email_address__email=self.email).exists()) + + @override_settings( + ACCOUNT_EMAIL_VERIFICATION=account_settings.EmailVerificationMethod.OPTIONAL, + SOCIALACCOUNT_EMAIL_VERIFICATION=account_settings.EmailVerificationMethod.OPTIONAL, + ) + def test_social_email_verification_optional(self): + self.identity_overwrites = {"email_verified": False} + self.login(resp_mock=None) + self.assertEqual(len(mail.outbox), 1) + self.login(resp_mock=None) + self.assertEqual(len(mail.outbox), 1) + + +@override_settings( + SOCIALACCOUNT_OIDC_PROVIDER_ENABLED=True, + SOCIALACCOUNT_PROVIDERS={ + "geonode_openid_connect": { + "NAME": "Google", + "SCOPE": [ + "profile", + "email", + ], + "AUTH_PARAMS": { + "access_type": "online", + "prompt": "select_account consent", + }, + "COMMON_FIELDS": {"email": "email", "last_name": "family_name", "first_name": "given_name"}, + "IS_MANAGER_FIELD": "is_manager", + "ACCOUNT_CLASS": "allauth.socialaccount.providers.google.provider.GoogleAccount", + "ACCESS_TOKEN_URL": "https://oauth2.googleapis.com/token", + "AUTHORIZE_URL": "https://accounts.google.com/o/oauth2/v2/auth", + "ID_TOKEN_ISSUER": "https://accounts.google.com", + "OAUTH_PKCE_ENABLED": True, + } + }, +) +class AppInSettingsTests(GoogleTests): + """ + Run the same set of tests but without having a SocialApp entry. + """ + + pass diff --git a/geonode/people/socialaccount/providers/geonode_openid_connect/urls.py b/geonode/people/socialaccount/providers/geonode_openid_connect/urls.py new file mode 100644 index 00000000000..daa5ff7d752 --- /dev/null +++ b/geonode/people/socialaccount/providers/geonode_openid_connect/urls.py @@ -0,0 +1,24 @@ +######################################################################### +# +# Copyright (C) 2023 OSGeo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### +from allauth.socialaccount.providers.oauth2.urls import default_urlpatterns + +from geonode.people.socialaccount.providers.geonode_openid_connect.provider import GenericOpenIDConnectProvider + + +urlpatterns = default_urlpatterns(GenericOpenIDConnectProvider) diff --git a/geonode/people/socialaccount/providers/geonode_openid_connect/views.py b/geonode/people/socialaccount/providers/geonode_openid_connect/views.py new file mode 100644 index 00000000000..c009c52b95c --- /dev/null +++ b/geonode/people/socialaccount/providers/geonode_openid_connect/views.py @@ -0,0 +1,30 @@ +######################################################################### +# +# Copyright (C) 2023 OSGeo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### +from django.conf import settings + +from geonode.utils import import_class_module + +from allauth.socialaccount.providers.oauth2.views import ( + OAuth2CallbackView, + OAuth2LoginView, +) + + +oauth2_login = OAuth2LoginView.adapter_view(import_class_module(settings.SOCIALACCOUNT_ADAPTER)) +oauth2_callback = OAuth2CallbackView.adapter_view(import_class_module(settings.SOCIALACCOUNT_ADAPTER)) diff --git a/geonode/settings.py b/geonode/settings.py index 8afedc72c17..14b58fec600 100644 --- a/geonode/settings.py +++ b/geonode/settings.py @@ -26,7 +26,6 @@ import dj_database_url from schema import Optional from datetime import timedelta -from distutils.util import strtobool # noqa from urllib.parse import urlparse, urljoin # @@ -1956,56 +1955,73 @@ def get_geonode_catalogue_service(): ACCOUNT_LOGIN_ATTEMPTS_LIMIT = int(os.getenv("ACCOUNT_LOGIN_ATTEMPTS_LIMIT", "3")) ACCOUNT_MAX_EMAIL_ADDRESSES = int(os.getenv("ACCOUNT_MAX_EMAIL_ADDRESSES", "2")) -SOCIALACCOUNT_ADAPTER = "geonode.people.adapters.SocialAccountAdapter" SOCIALACCOUNT_AUTO_SIGNUP = ast.literal_eval(os.environ.get("SOCIALACCOUNT_AUTO_SIGNUP", "True")) +SOCIALACCOUNT_LOGIN_ON_GET = ast.literal_eval(os.environ.get("SOCIALACCOUNT_LOGIN_ON_GET", "True")) # This will hide or show local registration form in allauth view. True will show form -SOCIALACCOUNT_WITH_GEONODE_LOCAL_SINGUP = strtobool(os.environ.get("SOCIALACCOUNT_WITH_GEONODE_LOCAL_SINGUP", "True")) +SOCIALACCOUNT_WITH_GEONODE_LOCAL_SINGUP = ast.literal_eval( + os.environ.get("SOCIALACCOUNT_WITH_GEONODE_LOCAL_SINGUP", "True") +) -# Uncomment this to enable Linkedin and Facebook login -# INSTALLED_APPS += ( -# 'allauth.socialaccount.providers.linkedin_oauth2', -# 'allauth.socialaccount.providers.facebook', -# ) +# GeoNode Default Generic OIDC Provider -SOCIALACCOUNT_PROVIDERS = { - "linkedin_oauth2": { - "SCOPE": [ - "r_emailaddress", - "r_liteprofile", - ], - "PROFILE_FIELDS": [ - "id", - "email-address", - "first-name", - "last-name", - "picture-url", - "public-profile-url", - ], +SOCIALACCOUNT_OIDC_PROVIDER = os.environ.get("SOCIALACCOUNT_OIDC_PROVIDER", "geonode_openid_connect") +SOCIALACCOUNT_OIDC_PROVIDER_ENABLED = ast.literal_eval(os.environ.get("SOCIALACCOUNT_OIDC_PROVIDER_ENABLED", "False")) +SOCIALACCOUNT_ADAPTER = os.environ.get("SOCIALACCOUNT_ADAPTER", "geonode.people.adapters.GenericOpenIDConnectAdapter") + +# Enable this in order to enable the OIDC SocialAccount Provider +if SOCIALACCOUNT_OIDC_PROVIDER_ENABLED: + INSTALLED_APPS += ("geonode.people.socialaccount.providers.geonode_openid_connect",) + +_AZURE_TENANT_ID = os.getenv("MICROSOFT_TENANT_ID", "") +_AZURE_SOCIALACCOUNT_PROVIDER = { + "NAME": "Microsoft Azure", + "SCOPE": [ + "User.Read", + "openid", + ], + "AUTH_PARAMS": { + "access_type": "online", + "prompt": "select_account", }, - "facebook": { - "METHOD": "oauth2", - "SCOPE": [ - "email", - "public_profile", - ], - "FIELDS": [ - "id", - "email", - "name", - "first_name", - "last_name", - "verified", - "locale", - "timezone", - "link", - "gender", - ], + "COMMON_FIELDS": {"email": "mail", "last_name": "surname", "first_name": "givenName"}, + "IS_MANAGER_FIELD": "is_manager", + "ACCOUNT_CLASS": "allauth.socialaccount.providers.azure.provider.AzureAccount", + "ACCESS_TOKEN_URL": f"https://login.microsoftonline.com/{_AZURE_TENANT_ID}/oauth2/v2.0/token", + "AUTHORIZE_URL": f"https://login.microsoftonline.com/{_AZURE_TENANT_ID}/oauth2/v2.0/authorize", + "PROFILE_URL": "https://graph.microsoft.com/v1.0/me", +} + +_GOOGLE_SOCIALACCOUNT_PROVIDER = { + "NAME": "Google", + "SCOPE": [ + "profile", + "email", + ], + "AUTH_PARAMS": { + "access_type": "online", + "prompt": "select_account consent", }, + "COMMON_FIELDS": {"email": "email", "last_name": "family_name", "first_name": "given_name"}, + "IS_MANAGER_FIELD": "is_manager", + "ACCOUNT_CLASS": "allauth.socialaccount.providers.google.provider.GoogleAccount", + "ACCESS_TOKEN_URL": "https://oauth2.googleapis.com/token", + "AUTHORIZE_URL": "https://accounts.google.com/o/oauth2/v2/auth", + "ID_TOKEN_ISSUER": "https://accounts.google.com", + "OAUTH_PKCE_ENABLED": True, +} + +SOCIALACCOUNT_PROVIDERS_DEFS = {"azure": _AZURE_SOCIALACCOUNT_PROVIDER, "google": _GOOGLE_SOCIALACCOUNT_PROVIDER} + +_SOCIALACCOUNT_PROVIDER = os.environ.get("SOCIALACCOUNT_PROVIDER", "google") +SOCIALACCOUNT_PROVIDERS = { + SOCIALACCOUNT_OIDC_PROVIDER: SOCIALACCOUNT_PROVIDERS_DEFS.get(_SOCIALACCOUNT_PROVIDER), } +_SOCIALACCOUNT_PROFILE_EXTRACTOR = os.environ.get( + "SOCIALACCOUNT_PROFILE_EXTRACTOR", "geonode.people.profileextractors.OpenIDExtractor" +) SOCIALACCOUNT_PROFILE_EXTRACTORS = { - "facebook": "geonode.people.profileextractors.FacebookExtractor", - "linkedin_oauth2": "geonode.people.profileextractors.LinkedInExtractor", + SOCIALACCOUNT_OIDC_PROVIDER: _SOCIALACCOUNT_PROFILE_EXTRACTOR, } INVITATIONS_ADAPTER = ACCOUNT_ADAPTER @@ -2117,7 +2133,7 @@ def get_geonode_catalogue_service(): GEOIP_PATH = os.getenv("GEOIP_PATH", os.path.join(PROJECT_ROOT, "GeoIPCities.dat")) # This controls if tastypie search on resourches is performed only with titles -SEARCH_RESOURCES_EXTENDED = strtobool(os.getenv("SEARCH_RESOURCES_EXTENDED", "True")) +SEARCH_RESOURCES_EXTENDED = ast.literal_eval(os.getenv("SEARCH_RESOURCES_EXTENDED", "True")) # -- END Settings for MONITORING plugin CATALOG_METADATA_TEMPLATE = os.getenv("CATALOG_METADATA_TEMPLATE", "catalogue/full_metadata.xml") diff --git a/geonode/utils.py b/geonode/utils.py index fc27b9b5647..6d541926ba1 100755 --- a/geonode/utils.py +++ b/geonode/utils.py @@ -17,7 +17,6 @@ # ######################################################################### -import itertools import os import gc import re @@ -34,6 +33,8 @@ import datetime import requests import tempfile +import importlib +import itertools import traceback import subprocess @@ -2005,3 +2006,19 @@ def safe_path_leaf(path): f"The provided path '{path}' is not safe. The file is outside the MEDIA_ROOT '{base_path}' base path!" ) return fullpath + + +def import_class_module(full_class_string): + """ + Dynamically load a class from a string + + >>> klass = load_class("module.submodule.ClassName") + >>> klass2 = load_class("myfile.Class2") + """ + try: + module_path, class_name = full_class_string.rsplit(".", 1) + module = importlib.import_module(module_path) + class_obj = getattr(module, class_name) + return class_obj + except Exception: + return None diff --git a/pavement.py b/pavement.py index 8ef01a09492..f27d9d3c5de 100644 --- a/pavement.py +++ b/pavement.py @@ -521,7 +521,7 @@ def start_django(options): bind = options.get("bind", "0.0.0.0:8000") port = bind.split(":")[1] foreground = "" if options.get("foreground", False) else "&" - sh(f"{settings} python -W ignore manage.py runserver --nostatic {bind} {foreground}") + sh(f"{settings} python -W ignore manage.py runserver {bind} {foreground}") if ASYNC_SIGNALS: sh( @@ -784,7 +784,7 @@ def test_integration(options): foreground = "" if options.get("foreground", False) else "&" sh(f"DJANGO_SETTINGS_MODULE={settings} python -W ignore manage.py runmessaging {foreground}") sh( - f"DJANGO_SETTINGS_MODULE={settings} python -W ignore manage.py runserver --nostatic {bind} {foreground}" + f"DJANGO_SETTINGS_MODULE={settings} python -W ignore manage.py runserver {bind} {foreground}" ) sh("sleep 30") settings = f"REUSE_DB=1 DJANGO_SETTINGS_MODULE={settings}" From 892f4f4467200e0e119894bad5ce539bb8e05e50 Mon Sep 17 00:00:00 2001 From: Giovanni Allegri Date: Mon, 10 Jul 2023 12:27:53 +0200 Subject: [PATCH 040/111] Docker compose uses the published image (#11238) --- docker-compose-dev.yml | 171 +++++++++++++++++++++++++++++++++++++++++ docker-compose.yml | 6 +- 2 files changed, 172 insertions(+), 5 deletions(-) create mode 100644 docker-compose-dev.yml diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml new file mode 100644 index 00000000000..1ba2fec619f --- /dev/null +++ b/docker-compose-dev.yml @@ -0,0 +1,171 @@ +version: '3.9' + +# Common Django template for GeoNode and Celery services below +x-common-django: + &default-common-django + image: geonode:local + restart: on-failure + env_file: + - .env + volumes: + # - '.:/usr/src/geonode' + - statics:/mnt/volumes/statics + - geoserver-data-dir:/geoserver_data/data + - backup-restore:/backup_restore + - data:/data + - tmp:/tmp + depends_on: + db: + condition: service_healthy + geoserver: + condition: service_healthy + +services: + + # Our custom django application. It includes Geonode. + django: + << : *default-common-django + build: + context: ./ + dockerfile: Dockerfile + container_name: django4${COMPOSE_PROJECT_NAME} + healthcheck: + test: "curl --fail --silent --write-out 'HTTP CODE : %{http_code}\n' --output /dev/null http://127.0.0.1:8001/" + start_period: 60s + interval: 60s + timeout: 10s + retries: 10 + environment: + - IS_CELERY=False + entrypoint: ["/usr/src/geonode/entrypoint.sh"] + command: "uwsgi --ini /usr/src/geonode/uwsgi.ini" + + # Celery worker that executes celery tasks created by Django. + celery: + << : *default-common-django + image: geonode:local + container_name: celery4${COMPOSE_PROJECT_NAME} + depends_on: + - django + environment: + - IS_CELERY=True + entrypoint: ["/usr/src/geonode/entrypoint.sh"] + command: "celery-cmd" + + # Nginx is serving django static and media files and proxies to django and geonode + geonode: + image: geonode/nginx:4.0 + build: ./scripts/docker/nginx/ + container_name: nginx4${COMPOSE_PROJECT_NAME} + environment: + - HTTPS_HOST=${HTTPS_HOST} + - HTTP_HOST=${HTTP_HOST} + - HTTPS_PORT=${HTTPS_PORT} + - HTTP_PORT=${HTTP_PORT} + - LETSENCRYPT_MODE=${LETSENCRYPT_MODE} + - RESOLVER=127.0.0.11 + ports: + - "${HTTP_PORT}:80" + - "${HTTPS_PORT}:443" + volumes: + - nginx-confd:/etc/nginx + - nginx-certificates:/geonode-certificates + - statics:/mnt/volumes/statics + restart: on-failure + + # Gets and installs letsencrypt certificates + letsencrypt: + image: geonode/letsencrypt:4.0 + build: ./scripts/docker/letsencrypt/ + container_name: letsencrypt4${COMPOSE_PROJECT_NAME} + environment: + - HTTPS_HOST=${HTTPS_HOST} + - HTTP_HOST=${HTTP_HOST} + - ADMIN_EMAIL=${ADMIN_EMAIL} + - LETSENCRYPT_MODE=${LETSENCRYPT_MODE} + volumes: + - nginx-certificates:/geonode-certificates + restart: on-failure + + # Geoserver backend + geoserver: + image: geonode/geoserver:2.23.0 + container_name: geoserver4${COMPOSE_PROJECT_NAME} + healthcheck: + test: "curl --fail --silent --write-out 'HTTP CODE : %{http_code}\n' --output /dev/null http://127.0.0.1:8080/geoserver/ows" + start_period: 60s + interval: 60s + timeout: 10s + retries: 10 + env_file: + - .env + volumes: + - statics:/mnt/volumes/statics + - geoserver-data-dir:/geoserver_data/data + - backup-restore:/backup_restore + - data:/data + - tmp:/tmp + restart: on-failure + depends_on: + db: + condition: service_healthy + data-dir-conf: + condition: service_healthy + + data-dir-conf: + image: geonode/geoserver_data:2.23.0 + container_name: gsconf4${COMPOSE_PROJECT_NAME} + entrypoint: sleep infinity + volumes: + - geoserver-data-dir:/geoserver_data/data + restart: on-failure + healthcheck: + test: "ls -A '/geoserver_data/data' | wc -l" + + # PostGIS database. + db: + # use geonode official postgis 13 image + image: geonode/postgis:13 + command: postgres -c "max_connections=${POSTGRESQL_MAX_CONNECTIONS}" + container_name: db4${COMPOSE_PROJECT_NAME} + env_file: + - .env + volumes: + - dbdata:/var/lib/postgresql/data + - dbbackups:/pg_backups + restart: on-failure + healthcheck: + test: "pg_isready -d postgres -U postgres" + # uncomment to enable remote connections to postgres + #ports: + # - "5432:5432" + + # Vanilla RabbitMQ service. This is needed by celery + rabbitmq: + image: rabbitmq:3.7-alpine + container_name: rabbitmq4${COMPOSE_PROJECT_NAME} + volumes: + - rabbitmq:/var/lib/rabbitmq + restart: on-failure + +volumes: + statics: + name: ${COMPOSE_PROJECT_NAME}-statics + nginx-confd: + name: ${COMPOSE_PROJECT_NAME}-nginxconfd + nginx-certificates: + name: ${COMPOSE_PROJECT_NAME}-nginxcerts + geoserver-data-dir: + name: ${COMPOSE_PROJECT_NAME}-gsdatadir + dbdata: + name: ${COMPOSE_PROJECT_NAME}-dbdata + dbbackups: + name: ${COMPOSE_PROJECT_NAME}-dbbackups + backup-restore: + name: ${COMPOSE_PROJECT_NAME}-backup-restore + data: + name: ${COMPOSE_PROJECT_NAME}-data + tmp: + name: ${COMPOSE_PROJECT_NAME}-tmp + rabbitmq: + name: ${COMPOSE_PROJECT_NAME}-rabbitmq diff --git a/docker-compose.yml b/docker-compose.yml index a4fc63d9b88..3af97ca2ebc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,7 +3,7 @@ version: '3.9' # Common Django template for GeoNode and Celery services below x-common-django: &default-common-django - image: geonode/geonode:4.0 + image: geonode/geonode:latest-ubuntu-22.10 restart: on-failure env_file: - .env @@ -25,9 +25,6 @@ services: # Our custom django application. It includes Geonode. django: << : *default-common-django - build: - context: ./ - dockerfile: Dockerfile container_name: django4${COMPOSE_PROJECT_NAME} healthcheck: test: "curl --fail --silent --write-out 'HTTP CODE : %{http_code}\n' --output /dev/null http://127.0.0.1:8001/" @@ -43,7 +40,6 @@ services: # Celery worker that executes celery tasks created by Django. celery: << : *default-common-django - image: geonode/geonode:4.0 container_name: celery4${COMPOSE_PROJECT_NAME} depends_on: - django From cb2b4c001441fef3418c63417ecb44ca9dcdb688 Mon Sep 17 00:00:00 2001 From: etj Date: Fri, 7 Jul 2023 12:26:30 +0200 Subject: [PATCH 041/111] [#11226] Faceting: improvement: key to filter - step 1 --- geonode/facets/providers/baseinfo.py | 3 ++- geonode/facets/providers/category.py | 3 ++- geonode/facets/providers/keyword.py | 3 ++- geonode/facets/providers/region.py | 3 ++- geonode/facets/providers/thesaurus.py | 3 ++- geonode/facets/providers/users.py | 3 ++- 6 files changed, 12 insertions(+), 6 deletions(-) diff --git a/geonode/facets/providers/baseinfo.py b/geonode/facets/providers/baseinfo.py index b1f55129be3..c1178ed69ec 100644 --- a/geonode/facets/providers/baseinfo.py +++ b/geonode/facets/providers/baseinfo.py @@ -38,7 +38,8 @@ def name(self) -> str: def get_info(self, lang="en") -> dict: return { "name": self.name, - "key": "filter{resource_type.in}", + "key": "filter{resource_type.in}", # deprecated + "filter": "filter{resource_type.in}", "label": "Resource type", "type": FACET_TYPE_BASE, "hierarchical": True, diff --git a/geonode/facets/providers/category.py b/geonode/facets/providers/category.py index e0f24e29044..ef6f3a27ab9 100644 --- a/geonode/facets/providers/category.py +++ b/geonode/facets/providers/category.py @@ -39,7 +39,8 @@ def name(self) -> str: def get_info(self, lang="en") -> dict: return { "name": self.name, - "key": "filter{category.identifier}", + "key": "filter{category.identifier}", # deprecated + "filter": "filter{category.identifier}", "label": "Category", "type": FACET_TYPE_CATEGORY, "hierarchical": False, diff --git a/geonode/facets/providers/keyword.py b/geonode/facets/providers/keyword.py index 4a82e9a3dbc..c63e19ce1ce 100644 --- a/geonode/facets/providers/keyword.py +++ b/geonode/facets/providers/keyword.py @@ -39,7 +39,8 @@ def name(self) -> str: def get_info(self, lang="en") -> dict: return { "name": self.name, - "key": "filter{keywords.slug.in}", + "key": "filter{keywords.slug.in}", # deprecated + "filter": "filter{keywords.slug.in}", "label": "Keyword", "type": FACET_TYPE_KEYWORD, "order": 2, diff --git a/geonode/facets/providers/region.py b/geonode/facets/providers/region.py index a0df25b2ea6..d46e6cd0b4d 100644 --- a/geonode/facets/providers/region.py +++ b/geonode/facets/providers/region.py @@ -39,7 +39,8 @@ def name(self) -> str: def get_info(self, lang="en") -> dict: return { "name": self.name, - "key": "filter{regions.code.in}", + "key": "filter{regions.code.in}", # deprecated + "filter": "filter{regions.code.in}", "label": "Region", "type": FACET_TYPE_PLACE, "hierarchical": False, # source data is hierarchical, but this implementation is flat diff --git a/geonode/facets/providers/thesaurus.py b/geonode/facets/providers/thesaurus.py index d9897f6139d..5b4bb92f387 100644 --- a/geonode/facets/providers/thesaurus.py +++ b/geonode/facets/providers/thesaurus.py @@ -45,7 +45,8 @@ def name(self) -> str: def get_info(self, lang="en") -> dict: return { "name": self._name, - "key": "filter{tkeywords}", + "key": "filter{tkeywords}", # deprecated + "filter": "filter{tkeywords}", "label": self.labels.get(lang, self.label), "is_localized": self.labels.get(lang, None) is not None, "type": FACET_TYPE_THESAURUS, diff --git a/geonode/facets/providers/users.py b/geonode/facets/providers/users.py index aedb2fc06f2..4845cade297 100644 --- a/geonode/facets/providers/users.py +++ b/geonode/facets/providers/users.py @@ -39,7 +39,8 @@ def name(self) -> str: def get_info(self, lang="en") -> dict: return { "name": "owner", - "key": "owner", + "key": "owner", # deprecated + "filter": "owner", "label": "Owner", "type": FACET_TYPE_USER, "hierarchical": False, From 92320381e2ee594f5ca4c6a0698c7ca34ffef7e3 Mon Sep 17 00:00:00 2001 From: etj Date: Fri, 7 Jul 2023 18:03:18 +0200 Subject: [PATCH 042/111] [Fixes #11233] Faceting: improvement: facet configuration --- geonode/facets/models.py | 13 ++++++++----- geonode/facets/providers/baseinfo.py | 9 ++++----- geonode/facets/providers/category.py | 6 ++---- geonode/facets/providers/keyword.py | 5 ++--- geonode/facets/providers/region.py | 6 ++---- geonode/facets/providers/thesaurus.py | 13 +++++++------ geonode/facets/providers/users.py | 6 ++---- geonode/facets/tests.py | 26 +++++++++++++++++++++++++- geonode/facets/views.py | 10 ++++++++++ geonode/settings.py | 18 +++++++++--------- 10 files changed, 71 insertions(+), 41 deletions(-) diff --git a/geonode/facets/models.py b/geonode/facets/models.py index 4beffbb1ac6..1b7f9964f7b 100644 --- a/geonode/facets/models.py +++ b/geonode/facets/models.py @@ -39,6 +39,9 @@ class FacetProvider: Provides access to the facet information and the related topics """ + def __init__(self, **kwargs): + self.config = kwargs.get("config", {}).copy() + def __str__(self): return f"{self.__class__.__name__}[{self.name}]" @@ -51,7 +54,7 @@ def name(self) -> str: """ self.get_info()["name"] - def get_info(self, lang="en") -> dict: + def get_info(self, lang="en", **kwargs) -> dict: """ Get the basic info for this provider, as a dict with these keys: - 'name': the name of the provider (the one returned by name()) @@ -127,10 +130,10 @@ def _load_facets_configuration(self) -> None: logger.info("Initializing Facets") - if providers := getattr(settings, "FACET_PROVIDERS", []): - _providers = [import_string(module_path) for module_path in providers] - for provider in _providers: - provider.register(self) + for providerconf in getattr(settings, "FACET_PROVIDERS", []): + clz = providerconf["class"] + provider = import_string(clz) + provider.register(self, config=providerconf.get("config", {})) def register_facet_provider(self, provider: FacetProvider): logger.info(f"Registering {provider}") diff --git a/geonode/facets/providers/baseinfo.py b/geonode/facets/providers/baseinfo.py index c1178ed69ec..7a3ee9b1ffb 100644 --- a/geonode/facets/providers/baseinfo.py +++ b/geonode/facets/providers/baseinfo.py @@ -35,7 +35,7 @@ class ResourceTypeFacetProvider(FacetProvider): def name(self) -> str: return "resourcetype" - def get_info(self, lang="en") -> dict: + def get_info(self, lang="en", **kwargs) -> dict: return { "name": self.name, "key": "filter{resource_type.in}", # deprecated @@ -43,7 +43,6 @@ def get_info(self, lang="en") -> dict: "label": "Resource type", "type": FACET_TYPE_BASE, "hierarchical": True, - "order": 0, } def get_facet_items( @@ -92,7 +91,7 @@ def get_facet_items( @classmethod def register(cls, registry, **kwargs) -> None: - registry.register_facet_provider(ResourceTypeFacetProvider()) + registry.register_facet_provider(ResourceTypeFacetProvider(**kwargs)) class FeaturedFacetProvider(FacetProvider): @@ -104,7 +103,7 @@ class FeaturedFacetProvider(FacetProvider): def name(self) -> str: return "featured" - def get_info(self, lang="en") -> dict: + def get_info(self, lang="en", **kwargs) -> dict: return { "name": self.name, "key": "filter{featured}", @@ -145,4 +144,4 @@ def get_facet_items( @classmethod def register(cls, registry, **kwargs) -> None: - registry.register_facet_provider(FeaturedFacetProvider()) + registry.register_facet_provider(FeaturedFacetProvider(**kwargs)) diff --git a/geonode/facets/providers/category.py b/geonode/facets/providers/category.py index ef6f3a27ab9..9e027dec1e9 100644 --- a/geonode/facets/providers/category.py +++ b/geonode/facets/providers/category.py @@ -36,15 +36,13 @@ class CategoryFacetProvider(FacetProvider): def name(self) -> str: return "category" - def get_info(self, lang="en") -> dict: + def get_info(self, lang="en", **kwargs) -> dict: return { "name": self.name, "key": "filter{category.identifier}", # deprecated "filter": "filter{category.identifier}", "label": "Category", "type": FACET_TYPE_CATEGORY, - "hierarchical": False, - "order": 2, } def get_facet_items( @@ -99,4 +97,4 @@ def get_topics(self, keys: list, lang="en", **kwargs) -> list: @classmethod def register(cls, registry, **kwargs) -> None: - registry.register_facet_provider(CategoryFacetProvider()) + registry.register_facet_provider(CategoryFacetProvider(**kwargs)) diff --git a/geonode/facets/providers/keyword.py b/geonode/facets/providers/keyword.py index c63e19ce1ce..570f2e04cbb 100644 --- a/geonode/facets/providers/keyword.py +++ b/geonode/facets/providers/keyword.py @@ -36,14 +36,13 @@ class KeywordFacetProvider(FacetProvider): def name(self) -> str: return "keyword" - def get_info(self, lang="en") -> dict: + def get_info(self, lang="en", **kwargs) -> dict: return { "name": self.name, "key": "filter{keywords.slug.in}", # deprecated "filter": "filter{keywords.slug.in}", "label": "Keyword", "type": FACET_TYPE_KEYWORD, - "order": 2, } def get_facet_items( @@ -94,4 +93,4 @@ def get_topics(self, keys: list, lang="en", **kwargs) -> list: @classmethod def register(cls, registry, **kwargs) -> None: - registry.register_facet_provider(KeywordFacetProvider()) + registry.register_facet_provider(KeywordFacetProvider(**kwargs)) diff --git a/geonode/facets/providers/region.py b/geonode/facets/providers/region.py index d46e6cd0b4d..a4935d35468 100644 --- a/geonode/facets/providers/region.py +++ b/geonode/facets/providers/region.py @@ -36,15 +36,13 @@ class RegionFacetProvider(FacetProvider): def name(self) -> str: return "region" - def get_info(self, lang="en") -> dict: + def get_info(self, lang="en", **kwargs) -> dict: return { "name": self.name, "key": "filter{regions.code.in}", # deprecated "filter": "filter{regions.code.in}", "label": "Region", "type": FACET_TYPE_PLACE, - "hierarchical": False, # source data is hierarchical, but this implementation is flat - "order": 2, } def get_facet_items( @@ -95,4 +93,4 @@ def get_topics(self, keys: list, lang="en", **kwargs) -> list: @classmethod def register(cls, registry, **kwargs) -> None: - registry.register_facet_provider(RegionFacetProvider()) + registry.register_facet_provider(RegionFacetProvider(**kwargs)) diff --git a/geonode/facets/providers/thesaurus.py b/geonode/facets/providers/thesaurus.py index 5b4bb92f387..91823f00ac4 100644 --- a/geonode/facets/providers/thesaurus.py +++ b/geonode/facets/providers/thesaurus.py @@ -32,17 +32,20 @@ class ThesaurusFacetProvider(FacetProvider): Implements faceting for a given Thesaurus """ - def __init__(self, identifier, title, order, labels: dict): + def __init__(self, identifier, title, order, labels: dict, **kwargs): + super().__init__(**kwargs) + self._name = identifier self.label = title - self.order = order self.labels = labels + self.config["order"] = order + @property def name(self) -> str: return self._name - def get_info(self, lang="en") -> dict: + def get_info(self, lang="en", **kwargs) -> dict: return { "name": self._name, "key": "filter{tkeywords}", # deprecated @@ -50,8 +53,6 @@ def get_info(self, lang="en") -> dict: "label": self.labels.get(lang, self.label), "is_localized": self.labels.get(lang, None) is not None, "type": FACET_TYPE_THESAURUS, - "hierarchical": False, - "order": self.order, } def get_facet_items( @@ -153,5 +154,5 @@ def register(cls, registry, **kwargs) -> None: logger.info("Creating providers for %r", ret) for t in ret.values(): registry.register_facet_provider( - ThesaurusFacetProvider(t["identifier"], t["title"], t["order"], t["labels"]) + ThesaurusFacetProvider(t["identifier"], t["title"], t["order"], t["labels"], **kwargs) ) diff --git a/geonode/facets/providers/users.py b/geonode/facets/providers/users.py index 4845cade297..a6399952868 100644 --- a/geonode/facets/providers/users.py +++ b/geonode/facets/providers/users.py @@ -36,15 +36,13 @@ class OwnerFacetProvider(FacetProvider): def name(self) -> str: return "owner" - def get_info(self, lang="en") -> dict: + def get_info(self, lang="en", **kwargs) -> dict: return { "name": "owner", "key": "owner", # deprecated "filter": "owner", "label": "Owner", "type": FACET_TYPE_USER, - "hierarchical": False, - "order": 5, } def get_facet_items( @@ -95,4 +93,4 @@ def get_topics(self, keys: list, lang="en", **kwargs) -> list: @classmethod def register(cls, registry, **kwargs) -> None: - registry.register_facet_provider(OwnerFacetProvider()) + registry.register_facet_provider(OwnerFacetProvider(**kwargs)) diff --git a/geonode/facets/tests.py b/geonode/facets/tests.py index cc6d9a8e0ec..50a7cef842c 100644 --- a/geonode/facets/tests.py +++ b/geonode/facets/tests.py @@ -69,7 +69,7 @@ def _create_thesauri(cls): cls.thesauri_k = {} for tn in range(2): - t = Thesaurus.objects.create(identifier=f"t_{tn}", title=f"Thesaurus {tn}") + t = Thesaurus.objects.create(identifier=f"t_{tn}", title=f"Thesaurus {tn}", order=100 + tn * 10) cls.thesauri[tn] = t for tl in ( "en", @@ -335,6 +335,30 @@ def test_prefiltering(self): self.assertEqual(totals, obj["topics"]["total"], f"Bad totals for facet '{facet} and filter {filters}") self.assertEqual(count0, obj["topics"]["items"][0]["count"], f"Bad count0 for facet '{facet}") + def test_config(self): + for facet, type, order in ( + ("resourcetype", None, None), + ("t_0", "select", 100), + ("category", "select", 5), + ("region", "select", 7), + ("owner", "select", 8), + ): + req = self.rf.get(reverse("get_facet", args=[facet]), data={"include_config": True}) + res: JsonResponse = views.get_facet(req, facet) + obj = json.loads(res.content) + self.assertIn("config", obj, "Config info not found in payload") + conf = obj["config"] + + if type is None: + self.assertNotIn("type", conf) + else: + self.assertEqual(type, conf["type"], "Unexpected type") + + if order is None: + self.assertNotIn("order", conf) + else: + self.assertEqual(order, conf["order"], "Unexpected order") + def test_user_auth(self): # make sure the user authorization pre-filters the visible resources # TODO test diff --git a/geonode/facets/views.py b/geonode/facets/views.py index 717eabfc57a..e3a8185ae4c 100644 --- a/geonode/facets/views.py +++ b/geonode/facets/views.py @@ -38,6 +38,7 @@ PARAM_LANG = "lang" PARAM_ADD_LINKS = "add_links" PARAM_INCLUDE_TOPICS = "include_topics" +PARAM_INCLUDE_CONFIG = "include_config" PARAM_TOPIC_CONTAINS = "topic_contains" logger = logging.getLogger(__name__) @@ -49,12 +50,17 @@ def list_facets(request, **kwargs): lang, lang_requested = _resolve_language(request) add_links = _resolve_boolean(request, PARAM_ADD_LINKS, False) include_topics = _resolve_boolean(request, PARAM_INCLUDE_TOPICS, False) + include_config = _resolve_boolean(request, PARAM_INCLUDE_CONFIG, False) facets = [] for provider in facet_registry.get_providers(): logger.debug("Fetching data from provider %r", provider) info = provider.get_info(lang=lang) + + if include_config: + info["config"] = provider.config + if add_links: link_args = {PARAM_ADD_LINKS: True} if lang_requested: # only add lang param if specified in current call @@ -83,11 +89,15 @@ def get_facet(request, facet): # parse some query params lang, lang_requested = _resolve_language(request) add_link = _resolve_boolean(request, PARAM_ADD_LINKS, False) + include_config = _resolve_boolean(request, PARAM_INCLUDE_CONFIG, False) + topic_contains = request.GET.get(PARAM_TOPIC_CONTAINS, None) page = int(request.GET.get(PARAM_PAGE, 0)) page_size = int(request.GET.get(PARAM_PAGE_SIZE, DEFAULT_FACET_PAGE_SIZE)) info = provider.get_info(lang) + if include_config: + info["config"] = provider.config qs = _prefilter_topics(request) topics = _get_topics( diff --git a/geonode/settings.py b/geonode/settings.py index 14b58fec600..4a90f085e68 100644 --- a/geonode/settings.py +++ b/geonode/settings.py @@ -2335,12 +2335,12 @@ def get_geonode_catalogue_service(): INSTALLED_APPS += ("geonode.facets",) GEONODE_APPS += ("geonode.facets",) -FACET_PROVIDERS = ( - "geonode.facets.providers.baseinfo.ResourceTypeFacetProvider", - "geonode.facets.providers.baseinfo.FeaturedFacetProvider", - "geonode.facets.providers.category.CategoryFacetProvider", - "geonode.facets.providers.keyword.KeywordFacetProvider", - "geonode.facets.providers.users.OwnerFacetProvider", - "geonode.facets.providers.thesaurus.ThesaurusFacetProvider", - "geonode.facets.providers.region.RegionFacetProvider", -) +FACET_PROVIDERS = [ + {"class": "geonode.facets.providers.baseinfo.ResourceTypeFacetProvider"}, + {"class": "geonode.facets.providers.baseinfo.FeaturedFacetProvider"}, + {"class": "geonode.facets.providers.category.CategoryFacetProvider", "config": {"order": 5, "type": "select"}}, + {"class": "geonode.facets.providers.keyword.KeywordFacetProvider", "config": {"order": 6, "type": "select"}}, + {"class": "geonode.facets.providers.region.RegionFacetProvider", "config": {"order": 7, "type": "select"}}, + {"class": "geonode.facets.providers.users.OwnerFacetProvider", "config": {"order": 8, "type": "select"}}, + {"class": "geonode.facets.providers.thesaurus.ThesaurusFacetProvider", "config": {"type": "select"}}, +] From 2a0eedbf28ea0b06f2c4c8feff94a321298df079 Mon Sep 17 00:00:00 2001 From: Giovanni Allegri Date: Tue, 11 Jul 2023 12:18:29 +0200 Subject: [PATCH 043/111] [Fixes #11246] Create thumbnails for remote documents (#11247) * Create thumbnails for remote documents * removed unused import * adop storage manager and rendere remote pdf * tests for remote doc thumbanil * remove unused requests * fix document type checks --- geonode/documents/models.py | 6 ++-- geonode/documents/tasks.py | 57 +++++++++++++++++++++++++------------ geonode/documents/tests.py | 18 +++++++++++- geonode/resource/manager.py | 2 +- geonode/storage/manager.py | 2 +- 5 files changed, 61 insertions(+), 24 deletions(-) diff --git a/geonode/documents/models.py b/geonode/documents/models.py index e9a81dac1dc..402194f9908 100644 --- a/geonode/documents/models.py +++ b/geonode/documents/models.py @@ -116,17 +116,17 @@ def mime_type(self): @property def is_audio(self): AUDIOTYPES = [_e for _e, _t in DOCUMENT_TYPE_MAP.items() if _t == "audio"] - return self.is_file and self.extension.lower() in AUDIOTYPES + return self.extension and self.extension.lower() in AUDIOTYPES @property def is_image(self): IMGTYPES = [_e for _e, _t in DOCUMENT_TYPE_MAP.items() if _t == "image"] - return self.is_file and self.extension.lower() in IMGTYPES + return self.extension and self.extension.lower() in IMGTYPES @property def is_video(self): VIDEOTYPES = [_e for _e, _t in DOCUMENT_TYPE_MAP.items() if _t == "video"] - return self.is_file and self.extension.lower() in VIDEOTYPES + return self.extension and self.extension.lower() in VIDEOTYPES @property def class_name(self): diff --git a/geonode/documents/tasks.py b/geonode/documents/tasks.py index a5ddb247a19..e0ed9617354 100644 --- a/geonode/documents/tasks.py +++ b/geonode/documents/tasks.py @@ -25,7 +25,7 @@ from celery.utils.log import get_task_logger from geonode.celery_app import app -from geonode.storage.manager import storage_manager +from geonode.storage.manager import StorageManager from ..base.models import ResourceBase from .models import Document @@ -90,6 +90,8 @@ def create_document_thumbnail(self, object_id): """ logger.debug(f"Generating thumbnail for document #{object_id}.") + storage_manager = StorageManager() + try: document = Document.objects.get(id=object_id) except Document.DoesNotExist: @@ -98,33 +100,52 @@ def create_document_thumbnail(self, object_id): image_file = None thumbnail_content = None + remove_tmp_file = False centering = (0.5, 0.5) - if document.is_image: - dname = storage_manager.path(document.files[0]) - if storage_manager.exists(dname): - image_file = storage_manager.open(dname, "rb") + doc_path = None + if document.files: + doc_path = storage_manager.path(document.files[0]) + elif document.doc_url: + doc_path = document.doc_url + remove_tmp_file = True + if document.is_image: try: - image = Image.open(image_file) - with io.BytesIO() as output: - image.save(output, format="PNG") - thumbnail_content = output.getvalue() - output.close() + image_file = storage_manager.open(doc_path) except Exception as e: - logger.debug(f"Could not generate thumbnail: {e}") - finally: - if image_file is not None: - image_file.close() - - elif doc_renderer.supports(document.files[0]): + logger.debug(f"Could not generate thumbnail from remote document {document.doc_url}: {e}") + + if image_file: + try: + image = Image.open(image_file) + with io.BytesIO() as output: + image.save(output, format="PNG") + thumbnail_content = output.getvalue() + output.close() + except Exception as e: + logger.debug(f"Could not generate thumbnail: {e}") + finally: + if image_file is not None: + image_file.close() + if remove_tmp_file: + storage_manager.delete(doc_path) + + elif doc_renderer.supports(doc_path): + # in case it's a remote document we want to retrieve it first + if document.doc_url: + doc_path = storage_manager.open(doc_path).name + remove_tmp_file = True try: - thumbnail_content = doc_renderer.render(document.files[0]) - preferred_centering = doc_renderer.preferred_crop_centering(document.files[0]) + thumbnail_content = doc_renderer.render(doc_path) + preferred_centering = doc_renderer.preferred_crop_centering(doc_path) if preferred_centering is not None: centering = preferred_centering except Exception as e: print(e) + finally: + if remove_tmp_file: + storage_manager.delete(doc_path) if not thumbnail_content: logger.warning(f"Thumbnail for document #{object_id} empty.") ResourceBase.objects.filter(id=document.id).update(thumbnail_url=None) diff --git a/geonode/documents/tests.py b/geonode/documents/tests.py index 369256492fa..0da85504577 100644 --- a/geonode/documents/tests.py +++ b/geonode/documents/tests.py @@ -253,7 +253,7 @@ def test_non_image_documents_thumbnail(self): finally: Document.objects.filter(title="Non img File Doc").delete() - def test_image_documents_thumbnail(self): + def test_documents_thumbnail(self): self.client.login(username="admin", password="admin") try: # test image doc @@ -274,6 +274,22 @@ def test_image_documents_thumbnail(self): self.assertEqual(file.size, (400, 200)) # check thumbnail qualty and extention self.assertEqual(file.format, "JPEG") + data = { + "title": "Remote img File Doc", + "doc_url": "https://raw.githubusercontent.com/GeoNode/geonode/master/geonode/documents/tests/data/img.gif", + "extension": "gif", + } + with self.settings(THUMBNAIL_SIZE={"width": 400, "height": 200}): + self.client.post(reverse("document_upload"), data=data) + d = Document.objects.get(title="Remote img File Doc") + self.assertIsNotNone(d.thumbnail_url) + thumb_file = os.path.join( + settings.MEDIA_ROOT, f"thumbs/{os.path.basename(urlparse(d.thumbnail_url).path)}" + ) + file = Image.open(thumb_file) + self.assertEqual(file.size, (400, 200)) + # check thumbnail qualty and extention + self.assertEqual(file.format, "JPEG") # test pdf doc with open(os.path.join(f"{self.project_root}", "tests/data/pdf_doc.pdf"), "rb") as f: data = { diff --git a/geonode/resource/manager.py b/geonode/resource/manager.py index 28d52999e5f..38ca1a0e7c3 100644 --- a/geonode/resource/manager.py +++ b/geonode/resource/manager.py @@ -944,7 +944,7 @@ def set_thumbnail( file_name = _generate_thumbnail_name(_resource.get_real_instance()) _resource.save_thumbnail(file_name, thumbnail) else: - if instance and instance.files and isinstance(instance.get_real_instance(), Document): + if instance and isinstance(instance.get_real_instance(), Document): if overwrite or not instance.thumbnail_url: create_document_thumbnail.apply((instance.id,)) self._concrete_resource_manager.set_thumbnail( diff --git a/geonode/storage/manager.py b/geonode/storage/manager.py index f4adf983d2a..421ce6946e3 100644 --- a/geonode/storage/manager.py +++ b/geonode/storage/manager.py @@ -249,7 +249,7 @@ def clone_remote_files(self) -> Mapping: """ Using the data retriever object clone the remote path into a local temporary storage """ - self.data_retriever.get_paths(allow_transfer=True) + return self.data_retriever.get_paths(allow_transfer=True) def get_retrieved_paths(self) -> Mapping: """ From 61f69c458a7f274a4c22960a6268c46644d14a4f Mon Sep 17 00:00:00 2001 From: Giovanni Allegri Date: Tue, 11 Jul 2023 15:58:20 +0200 Subject: [PATCH 044/111] [Fixes #11249] Mark remote documents with sourcetype REMOTE (#11250) * mark remote documents with sourcetype REMOTE * added tests * fix test --- geonode/documents/api/tests.py | 17 +++++++++++++++++ geonode/documents/api/views.py | 2 ++ geonode/documents/tests.py | 16 ++++++++++++++++ geonode/documents/views.py | 6 +++++- 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/geonode/documents/api/tests.py b/geonode/documents/api/tests.py index dbb3b13588b..1951da4e2e1 100644 --- a/geonode/documents/api/tests.py +++ b/geonode/documents/api/tests.py @@ -28,6 +28,7 @@ from geonode import settings from geonode.base.populate_test_data import create_models +from geonode.base.enumerations import SOURCE_TYPE_REMOTE from geonode.documents.models import Document logger = logging.getLogger(__name__) @@ -172,6 +173,22 @@ def test_creation_from_url_should_create_the_doc(self): created_doc_url = actual.json().get("document", {}).get("doc_url", "") self.assertEqual(created_doc_url, doc_url) + def test_remote_document_is_marked_remote(self): + """Tests creating an external document set its sourcetype to REMOTE.""" + self.client.force_login(self.admin) + doc_url = "https://example.com/image" + payload = { + "document": { + "title": "A remote document is remote", + "doc_url": doc_url, + "extension": "jpeg", + } + } + actual = self.client.post(self.url, data=payload, format="json") + self.assertEqual(201, actual.status_code) + created_sourcetype = actual.json().get("document", {}).get("sourcetype", "") + self.assertEqual(created_sourcetype, SOURCE_TYPE_REMOTE) + def test_either_path_or_url_doc(self): """ If file_path is not available, should raise error diff --git a/geonode/documents/api/views.py b/geonode/documents/api/views.py index 05159272363..720cb7639ba 100644 --- a/geonode/documents/api/views.py +++ b/geonode/documents/api/views.py @@ -31,6 +31,7 @@ from geonode.base.api.filters import DynamicSearchFilter, ExtentFilter from geonode.base.api.pagination import GeoNodeApiPagination from geonode.base.api.permissions import UserHasPerms +from geonode.base import enumerations from geonode.documents.api.exceptions import DocumentException from geonode.documents.models import Document @@ -123,6 +124,7 @@ def perform_create(self, serializer): payload["files"] = [manager.get_retrieved_paths().get("base_file")] if doc_url: payload["doc_url"] = doc_url + payload["sourcetype"] = enumerations.SOURCE_TYPE_REMOTE resource = serializer.save(**payload) diff --git a/geonode/documents/tests.py b/geonode/documents/tests.py index 0da85504577..8bf91a88529 100644 --- a/geonode/documents/tests.py +++ b/geonode/documents/tests.py @@ -47,6 +47,7 @@ from geonode.layers.models import Dataset from geonode.compat import ensure_string from geonode.base.models import License, Region +from geonode.base.enumerations import SOURCE_TYPE_REMOTE from geonode.documents import DocumentsAppConfig from geonode.resource.manager import resource_manager from geonode.documents.forms import DocumentFormMixin @@ -137,6 +138,21 @@ def test_create_document_with_rel(self, thumb): self.assertEqual(Document.objects.get(pk=c.id).title, "theimg") self.assertEqual(DocumentResourceLink.objects.get(pk=_d.id).object_id, m.id) + def test_remote_document_is_marked_remote(self): + """Tests creating an external document set its sourcetype to REMOTE.""" + self.client.login(username="admin", password="admin") + form_data = { + "title": "A remote document through form is remote", + "doc_url": "http://www.geonode.org/map.pdf", + } + + response = self.client.post(reverse("document_upload"), data=form_data) + + self.assertEqual(response.status_code, 302) + + d = Document.objects.get(title="A remote document through form is remote") + self.assertEqual(d.sourcetype, SOURCE_TYPE_REMOTE) + def test_create_document_url(self): """Tests creating an external document instead of a file.""" diff --git a/geonode/documents/views.py b/geonode/documents/views.py index 3ba50d2f6bc..ed4355fcee2 100644 --- a/geonode/documents/views.py +++ b/geonode/documents/views.py @@ -51,6 +51,7 @@ from geonode.security.utils import get_user_visible_groups, AdvancedSecurityWorkflowManager from geonode.base.forms import CategoryForm, TKeywordForm, ThesaurusAvailableForm from geonode.base.models import Thesaurus, TopicCategory +from geonode.base import enumerations from .utils import get_download_response @@ -184,7 +185,10 @@ def form_valid(self, form): None, resource_type=Document, defaults=dict( - owner=self.request.user, doc_url=doc_form.pop("doc_url", None), title=doc_form.pop("title", None) + owner=self.request.user, + doc_url=doc_form.pop("doc_url", None), + title=doc_form.pop("title", None), + sourcetype=enumerations.SOURCE_TYPE_REMOTE, ), ) From 0842b134ece127c7a5fc54f732041f98a1c6b1ab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Jul 2023 08:42:18 +0200 Subject: [PATCH 045/111] Bump jsonschema from 4.17.3 to 4.18.0 (#11245) * Bump jsonschema from 4.17.3 to 4.18.0 Bumps [jsonschema](https://github.com/python-jsonschema/jsonschema) from 4.17.3 to 4.18.0. - [Release notes](https://github.com/python-jsonschema/jsonschema/releases) - [Changelog](https://github.com/python-jsonschema/jsonschema/blob/main/CHANGELOG.rst) - [Commits](https://github.com/python-jsonschema/jsonschema/compare/v4.17.3...v4.18.0) --- updated-dependencies: - dependency-name: jsonschema dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 022221a3a3a..e6b588f93db 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,7 +20,7 @@ vine==5.0.0 tqdm==4.65.0 Deprecated==1.2.14 wrapt==1.15.0 -jsonschema==4.17.3 +jsonschema==4.18.0 zipstream-new==1.1.8 schema==0.7.5 rdflib==6.3.2 diff --git a/setup.cfg b/setup.cfg index d30ae2d8c54..abfdb1a0903 100644 --- a/setup.cfg +++ b/setup.cfg @@ -46,7 +46,7 @@ install_requires = tqdm==4.65.0 Deprecated==1.2.14 wrapt==1.15.0 - jsonschema==4.17.3 + jsonschema==4.18.0 zipstream-new==1.1.8 schema==0.7.5 rdflib==6.3.2 From 84e85290cfc4b08b5f4a990dc23c74d492ffab0f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Jul 2023 08:57:28 +0200 Subject: [PATCH 046/111] Bump google-cloud-core from 2.3.2 to 2.3.3 (#11244) * Bump google-cloud-core from 2.3.2 to 2.3.3 Bumps [google-cloud-core](https://github.com/googleapis/python-cloud-core) from 2.3.2 to 2.3.3. - [Release notes](https://github.com/googleapis/python-cloud-core/releases) - [Changelog](https://github.com/googleapis/python-cloud-core/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/python-cloud-core/compare/v2.3.2...v2.3.3) --- updated-dependencies: - dependency-name: google-cloud-core dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index e6b588f93db..0669008598a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -112,7 +112,7 @@ django-bootstrap3-datetimepicker-2==2.8.3 django-storages==1.13.2 dropbox==11.36.2 google-cloud-storage==2.10.0 -google-cloud-core==2.3.2 +google-cloud-core==2.3.3 boto3==1.26.165 # Django Caches diff --git a/setup.cfg b/setup.cfg index abfdb1a0903..6719672bdbb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -137,7 +137,7 @@ install_requires = django-storages==1.13.2 dropbox==11.36.2 google-cloud-storage==2.10.0 - google-cloud-core==2.3.2 + google-cloud-core==2.3.3 boto3==1.26.165 # Django Caches From d779c443e5b7fc0c109aa12edb59bc248e8f57b0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Jul 2023 08:57:55 +0200 Subject: [PATCH 047/111] Bump lxml from 4.9.2 to 4.9.3 (#11243) * Bump lxml from 4.9.2 to 4.9.3 Bumps [lxml](https://github.com/lxml/lxml) from 4.9.2 to 4.9.3. - [Release notes](https://github.com/lxml/lxml/releases) - [Changelog](https://github.com/lxml/lxml/blob/master/CHANGES.txt) - [Commits](https://github.com/lxml/lxml/compare/lxml-4.9.2...lxml-4.9.3) --- updated-dependencies: - dependency-name: lxml dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 0669008598a..02b05af4d82 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ # native dependencies Pillow==10.0.0 -lxml==4.9.2 +lxml==4.9.3 psycopg2==2.9.6 Django==3.2.20 diff --git a/setup.cfg b/setup.cfg index 6719672bdbb..6351f23d76d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -26,7 +26,7 @@ setup_requires = install_requires = # native dependencies Pillow==10.0.0 - lxml==4.9.2 + lxml==4.9.3 psycopg2==2.9.6 Django==3.2.20 From 2da20b208cb02f2747663453d8137afea7efd240 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Jul 2023 08:58:41 +0200 Subject: [PATCH 048/111] Bump wandb from 0.15.4 to 0.15.5 (#11242) * Bump wandb from 0.15.4 to 0.15.5 Bumps [wandb](https://github.com/wandb/wandb) from 0.15.4 to 0.15.5. - [Release notes](https://github.com/wandb/wandb/releases) - [Changelog](https://github.com/wandb/wandb/blob/main/CHANGELOG.md) - [Commits](https://github.com/wandb/wandb/compare/v0.15.4...v0.15.5) --- updated-dependencies: - dependency-name: wandb dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 02b05af4d82..81494b1bc10 100644 --- a/requirements.txt +++ b/requirements.txt @@ -171,7 +171,7 @@ webdriver_manager==3.8.6 # Security and audit mistune==3.0.1 -wandb==0.15.4 +wandb==0.15.5 protobuf==3.20.3 mako==1.2.4 certifi>=2022.12.7 # not directly required, pinned by Snyk to avoid a vulnerability diff --git a/setup.cfg b/setup.cfg index 6351f23d76d..fc454b6d816 100644 --- a/setup.cfg +++ b/setup.cfg @@ -196,7 +196,7 @@ install_requires = # Security and audit mistune==3.0.1 - wandb==0.15.4 + wandb==0.15.5 protobuf==3.20.3 mako==1.2.4 certifi>=2022.12.7 # not directly required, pinned by Snyk to avoid a vulnerability From 572cde9ada2cd8171056d14d91fcb41dcd9b2b1d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Jul 2023 08:59:03 +0200 Subject: [PATCH 049/111] Bump sqlalchemy from 2.0.17 to 2.0.18 (#11241) * Bump sqlalchemy from 2.0.17 to 2.0.18 Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 2.0.17 to 2.0.18. - [Release notes](https://github.com/sqlalchemy/sqlalchemy/releases) - [Changelog](https://github.com/sqlalchemy/sqlalchemy/blob/main/CHANGES.rst) - [Commits](https://github.com/sqlalchemy/sqlalchemy/commits) --- updated-dependencies: - dependency-name: sqlalchemy dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 81494b1bc10..7cce230a184 100644 --- a/requirements.txt +++ b/requirements.txt @@ -56,7 +56,7 @@ pyjwt==2.7.0 pyproj<3.7.0 OWSLib==0.29.2 pycsw==2.6.1 -SQLAlchemy==2.0.17 # required by PyCSW +SQLAlchemy==2.0.18 # required by PyCSW Shapely==1.8.5.post1 mercantile==1.2.1 geoip2==4.7.0 diff --git a/setup.cfg b/setup.cfg index fc454b6d816..bce71b8a3fb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -82,7 +82,7 @@ install_requires = pyproj<3.7.0 OWSLib==0.29.2 pycsw==2.6.1 - SQLAlchemy==2.0.17 # required by PyCSW + SQLAlchemy==2.0.18 # required by PyCSW Shapely==1.8.5.post1 mercantile==1.2.1 geoip2==4.7.0 From d7747b7ecd1051ca769027a4725ef71a67bfb3cc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Jul 2023 09:00:14 +0200 Subject: [PATCH 050/111] Bump boto3 from 1.26.165 to 1.28.1 (#11240) * Bump boto3 from 1.26.165 to 1.28.1 Bumps [boto3](https://github.com/boto/boto3) from 1.26.165 to 1.28.1. - [Release notes](https://github.com/boto/boto3/releases) - [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/boto3/compare/1.26.165...1.28.1) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 7cce230a184..e2ac13c71bc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -113,7 +113,7 @@ django-storages==1.13.2 dropbox==11.36.2 google-cloud-storage==2.10.0 google-cloud-core==2.3.3 -boto3==1.26.165 +boto3==1.28.1 # Django Caches python-memcached<=1.59 diff --git a/setup.cfg b/setup.cfg index bce71b8a3fb..2dacd3779a2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -138,7 +138,7 @@ install_requires = dropbox==11.36.2 google-cloud-storage==2.10.0 google-cloud-core==2.3.3 - boto3==1.26.165 + boto3==1.28.1 # Django Caches python-memcached<=1.59 From 2285c269296bf0058e2d5d6d2e8b8f02b31b3aee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Jul 2023 09:37:56 +0200 Subject: [PATCH 051/111] Bump black from 23.3.0 to 23.7.0 (#11253) * Bump black from 23.3.0 to 23.7.0 Bumps [black](https://github.com/psf/black) from 23.3.0 to 23.7.0. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/compare/23.3.0...23.7.0) --- updated-dependencies: - dependency-name: black dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index e2ac13c71bc..4539e9f56bd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -153,7 +153,7 @@ invoke==2.1.3 coverage==7.2.7 requests-toolbelt==1.0.0 flake8==6.0.0 -black==23.3.0 +black==23.7.0 pytest==7.4.0 pytest-bdd==6.1.1 splinter==0.19.0 diff --git a/setup.cfg b/setup.cfg index 2dacd3779a2..121bace7dff 100644 --- a/setup.cfg +++ b/setup.cfg @@ -178,7 +178,7 @@ install_requires = coverage==7.2.7 requests-toolbelt==1.0.0 flake8==6.0.0 - black==23.3.0 + black==23.7.0 pytest==7.4.0 pytest-bdd==6.1.1 splinter==0.19.0 From 2315bee4c9504e5a4a2d5173c0a6bc19e98ba654 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Jul 2023 09:38:14 +0200 Subject: [PATCH 052/111] Bump django-cors-headers from 4.1.0 to 4.2.0 (#11254) * Bump django-cors-headers from 4.1.0 to 4.2.0 Bumps [django-cors-headers](https://github.com/adamchainz/django-cors-headers) from 4.1.0 to 4.2.0. - [Changelog](https://github.com/adamchainz/django-cors-headers/blob/main/CHANGELOG.rst) - [Commits](https://github.com/adamchainz/django-cors-headers/compare/4.1.0...4.2.0) --- updated-dependencies: - dependency-name: django-cors-headers dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 4539e9f56bd..17733ac070d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -134,7 +134,7 @@ sherlock==0.4.1 # required by monitoring psutil==5.9.5 -django-cors-headers==4.1.0 +django-cors-headers==4.2.0 user-agents django-user-agents xmljson diff --git a/setup.cfg b/setup.cfg index 121bace7dff..6f5b98c65aa 100644 --- a/setup.cfg +++ b/setup.cfg @@ -159,7 +159,7 @@ install_requires = # required by monitoring psutil==5.9.5 - django-cors-headers==4.1.0 + django-cors-headers==4.2.0 user-agents django-user-agents xmljson From d54d04afa5c1f7e8e4d2d1bea4a6f19051ba7b05 Mon Sep 17 00:00:00 2001 From: Giovanni Allegri Date: Wed, 12 Jul 2023 16:37:22 +0200 Subject: [PATCH 053/111] [Fixes #11256] Assign regions based on contains and overlaps (#11257) * Assign regions based on contains and overlaps * tests * fix E501 --- geonode/base/models.py | 17 +++++++++++++++- geonode/base/tests.py | 41 ++++++++++++++++++++++++++++++++++++++- geonode/resource/utils.py | 10 ++-------- 3 files changed, 58 insertions(+), 10 deletions(-) diff --git a/geonode/base/models.py b/geonode/base/models.py index 6da70b6994c..1efe7288b0d 100644 --- a/geonode/base/models.py +++ b/geonode/base/models.py @@ -41,7 +41,7 @@ from django.contrib.auth import get_user_model from django.db.models.fields.json import JSONField from django.utils.functional import cached_property, classproperty -from django.contrib.gis.geos import Polygon, Point +from django.contrib.gis.geos import GEOSGeometry, Polygon, Point from django.contrib.gis.db.models import PolygonField from django.core.exceptions import ValidationError from django.utils.translation import ugettext_lazy as _ @@ -204,6 +204,21 @@ def geographic_bounding_box(self): """BBOX is in the format: [x0,x1,y0,y1].""" return bbox_to_wkt(self.bbox_x0, self.bbox_x1, self.bbox_y0, self.bbox_y1, srid=self.srid) + @property + def geom(self): + srid, wkt = self.geographic_bounding_box.split(";") + srid = re.findall(r"\d+", srid) + geom = GEOSGeometry(wkt, srid=int(srid[0])) + geom.transform(4326) + return geom + + def is_assignable_to_geom(self, extent_geom: GEOSGeometry): + region_geom = self.geom + + if region_geom.contains(extent_geom) or region_geom.overlaps(extent_geom): + return True + return False + class Meta: ordering = ("name",) verbose_name_plural = "Metadata Regions" diff --git a/geonode/base/tests.py b/geonode/base/tests.py index 7ee9a49b557..3c6cfb9b4f2 100644 --- a/geonode/base/tests.py +++ b/geonode/base/tests.py @@ -48,13 +48,14 @@ Menu, MenuItem, Configuration, + Region, TopicCategory, Thesaurus, ThesaurusKeyword, generate_thesaurus_reference, ) from django.conf import settings -from django.contrib.gis.geos import Polygon +from django.contrib.gis.geos import Polygon, GEOSGeometry from django.template import Template, Context from django.contrib.auth import get_user_model from geonode.storage.manager import storage_manager @@ -1124,3 +1125,41 @@ def test_keyword_raise_db_error(self, add_root_mocked): "Error during the keyword creation for keyword: keyword2", [x.message for x in _log.records], ) + + +class TestRegions(GeoNodeBaseTestSupport): + def setUp(self): + self.dataset_inside_region = GEOSGeometry( + "POLYGON ((-4.01799226543944599 57.18451093931114571, 8.89409253052255622 56.91828238681708285, \ + 9.29343535926363984 47.73339732577194638, -3.75176371294537603 48.13274015451304422, \ + -4.01799226543944599 57.18451093931114571))", + srid=4326, + ) + + self.dataset_overlapping_region = GEOSGeometry( + "POLYGON ((15.28357779038003628 33.6232840435866791, 28.19566258634203848 33.35705549109261625, \ + 28.5950054150831221 24.17217043004747978, 15.54980634287410624 24.57151325878857762, \ + 15.28357779038003628 33.6232840435866791))", + srid=4326, + ) + + self.dataset_outside_region = GEOSGeometry( + "POLYGON ((-3.75176371294537603 23.10725622007123548, 9.16032108301662618 22.84102766757717262, \ + 9.5596639117577098 13.65614260653203615, -3.48553516045130607 14.05548543527313399, \ + -3.75176371294537603 23.10725622007123548))", + srid=4326, + ) + + def test_region_assignment_for_extent(self): + region = Region.objects.get(code="EUR") + + self.assertTrue( + region.is_assignable_to_geom(self.dataset_inside_region), "Extent inside a region shouldn't be assigned" + ) + self.assertTrue( + region.is_assignable_to_geom(self.dataset_overlapping_region), + "Extent overlapping a region should be assigned", + ) + self.assertFalse( + region.is_assignable_to_geom(self.dataset_outside_region), "Extent outside a region should be assigned" + ) diff --git a/geonode/resource/utils.py b/geonode/resource/utils.py index 70dda53f921..ca7175c799b 100644 --- a/geonode/resource/utils.py +++ b/geonode/resource/utils.py @@ -483,13 +483,7 @@ def metadata_post_save(instance, *args, **kwargs): regions_to_add = [] for region in queryset: try: - srid2, wkt2 = region.geographic_bounding_box.split(";") - srid2 = re.findall(r"\d+", srid2) - - poly2 = GEOSGeometry(wkt2, srid=int(srid2[0])) - poly2.transform(4326) - - if not poly2.intersection(poly1).empty: + if region.is_assignable_to_geom(poly1): regions_to_add.append(region) if region.level == 0 and region.parent is None: global_regions.append(region) @@ -498,7 +492,7 @@ def metadata_post_save(instance, *args, **kwargs): if tb: logger.debug(tb) if regions_to_add or global_regions: - if regions_to_add and len(regions_to_add) > 0 and len(regions_to_add) <= 30: + if regions_to_add: instance.regions.add(*regions_to_add) else: instance.regions.add(*global_regions) From 910455379ce0aabbfafc3b7f3b45a82ebdf6b2df Mon Sep 17 00:00:00 2001 From: Emanuele Tajariol Date: Wed, 12 Jul 2023 16:45:16 +0200 Subject: [PATCH 054/111] [Fixes #11259] Faceting: fixes (#11260) --- geonode/facets/providers/keyword.py | 12 +++++++++--- geonode/facets/providers/users.py | 4 ++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/geonode/facets/providers/keyword.py b/geonode/facets/providers/keyword.py index 570f2e04cbb..4d377538a2d 100644 --- a/geonode/facets/providers/keyword.py +++ b/geonode/facets/providers/keyword.py @@ -55,10 +55,16 @@ def get_facet_items( ) -> (int, list): logger.debug("Retrieving facets for %s", self.name) - q = queryset.values("keywords__slug", "keywords__name").filter(keywords__isnull=False) + filters = {"keywords__isnull": False} if topic_contains: - q = q.filter(keywords__name=topic_contains) - q = q.annotate(count=Count("keywords__slug")).order_by("-count") + filters["keywords__name__icontains"] = topic_contains + + q = ( + queryset.filter(**filters) + .values("keywords__slug", "keywords__name") + .annotate(count=Count("keywords__slug")) + .order_by("-count") + ) cnt = q.count() diff --git a/geonode/facets/providers/users.py b/geonode/facets/providers/users.py index a6399952868..80e47c50952 100644 --- a/geonode/facets/providers/users.py +++ b/geonode/facets/providers/users.py @@ -39,8 +39,8 @@ def name(self) -> str: def get_info(self, lang="en", **kwargs) -> dict: return { "name": "owner", - "key": "owner", # deprecated - "filter": "owner", + "key": "filter{owner.pk.in}", # deprecated + "filter": "filter{owner.pk.in}", "label": "Owner", "type": FACET_TYPE_USER, } From b3f3764bdaaf5f58e57bb2a05f0839d2472ef6b5 Mon Sep 17 00:00:00 2001 From: etj Date: Mon, 17 Jul 2023 17:34:11 +0200 Subject: [PATCH 055/111] [Fixes #11273] Faceting: /facets loses some filters along the way --- geonode/facets/tests.py | 56 ++++++++++++++++++++++++++++++++++++++--- geonode/facets/views.py | 4 ++- 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/geonode/facets/tests.py b/geonode/facets/tests.py index 50a7cef842c..127ada7cd7a 100644 --- a/geonode/facets/tests.py +++ b/geonode/facets/tests.py @@ -29,6 +29,7 @@ from geonode.base.models import Thesaurus, ThesaurusLabel, ThesaurusKeyword, ThesaurusKeywordLabel, ResourceBase, Region from geonode.facets.models import facet_registry +from geonode.facets.providers.baseinfo import FeaturedFacetProvider from geonode.facets.providers.region import RegionFacetProvider from geonode.tests.base import GeoNodeBaseTestSupport import geonode.facets.views as views @@ -136,13 +137,13 @@ def _create_resources(self): # RB19 -> T0K0 T0K1 FEAT if x % 2 == 1: - print(f"ADDING KEYWORDS {self.thesauri_k['0_0']} to RB {d}") + logger.debug(f"ADDING KEYWORDS {self.thesauri_k['0_0']} to RB {d}") d.tkeywords.add(self.thesauri_k["0_0"]) if x % 2 == 1 and x > 10: - print(f"ADDING KEYWORDS {self.thesauri_k['0_1']} to RB {d}") + logger.debug(f"ADDING KEYWORDS {self.thesauri_k['0_1']} to RB {d}") d.tkeywords.add(self.thesauri_k["0_1"]) if x < 10: - print(f"ADDING KEYWORDS {self.thesauri_k['1_0']} to RB {d}") + logger.debug(f"ADDING KEYWORDS {self.thesauri_k['1_0']} to RB {d}") d.tkeywords.add(self.thesauri_k["1_0"]) if 7 < x < 13: d.tkeywords.add(self.thesauri_k["1_1"]) @@ -335,6 +336,55 @@ def test_prefiltering(self): self.assertEqual(totals, obj["topics"]["total"], f"Bad totals for facet '{facet} and filter {filters}") self.assertEqual(count0, obj["topics"]["items"][0]["count"], f"Bad count0 for facet '{facet}") + def test_prefiltering_tkeywords(self): + regname = RegionFacetProvider().name + featname = FeaturedFacetProvider().name + t1filter = facet_registry.get_provider("t_1").get_info()["filter"] + tkey_1_1 = self.thesauri_k["1_1"].id + + expected_region = {"R1": 1} + expected_feat = {True: 2, False: 3} + + # Run the single requests + for facet, params, items in ( + (regname, {t1filter: tkey_1_1}, expected_region), + (featname, {t1filter: tkey_1_1}, expected_feat), + ): + req = self.rf.get(reverse("get_facet", args=[facet]), data=params) + res: JsonResponse = views.get_facet(req, facet) + obj = json.loads(res.content) + + self.assertEqual( + len(items), + len(obj["topics"]["items"]), + f"Bad count for items '{facet} \n PARAMS: {params} \n RESULT: {obj} \n EXPECTED: {items}", + ) + # search item + for item in items.keys(): + found = next((i for i in obj["topics"]["items"] if i["key"] == item), None) + self.assertIsNotNone(found, f"Topic '{item}' not found in facet {facet} -- {obj}") + self.assertEqual(items[item], found.get("count", None), f"Bad count for facet '{facet}:{item}") + + # Run the single request + req = self.rf.get(reverse("list_facets"), data={"include_topics": 1, t1filter: tkey_1_1}) + res: JsonResponse = views.list_facets(req) + obj = json.loads(res.content) + + facets_list = obj["facets"] + fmap = self._facets_to_map(facets_list) + + for name, items in ( + (regname, expected_region), + (featname, expected_feat), + ): + self.assertIn(name, fmap) + facet = fmap[name] + + for item in items.keys(): + found = next((i for i in facet["topics"]["items"] if i["key"] == item), None) + self.assertIsNotNone(found, f"Topic '{item}' not found in facet {facet} -- {facet}") + self.assertEqual(items[item], found.get("count", None), f"Bad count for facet '{facet}:{item}") + def test_config(self): for facet, type, order in ( ("resourcetype", None, None), diff --git a/geonode/facets/views.py b/geonode/facets/views.py index e3a8185ae4c..b6e876f1a4d 100644 --- a/geonode/facets/views.py +++ b/geonode/facets/views.py @@ -53,6 +53,7 @@ def list_facets(request, **kwargs): include_config = _resolve_boolean(request, PARAM_INCLUDE_CONFIG, False) facets = [] + prefiltered = None for provider in facet_registry.get_providers(): logger.debug("Fetching data from provider %r", provider) @@ -68,7 +69,8 @@ def list_facets(request, **kwargs): info["link"] = f"{reverse('get_facet', args=[info['name']])}?{urlencode(link_args)}" if include_topics: - info["topics"] = _get_topics(provider, queryset=_prefilter_topics(request), lang=lang) + prefiltered = prefiltered or _prefilter_topics(request) + info["topics"] = _get_topics(provider, queryset=prefiltered, lang=lang) facets.append(info) From 20efc5064ed416f885135e2d71af7a2b8ae0e4dc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Jul 2023 10:00:31 +0200 Subject: [PATCH 056/111] Bump word-wrap from 1.2.3 to 1.2.4 in /geonode/monitoring/frontend (#11276) Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.4. - [Release notes](https://github.com/jonschlinkert/word-wrap/releases) - [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.4) --- updated-dependencies: - dependency-name: word-wrap dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- geonode/monitoring/frontend/yarn.lock | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/geonode/monitoring/frontend/yarn.lock b/geonode/monitoring/frontend/yarn.lock index d8f15f3051a..d0fab64321f 100644 --- a/geonode/monitoring/frontend/yarn.lock +++ b/geonode/monitoring/frontend/yarn.lock @@ -2337,11 +2337,16 @@ core-js@^1.0.0: resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" integrity sha512-ZiPp9pZlgxpWRu0M+YWbm6+aQ84XEfH1JRXvfOc/fILWI0VKhLC2LX13X1NYq4fULzLMq7Hfh43CSo2/aIaUPA== -core-js@^2.4.0, core-js@^2.5.0, core-js@^2.5.1: +core-js@^2.4.0, core-js@^2.5.0: version "2.6.12" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== +core-js@^3.4.2: + version "3.31.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.31.1.tgz#f2b0eea9be9da0def2c5fece71064a7e5d687653" + integrity sha512-2sKLtfq1eFST7l7v62zaqXacPc7uG8ZAya8ogijLhTtaKNcpzpB4TMoTw2Si+8GYKRwFPMMtUT0263QFWFfqyQ== + core-util-is@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -6574,7 +6579,7 @@ react-simple-maps@0.12.1: d3-geo-projection "1.2.2" topojson-client "2.1.0" -react-smooth@^1.0.0: +react-smooth@^1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/react-smooth/-/react-smooth-1.0.6.tgz#18b964f123f7bca099e078324338cd8739346d0a" integrity sha512-B2vL4trGpNSMSOzFiAul9kFAsxTukL9Wyy9EXtkQy3GJr6sZqW9e1nShdVOJ3hRYamPZ94O17r3Q0bjSw3UYtg== @@ -6706,20 +6711,20 @@ recharts-scale@^0.4.2: dependencies: decimal.js-light "^2.4.1" -recharts@1.6.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/recharts/-/recharts-1.6.2.tgz#4ced884f04b680e8dac5d3e109f99b0e7cfb9b0f" - integrity sha512-NqVN8Hq5wrrBthTxQB+iCnZjup1dc+AYRIB6Q9ck9UjdSJTt4PbLepGpudQEYJEN5iIpP/I2vThC4uiTJa7xUQ== +recharts@1.8.6: + version "1.8.6" + resolved "https://registry.yarnpkg.com/recharts/-/recharts-1.8.6.tgz#ba651a4610dbac936da5001f8cb556b36a170410" + integrity sha512-UlfSEOnZRAxxaH33Fc86yHEcqN+IRauPP31NfVvlGudtwVZEIb2RFI5b1J3npQo7XyoSnkUodg3Ha6EupkV+SQ== dependencies: classnames "^2.2.5" - core-js "^2.5.1" + core-js "^3.4.2" d3-interpolate "^1.3.0" d3-scale "^2.1.0" d3-shape "^1.2.0" lodash "^4.17.5" prop-types "^15.6.0" react-resize-detector "^2.3.0" - react-smooth "^1.0.0" + react-smooth "^1.0.5" recharts-scale "^0.4.2" reduce-css-calc "^1.3.0" @@ -8464,9 +8469,9 @@ wide-align@^1.1.0: string-width "^1.0.2 || 2 || 3 || 4" word-wrap@~1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" - integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + version "1.2.4" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.4.tgz#cb4b50ec9aca570abd1f52f33cd45b6c61739a9f" + integrity sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA== wordwrap@~0.0.2: version "0.0.3" From be723297ed6946763e8e091ec821e353fc24194a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Jul 2023 10:36:05 +0200 Subject: [PATCH 057/111] Bump invoke from 2.1.3 to 2.2.0 (#11271) * Bump invoke from 2.1.3 to 2.2.0 Bumps [invoke](https://github.com/pyinvoke/invoke) from 2.1.3 to 2.2.0. - [Commits](https://github.com/pyinvoke/invoke/compare/2.1.3...2.2.0) --- updated-dependencies: - dependency-name: invoke dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 17733ac070d..73b3383050e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -147,7 +147,7 @@ uWSGI==2.0.21 gunicorn==20.1.0 ipython==8.14.0 docker==6.1.3 -invoke==2.1.3 +invoke==2.2.0 # tests coverage==7.2.7 diff --git a/setup.cfg b/setup.cfg index 6f5b98c65aa..054f960c72b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -172,7 +172,7 @@ install_requires = gunicorn==20.1.0 ipython==8.14.0 docker==6.1.3 - invoke==2.1.3 + invoke==2.2.0 # tests coverage==7.2.7 From 61c546f8b2d924a4690399cdb25d95362a88df74 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Jul 2023 10:36:35 +0200 Subject: [PATCH 058/111] Bump boto3 from 1.28.1 to 1.28.3 (#11270) * Bump boto3 from 1.28.1 to 1.28.3 Bumps [boto3](https://github.com/boto/boto3) from 1.28.1 to 1.28.3. - [Release notes](https://github.com/boto/boto3/releases) - [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/boto3/compare/1.28.1...1.28.3) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 73b3383050e..f4277547478 100644 --- a/requirements.txt +++ b/requirements.txt @@ -113,7 +113,7 @@ django-storages==1.13.2 dropbox==11.36.2 google-cloud-storage==2.10.0 google-cloud-core==2.3.3 -boto3==1.28.1 +boto3==1.28.3 # Django Caches python-memcached<=1.59 diff --git a/setup.cfg b/setup.cfg index 054f960c72b..a0680178b17 100644 --- a/setup.cfg +++ b/setup.cfg @@ -138,7 +138,7 @@ install_requires = dropbox==11.36.2 google-cloud-storage==2.10.0 google-cloud-core==2.3.3 - boto3==1.28.1 + boto3==1.28.3 # Django Caches python-memcached<=1.59 From 279ce27a7dc43b950f00be33d3d15207c1c15a58 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Jul 2023 10:36:52 +0200 Subject: [PATCH 059/111] Bump jsonschema from 4.18.0 to 4.18.3 (#11267) * Bump jsonschema from 4.18.0 to 4.18.3 Bumps [jsonschema](https://github.com/python-jsonschema/jsonschema) from 4.18.0 to 4.18.3. - [Release notes](https://github.com/python-jsonschema/jsonschema/releases) - [Changelog](https://github.com/python-jsonschema/jsonschema/blob/main/CHANGELOG.rst) - [Commits](https://github.com/python-jsonschema/jsonschema/compare/v4.18.0...v4.18.3) --- updated-dependencies: - dependency-name: jsonschema dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index f4277547478..3b99937e9da 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,7 +20,7 @@ vine==5.0.0 tqdm==4.65.0 Deprecated==1.2.14 wrapt==1.15.0 -jsonschema==4.18.0 +jsonschema==4.18.3 zipstream-new==1.1.8 schema==0.7.5 rdflib==6.3.2 diff --git a/setup.cfg b/setup.cfg index a0680178b17..061a1917fe3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -46,7 +46,7 @@ install_requires = tqdm==4.65.0 Deprecated==1.2.14 wrapt==1.15.0 - jsonschema==4.18.0 + jsonschema==4.18.3 zipstream-new==1.1.8 schema==0.7.5 rdflib==6.3.2 From e5a47d50c348beb19560ea4ff71941d4212a37ff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Jul 2023 10:38:35 +0200 Subject: [PATCH 060/111] Bump pathvalidate from 3.0.0 to 3.1.0 (#11268) * Bump pathvalidate from 3.0.0 to 3.1.0 Bumps [pathvalidate](https://github.com/thombashi/pathvalidate) from 3.0.0 to 3.1.0. - [Release notes](https://github.com/thombashi/pathvalidate/releases) - [Commits](https://github.com/thombashi/pathvalidate/compare/v3.0.0...v3.1.0) --- updated-dependencies: - dependency-name: pathvalidate dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 3b99937e9da..4dc3fe59083 100644 --- a/requirements.txt +++ b/requirements.txt @@ -26,7 +26,7 @@ schema==0.7.5 rdflib==6.3.2 smart_open==6.3.0 PyMuPDF==1.22.5 -pathvalidate==3.0.0 +pathvalidate==3.1.0 # Django Apps django-allauth==0.54.0 diff --git a/setup.cfg b/setup.cfg index 061a1917fe3..465bbf2b4bd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -52,7 +52,7 @@ install_requires = rdflib==6.3.2 smart_open==6.3.0 PyMuPDF==1.22.5 - pathvalidate==3.0.0 + pathvalidate==3.1.0 # Django Apps django-allauth==0.54.0 From 320892c572a267ed1f2d205af100af8728481325 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Jul 2023 10:39:56 +0200 Subject: [PATCH 061/111] Bump pip from 23.1.2 to 23.2 (#11266) * Bump pip from 23.1.2 to 23.2 Bumps [pip](https://github.com/pypa/pip) from 23.1.2 to 23.2. - [Changelog](https://github.com/pypa/pip/blob/main/NEWS.rst) - [Commits](https://github.com/pypa/pip/compare/23.1.2...23.2) --- updated-dependencies: - dependency-name: pip dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 4dc3fe59083..74ff30058e3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -160,7 +160,7 @@ splinter==0.19.0 pytest-splinter==3.3.2 pytest-django==4.5.2 setuptools>=59.1.1,<68.1.0 -pip==23.1.2 +pip==23.2 Twisted==22.10.0 pixelmatch==0.3.0 factory-boy==3.2.1 diff --git a/setup.cfg b/setup.cfg index 465bbf2b4bd..d4b92a88e3b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -185,7 +185,7 @@ install_requires = pytest-splinter==3.3.2 pytest-django==4.5.2 setuptools>=59.1.1,<68.1.0 - pip==23.1.2 + pip==23.2 Twisted==22.10.0 pixelmatch==0.3.0 factory-boy==3.2.1 From abf6803a104a249421d2636e25f1b60d111adf81 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Jul 2023 10:41:38 +0200 Subject: [PATCH 062/111] Bump sqlalchemy from 2.0.18 to 2.0.19 (#11269) * Bump sqlalchemy from 2.0.18 to 2.0.19 Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 2.0.18 to 2.0.19. - [Release notes](https://github.com/sqlalchemy/sqlalchemy/releases) - [Changelog](https://github.com/sqlalchemy/sqlalchemy/blob/main/CHANGES.rst) - [Commits](https://github.com/sqlalchemy/sqlalchemy/commits) --- updated-dependencies: - dependency-name: sqlalchemy dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 74ff30058e3..4bc6f0e5eff 100644 --- a/requirements.txt +++ b/requirements.txt @@ -56,7 +56,7 @@ pyjwt==2.7.0 pyproj<3.7.0 OWSLib==0.29.2 pycsw==2.6.1 -SQLAlchemy==2.0.18 # required by PyCSW +SQLAlchemy==2.0.19 # required by PyCSW Shapely==1.8.5.post1 mercantile==1.2.1 geoip2==4.7.0 diff --git a/setup.cfg b/setup.cfg index d4b92a88e3b..9b959977265 100644 --- a/setup.cfg +++ b/setup.cfg @@ -82,7 +82,7 @@ install_requires = pyproj<3.7.0 OWSLib==0.29.2 pycsw==2.6.1 - SQLAlchemy==2.0.18 # required by PyCSW + SQLAlchemy==2.0.19 # required by PyCSW Shapely==1.8.5.post1 mercantile==1.2.1 geoip2==4.7.0 From fdaa8ba3b62d9344cd465123d57f37fbcd03e8d4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Jul 2023 11:56:16 +0200 Subject: [PATCH 063/111] Bump pyjwt from 2.7.0 to 2.8.0 (#11280) * Bump pyjwt from 2.7.0 to 2.8.0 Bumps [pyjwt](https://github.com/jpadilla/pyjwt) from 2.7.0 to 2.8.0. - [Release notes](https://github.com/jpadilla/pyjwt/releases) - [Changelog](https://github.com/jpadilla/pyjwt/blob/master/CHANGELOG.rst) - [Commits](https://github.com/jpadilla/pyjwt/compare/2.7.0...2.8.0) --- updated-dependencies: - dependency-name: pyjwt dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 4bc6f0e5eff..9371ba8f79e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -50,7 +50,7 @@ django-widget-tweaks==1.4.12 django-sequences==2.7 oauthlib==3.2.2 pyopenssl==23.2.0 -pyjwt==2.7.0 +pyjwt==2.8.0 # geopython dependencies pyproj<3.7.0 diff --git a/setup.cfg b/setup.cfg index 9b959977265..fa2a1178b7c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -76,7 +76,7 @@ install_requires = django-sequences==2.7 oauthlib==3.2.2 pyopenssl==23.2.0 - pyjwt==2.7.0 + pyjwt==2.8.0 # geopython dependencies pyproj<3.7.0 From b52ac9d1796cafbae69b95879bd0621e91235f12 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Jul 2023 11:56:38 +0200 Subject: [PATCH 064/111] Bump boto3 from 1.28.1 to 1.28.5 (#11279) * Bump sqlalchemy from 2.0.18 to 2.0.19 (#11269) * Bump sqlalchemy from 2.0.18 to 2.0.19 Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 2.0.18 to 2.0.19. - [Release notes](https://github.com/sqlalchemy/sqlalchemy/releases) - [Changelog](https://github.com/sqlalchemy/sqlalchemy/blob/main/CHANGES.rst) - [Commits](https://github.com/sqlalchemy/sqlalchemy/commits) --- updated-dependencies: - dependency-name: sqlalchemy dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani * Bump boto3 from 1.28.1 to 1.28.5 Bumps [boto3](https://github.com/boto/boto3) from 1.28.1 to 1.28.5. - [Release notes](https://github.com/boto/boto3/releases) - [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/boto3/compare/1.28.1...1.28.5) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 9371ba8f79e..17ef9ea54a5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -113,7 +113,7 @@ django-storages==1.13.2 dropbox==11.36.2 google-cloud-storage==2.10.0 google-cloud-core==2.3.3 -boto3==1.28.3 +boto3==1.28.5 # Django Caches python-memcached<=1.59 diff --git a/setup.cfg b/setup.cfg index fa2a1178b7c..3b87a7c6909 100644 --- a/setup.cfg +++ b/setup.cfg @@ -138,7 +138,7 @@ install_requires = dropbox==11.36.2 google-cloud-storage==2.10.0 google-cloud-core==2.3.3 - boto3==1.28.3 + boto3==1.28.5 # Django Caches python-memcached<=1.59 From e780750ae26e879d2cc67a1cbdd577d32621ed4b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Jul 2023 11:56:56 +0200 Subject: [PATCH 065/111] Bump gunicorn from 20.1.0 to 21.1.0 (#11278) * Bump gunicorn from 20.1.0 to 21.1.0 Bumps [gunicorn](https://github.com/benoitc/gunicorn) from 20.1.0 to 21.1.0. - [Release notes](https://github.com/benoitc/gunicorn/releases) - [Commits](https://github.com/benoitc/gunicorn/compare/20.1.0...21.1.0) --- updated-dependencies: - dependency-name: gunicorn dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 17ef9ea54a5..b5b5c9be906 100644 --- a/requirements.txt +++ b/requirements.txt @@ -144,7 +144,7 @@ pycountry # production uWSGI==2.0.21 -gunicorn==20.1.0 +gunicorn==21.1.0 ipython==8.14.0 docker==6.1.3 invoke==2.2.0 diff --git a/setup.cfg b/setup.cfg index 3b87a7c6909..424e3cdc7cf 100644 --- a/setup.cfg +++ b/setup.cfg @@ -169,7 +169,7 @@ install_requires = # production uWSGI==2.0.21 - gunicorn==20.1.0 + gunicorn==21.1.0 ipython==8.14.0 docker==6.1.3 invoke==2.2.0 From 4d893a19ade200bb3186bfa63099fbb0dc2412be Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Jul 2023 11:57:15 +0200 Subject: [PATCH 066/111] Bump jsonschema from 4.18.0 to 4.18.4 (#11277) * Bump jsonschema from 4.18.0 to 4.18.4 Bumps [jsonschema](https://github.com/python-jsonschema/jsonschema) from 4.18.0 to 4.18.4. - [Release notes](https://github.com/python-jsonschema/jsonschema/releases) - [Changelog](https://github.com/python-jsonschema/jsonschema/blob/main/CHANGELOG.rst) - [Commits](https://github.com/python-jsonschema/jsonschema/compare/v4.18.0...v4.18.4) --- updated-dependencies: - dependency-name: jsonschema dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index b5b5c9be906..9c40b5461a8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,7 +20,7 @@ vine==5.0.0 tqdm==4.65.0 Deprecated==1.2.14 wrapt==1.15.0 -jsonschema==4.18.3 +jsonschema==4.18.4 zipstream-new==1.1.8 schema==0.7.5 rdflib==6.3.2 diff --git a/setup.cfg b/setup.cfg index 424e3cdc7cf..2e6ec3fa19d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -46,7 +46,7 @@ install_requires = tqdm==4.65.0 Deprecated==1.2.14 wrapt==1.15.0 - jsonschema==4.18.3 + jsonschema==4.18.4 zipstream-new==1.1.8 schema==0.7.5 rdflib==6.3.2 From a4cb52f3cf41592021b6b89babaef36551e365d6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Jul 2023 12:56:17 +0200 Subject: [PATCH 067/111] Bump factory-boy from 3.2.1 to 3.3.0 (#11281) * Bump factory-boy from 3.2.1 to 3.3.0 Bumps [factory-boy](https://github.com/FactoryBoy/factory_boy) from 3.2.1 to 3.3.0. - [Changelog](https://github.com/FactoryBoy/factory_boy/blob/master/docs/changelog.rst) - [Commits](https://github.com/FactoryBoy/factory_boy/compare/3.2.1...3.3.0) --- updated-dependencies: - dependency-name: factory-boy dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * - Align "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 9c40b5461a8..a9d73c1d309 100644 --- a/requirements.txt +++ b/requirements.txt @@ -163,7 +163,7 @@ setuptools>=59.1.1,<68.1.0 pip==23.2 Twisted==22.10.0 pixelmatch==0.3.0 -factory-boy==3.2.1 +factory-boy==3.3.0 flaky==3.7.0 selenium>=4.1.0,<5.0.0 selenium-requests==2.0.3 diff --git a/setup.cfg b/setup.cfg index 2e6ec3fa19d..3bbf22b9465 100644 --- a/setup.cfg +++ b/setup.cfg @@ -188,7 +188,7 @@ install_requires = pip==23.2 Twisted==22.10.0 pixelmatch==0.3.0 - factory-boy==3.2.1 + factory-boy==3.3.0 flaky==3.7.0 selenium>=4.1.0,<5.0.0 selenium-requests==2.0.3 From 2bdd2422ae20a389cd2a2910bb6afc121ce27656 Mon Sep 17 00:00:00 2001 From: etj Date: Thu, 20 Jul 2023 13:34:55 +0200 Subject: [PATCH 068/111] [Fixes #11225] Italian translations aren't built with the latest fixes [Fixes #11264] Fix the italian translation for Position --- geonode/locale/it/LC_MESSAGES/django.mo | Bin 155297 -> 161119 bytes geonode/locale/it/LC_MESSAGES/django.po | 2510 ++++++++++------------- 2 files changed, 1052 insertions(+), 1458 deletions(-) diff --git a/geonode/locale/it/LC_MESSAGES/django.mo b/geonode/locale/it/LC_MESSAGES/django.mo index a093d7f2986572518ff6c3196a2195cdfea49d9e..0a9e88e7f7d43b2150075b76272bf4703f194b8f 100644 GIT binary patch delta 38086 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 o-ILL_X&?9PXMD@M_5-$;`*Dudbom$ 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 delta 33511 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 b*7Ybc*jKo^w`O4YrkdVSzM-|e*F*jfhks># diff --git a/geonode/locale/it/LC_MESSAGES/django.po b/geonode/locale/it/LC_MESSAGES/django.po index 154a9219bfc..f698c7205b5 100644 --- a/geonode/locale/it/LC_MESSAGES/django.po +++ b/geonode/locale/it/LC_MESSAGES/django.po @@ -39,16 +39,15 @@ msgstr "" "Project-Id-Version: GeoNode\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-07-08 09:02+0300\n" -"PO-Revision-Date: 2021-04-29 13:43+0200\n" +"PO-Revision-Date: 2023-07-20 11:35+0200\n" "Last-Translator: Julien Collaer \n" -"Language-Team: Italian (http://www.transifex.com/geonode/geonode/language/" -"it/)\n" +"Language-Team: Italian (http://www.transifex.com/geonode/geonode/language/it/)\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 2.4.2\n" +"X-Generator: Poedit 3.0.1\n" msgid "Request to download a resource" msgstr "Richiedi di scaricare la risorsa" @@ -60,8 +59,7 @@ msgid "Request resource change" msgstr "Richiedi di modificare la risorsa" msgid "Owner has requested permissions to modify a resource" -msgstr "" -"Il proprietario ha richiesto le autorizzazioni per modificare la risorsa" +msgstr "Il proprietario ha richiesto le autorizzazioni per modificare la risorsa" msgid "series" msgstr "serie" @@ -199,11 +197,10 @@ msgid "Free-text Keywords" msgstr "Free-text Keywords" msgid "" -"A space or comma-separated list of keywords. Use the widget to select from " -"Hierarchical tree." +"A space or comma-separated list of keywords. Use the widget to select from Hierarchical tree." msgstr "" -"Uno spazio o un elenco delimitato da virgole di parole chiave. Utilizzare il " -"widget per selezionare dalla struttura ad albero gerarchica." +"Uno spazio o un elenco delimitato da virgole di parole chiave. Utilizzare il widget per " +"selezionare dalla struttura ad albero gerarchica." msgid "Regions" msgstr "Regioni" @@ -220,11 +217,9 @@ msgstr "Licenza" msgid "Language" msgstr "Lingua" -#, fuzzy -#| msgid "Users" msgid "User" msgid_plural "Users" -msgstr[0] "Utenti" +msgstr[0] "Utente" msgstr[1] "Utenti" msgid "Permission Type" @@ -258,41 +253,37 @@ msgid "version of the cited resource" msgstr "versione della risorsa citata" msgid "" -"authority or function assigned, as to a ruler, legislative assembly, " -"delegate, or the like." +"authority or function assigned, as to a ruler, legislative assembly, delegate, or the like." msgstr "" -"autorità o funzione assegnata, ad esempio un regolatore, un'assemblea " -"legislativa, un delegato o simili." +"autorità o funzione assegnata, ad esempio un regolatore, un'assemblea legislativa, un " +"delegato o simili." msgid "a DOI will be added by Admin before publication." msgstr "un DOI verrà aggiunto dall'amministratore prima della pubblicazione." msgid "summary of the intentions with which the resource(s) was developed" -msgstr "" -"sintesi delle intenzioni con cui la risorsa (o le risorse) sono state " -"sviluppate" +msgstr "sintesi delle intenzioni con cui la risorsa (o le risorse) sono state sviluppate" msgid "" -"frequency with which modifications and deletions are made to the data after " -"it is first produced" +"frequency with which modifications and deletions are made to the data after it is first " +"produced" msgstr "" -"frequenza con cui le modifiche e le cancellazioni sui dati vengono eseguite " -"dopo essere stati prodotti la prima volta" +"frequenza con cui le modifiche e le cancellazioni sui dati vengono eseguite dopo essere " +"stati prodotti la prima volta" msgid "" -"commonly used word(s) or formalised word(s) or phrase(s) used to describe " -"the subject (space or comma-separated)" +"commonly used word(s) or formalised word(s) or phrase(s) used to describe the subject (space " +"or comma-separated)" msgstr "" -"parola (o parole) comunemente usate o parola (o parole) e frase (o frasi) " -"formalizzate usate per descrivere il soggetto (separando attraverso spazio o " -"virgola" +"parola (o parole) comunemente usate o parola (o parole) e frase (o frasi) formalizzate usate " +"per descrivere il soggetto (separando attraverso spazio o virgola" msgid "" -"formalised word(s) or phrase(s) from a fixed thesaurus used to describe the " -"subject (space or comma-separated)" +"formalised word(s) or phrase(s) from a fixed thesaurus used to describe the subject (space " +"or comma-separated)" msgstr "" -"parola(e) o frase(i) formalizzate da un thesaurus fisso utilizzato per " -"descrivere l'oggetto (spazio o delimitato da virgole)" +"parola(e) o frase(i) formalizzate da un thesaurus fisso utilizzato per descrivere l'oggetto " +"(spazio o delimitato da virgole)" msgid "keyword identifies a location" msgstr "parola chiave che identifica una posizione" @@ -301,11 +292,9 @@ msgid "limitation(s) placed upon the access or use of the data." msgstr "limitazione(i) sull'accesso o l'uso dei dati." msgid "" -"other restrictions and legal prerequisites for accessing and using the " -"resource or metadata" +"other restrictions and legal prerequisites for accessing and using the resource or metadata" msgstr "" -"altre restrizioni e requisiti legali per l'accesso e l'uso della risorsa o " -"dei metadati" +"altre restrizioni e requisiti legali per l'accesso e l'uso della risorsa o dei metadati" msgid "license of the dataset" msgstr "licenza del dataset" @@ -314,11 +303,11 @@ msgid "language used within the dataset" msgstr "lingua usata all'interno del dataset" msgid "" -"high-level geographic data thematic classification to assist in the grouping " -"and search of available geographic data sets." +"high-level geographic data thematic classification to assist in the grouping and search of " +"available geographic data sets." msgstr "" -"classificazione tematica di dati geografici di alto livello per facilitare " -"il raggruppamento e la ricerca di set di dati geografici disponibili." +"classificazione tematica di dati geografici di alto livello per facilitare il raggruppamento " +"e la ricerca di set di dati geografici disponibili." msgid "method used to represent geographic information in the dataset." msgstr "metodo di rappresentazione spaziale del dataset." @@ -329,12 +318,10 @@ msgstr "periodo di tempo coperto dal contenuto del dataset (inizio)" msgid "time period covered by the content of the dataset (end)" msgstr "periodo di tempo coperto dal contenuto del dataset (fine)" -msgid "" -"general explanation of the data producer's knowledge about the lineage of a " -"dataset" +msgid "general explanation of the data producer's knowledge about the lineage of a dataset" msgstr "" -"spiegazione generale della conoscenza del produttore dei dati riguardo il " -"lignaggio di un dataset" +"spiegazione generale della conoscenza del produttore dei dati riguardo il lignaggio di un " +"dataset" msgid "title" msgstr "titolo" @@ -438,10 +425,8 @@ msgstr "Questa risorsa è stata convalidata da un editore o un curatore?" msgid "Thumbnail url" msgstr "Url della miniatura" -#, fuzzy -#| msgid "Dirty State" msgid "State" -msgstr "Non ancora aggiornato" +msgstr "Stato" msgid "Hold the resource processing state." msgstr "" @@ -621,25 +606,17 @@ msgid "Reason for the request" msgstr "Motivo della richiesta" msgid "" -"To allow the change, set the resource to not \"Approved\" under the metadata " -"settingsand write message to the owner to notify him" +"To allow the change, set the resource to not \"Approved\" under the metadata settingsand " +"write message to the owner to notify him" msgstr "" -"Per consentire la modifica, impostare la risorsa su non \"Approvato\" nelle " -"impostazioni dei metadati e scrivere un messaggio al proprietario per " -"notificarlo" +"Per consentire la modifica, impostare la risorsa su non \"Approvato\" nelle impostazioni dei " +"metadati e scrivere un messaggio al proprietario per notificarlo" -#, fuzzy -#| msgid "Service rescanned successfully" msgid "Resource Cloned Successfully!" -msgstr "Il servizio è stato analizzato di nuovo correttamente" +msgstr "La risorsa è stata clonata con successo!" -#, fuzzy, python-brace-format -#| msgid "" -#| "Some error occurred while trying to access the uploaded schema: {str(e)}" msgid "Error Occurred while Cloning the Resource: {e}" -msgstr "" -"Si è verificato un errore durante il tentativo di accesso allo schema " -"caricato: {str(e)}" +msgstr "Si è verificato un errore durante la clonazione della risorsa: {e}" msgid "Resource Metadata" msgstr "Metadati delle risorse" @@ -675,237 +652,219 @@ msgid "GeoNode Client Library" msgstr "Libreria client di GeoNode" msgid "" -"Flora and/or fauna in natural environment. Examples: wildlife, vegetation, " -"biological sciences, ecology, wilderness, sealife, wetlands, habitat." +"Flora and/or fauna in natural environment. Examples: wildlife, vegetation, biological " +"sciences, ecology, wilderness, sealife, wetlands, habitat." msgstr "" -"Flora e/o fauna in ambiente naturale. Esempi: fauna selvatica, vegetazione, " -"scienze biologiche, ecologia, natura selvaggia, vita marina, zone umide, " -"habitat." +"Flora e/o fauna in ambiente naturale. Esempi: fauna selvatica, vegetazione, scienze " +"biologiche, ecologia, natura selvaggia, vita marina, zone umide, habitat." msgid "Biota" msgstr "Biota" -msgid "" -"Legal land descriptions. Examples: political and administrative boundaries." -msgstr "" -"Descrizioni legali dei terreni. Esempi: confini politici e amministrativi." +msgid "Legal land descriptions. Examples: political and administrative boundaries." +msgstr "Descrizioni legali dei terreni. Esempi: confini politici e amministrativi." msgid "Boundaries" msgstr "Confini" msgid "" -"Processes and phenomena of the atmosphere. Examples: cloud cover, weather, " -"climate, atmospheric conditions, climate change, precipitation." +"Processes and phenomena of the atmosphere. Examples: cloud cover, weather, climate, " +"atmospheric conditions, climate change, precipitation." msgstr "" -"Processi e fenomeni dell'atmosfera. Esempi: copertura nuvolosa, meteo, " -"clima, condizioni atmosferiche, cambiamenti climatici, precipitazioni." +"Processi e fenomeni dell'atmosfera. Esempi: copertura nuvolosa, meteo, clima, condizioni " +"atmosferiche, cambiamenti climatici, precipitazioni." msgid "Climatology Meteorology Atmosphere" msgstr "Climatologia Meteorologia Atmosfera" msgid "" -"Economic activities, conditions and employment. Examples: production, " -"labour, revenue, commerce, industry, tourism and ecotourism, forestry, " -"fisheries, commercial or subsistence hunting, exploration and exploitation " -"of resources such as minerals, oil and gas." +"Economic activities, conditions and employment. Examples: production, labour, revenue, " +"commerce, industry, tourism and ecotourism, forestry, fisheries, commercial or subsistence " +"hunting, exploration and exploitation of resources such as minerals, oil and gas." msgstr "" -"Attività economiche, condizioni e occupazione. Esempi: produzione, lavoro, " -"entrate, commercio, industria, turismo ed ecoturismo, silvicoltura, pesca, " -"caccia commerciale o di sussistenza, esplorazione e sfruttamento di risorse " -"quali minerali, petrolio e gas." +"Attività economiche, condizioni e occupazione. Esempi: produzione, lavoro, entrate, " +"commercio, industria, turismo ed ecoturismo, silvicoltura, pesca, caccia commerciale o di " +"sussistenza, esplorazione e sfruttamento di risorse quali minerali, petrolio e gas." msgid "Economy" msgstr "Economia" msgid "" -"Height above or below sea level. Examples: altitude, bathymetry, digital " -"elevation models, slope, derived products." +"Height above or below sea level. Examples: altitude, bathymetry, digital elevation models, " +"slope, derived products." msgstr "" -"Altezza sopra o sotto il livello del mare. Esempi: altitudine, batimetria, " -"modelli digitali di elevazione, pendenza, prodotti derivati." +"Altezza sopra o sotto il livello del mare. Esempi: altitudine, batimetria, modelli digitali " +"di elevazione, pendenza, prodotti derivati." msgid "Elevation" msgstr "Elevazione" msgid "" -"Environmental resources, protection and conservation. Examples: " -"environmental pollution, waste storage and treatment, environmental impact " -"assessment, monitoring environmental risk, nature reserves, landscape." +"Environmental resources, protection and conservation. Examples: environmental pollution, " +"waste storage and treatment, environmental impact assessment, monitoring environmental risk, " +"nature reserves, landscape." msgstr "" -"Risorse ambientali, protezione e conservazione. Esempi: inquinamento " -"ambientale, stoccaggio e trattamento dei rifiuti, valutazione dell'impatto " -"ambientale, monitoraggio del rischio ambientale, riserve naturali, paesaggio." +"Risorse ambientali, protezione e conservazione. Esempi: inquinamento ambientale, stoccaggio " +"e trattamento dei rifiuti, valutazione dell'impatto ambientale, monitoraggio del rischio " +"ambientale, riserve naturali, paesaggio." msgid "Environment" msgstr "Ambiente" msgid "" -"Rearing of animals and/or cultivation of plants. Examples: agriculture, " -"irrigation, aquaculture, plantations, herding, pests and diseases affecting " -"crops and livestock." +"Rearing of animals and/or cultivation of plants. Examples: agriculture, irrigation, " +"aquaculture, plantations, herding, pests and diseases affecting crops and livestock." msgstr "" -"Allevamento di animali e/o coltivazione di piante. Esempi: agricoltura, " -"irrigazione, acquacoltura, piantagioni, mandrie, parassiti e malattie che " -"colpiscono le colture e il bestiame." +"Allevamento di animali e/o coltivazione di piante. Esempi: agricoltura, irrigazione, " +"acquacoltura, piantagioni, mandrie, parassiti e malattie che colpiscono le colture e il " +"bestiame." msgid "Farming" msgstr "Agricoltura" msgid "" -"Information pertaining to earth sciences. Examples: geophysical features and " -"processes, geology, minerals, sciences dealing with the composition, " -"structure and origin of the earth s rocks, risks of earthquakes, volcanic " -"activity, landslides, gravity information, soils, permafrost, hydrogeology, " -"erosion." +"Information pertaining to earth sciences. Examples: geophysical features and processes, " +"geology, minerals, sciences dealing with the composition, structure and origin of the earth " +"s rocks, risks of earthquakes, volcanic activity, landslides, gravity information, soils, " +"permafrost, hydrogeology, erosion." msgstr "" -"Informazioni relative alle scienze della terra. Esempi: caratteristiche e " -"processi geofisici, geologia, minerali, scienze che si occupano della " -"composizione, struttura e origine delle rocce terrestri, rischi di " -"terremoti, attività vulcanica, frane, informazioni sulla gravità, suoli, " -"permafrost, idrogeologia, erosione." +"Informazioni relative alle scienze della terra. Esempi: caratteristiche e processi " +"geofisici, geologia, minerali, scienze che si occupano della composizione, struttura e " +"origine delle rocce terrestri, rischi di terremoti, attività vulcanica, frane, informazioni " +"sulla gravità, suoli, permafrost, idrogeologia, erosione." msgid "Geoscientific Information" msgstr "Informazioni geoscientifiche" msgid "" -"Health, health services, human ecology, and safety. Examples: disease and " -"illness, factors affecting health, hygiene, substance abuse, mental and " -"physical health, health services." +"Health, health services, human ecology, and safety. Examples: disease and illness, factors " +"affecting health, hygiene, substance abuse, mental and physical health, health services." msgstr "" -"Salute, servizi sanitari, ecologia umana e sicurezza. Esempi: malattia e " -"malattia, fattori che influenzano la salute, l'igiene, l'abuso di sostanze, " -"la salute mentale e fisica, i servizi sanitari." +"Salute, servizi sanitari, ecologia umana e sicurezza. Esempi: malattia e malattia, fattori " +"che influenzano la salute, l'igiene, l'abuso di sostanze, la salute mentale e fisica, i " +"servizi sanitari." msgid "Health" msgstr "Salute" msgid "" -"Base maps. Examples: land cover, topographic maps, imagery, unclassified " -"images, annotations." +"Base maps. Examples: land cover, topographic maps, imagery, unclassified images, annotations." msgstr "" -"Mappe di base. Esempi: copertura del terreno, mappe topografiche, immagini, " -"immagini non classificate, annotazioni." +"Mappe di base. Esempi: copertura del terreno, mappe topografiche, immagini, immagini non " +"classificate, annotazioni." msgid "Imagery Base Maps Earth Cover" msgstr "Mappe Immagini di Base Copertina della Terra" msgid "" -"Inland water features, drainage systems and their characteristics. Examples: " -"rivers and glaciers, salt lakes, water utilization plans, dams, currents, " -"floods, water quality, hydrographic charts." +"Inland water features, drainage systems and their characteristics. Examples: rivers and " +"glaciers, salt lakes, water utilization plans, dams, currents, floods, water quality, " +"hydrographic charts." msgstr "" -"Caratteristiche dell'acqua dell'entroterra, sistemi di drenaggio e loro " -"caratteristiche. Esempi: fiumi e ghiacciai, laghi salati, piani di utilizzo " -"dell'acqua, dighe, correnti, inondazioni, qualità dell'acqua, grafici " -"idrografici." +"Caratteristiche dell'acqua dell'entroterra, sistemi di drenaggio e loro caratteristiche. " +"Esempi: fiumi e ghiacciai, laghi salati, piani di utilizzo dell'acqua, dighe, correnti, " +"inondazioni, qualità dell'acqua, grafici idrografici." msgid "Inland Waters" msgstr "Acque interne" msgid "" -"Military bases, structures, activities. Examples: barracks, training " -"grounds, military transportation, information collection." +"Military bases, structures, activities. Examples: barracks, training grounds, military " +"transportation, information collection." msgstr "" -"Basi militari, strutture, attività. Esempi: caserme, campi di addestramento, " -"trasporto militare, raccolta di informazioni." +"Basi militari, strutture, attività. Esempi: caserme, campi di addestramento, trasporto " +"militare, raccolta di informazioni." msgid "Intelligence Military" msgstr "Intelligence militare" msgid "" -"Positional information and services. Examples: addresses, geodetic networks, " -"control points, postal zones and services, place names." +"Positional information and services. Examples: addresses, geodetic networks, control points, " +"postal zones and services, place names." msgstr "" -"Informazioni e servizi di localizzazione. Esempi: indirizzi, reti " -"geodetiche, punti di controllo, zone postali e servizi, nomi di luoghi." +"Informazioni e servizi di localizzazione. Esempi: indirizzi, reti geodetiche, punti di " +"controllo, zone postali e servizi, nomi di luoghi." msgid "Location" msgstr "Posizione" msgid "" -"Features and characteristics of salt water bodies (excluding inland waters). " -"Examples: tides, tidal waves, coastal information, reefs." +"Features and characteristics of salt water bodies (excluding inland waters). Examples: " +"tides, tidal waves, coastal information, reefs." msgstr "" -"Caratteristiche e caratteristiche dei corpi idrici dell'acqua salata " -"(escluse le acque interne). Esempi: maree, onde di marea, informazioni " -"costiere, barriere coralline." +"Caratteristiche e caratteristiche dei corpi idrici dell'acqua salata (escluse le acque " +"interne). Esempi: maree, onde di marea, informazioni costiere, barriere coralline." msgid "Oceans" msgstr "Oceani" msgid "" -"Information used for appropriate actions for future use of the land. " -"Examples: land use maps, zoning maps, cadastral surveys, land ownership." +"Information used for appropriate actions for future use of the land. Examples: land use " +"maps, zoning maps, cadastral surveys, land ownership." msgstr "" -"Informazioni utilizzate per azioni appropriate per un uso futuro del " -"terreno. Esempi: mappe dell'uso del suolo, mappe di zonzing, indagini " -"cadastrali, proprietà del terreno." +"Informazioni utilizzate per azioni appropriate per un uso futuro del terreno. Esempi: mappe " +"dell'uso del suolo, mappe di zonzing, indagini cadastrali, proprietà del terreno." msgid "Planning Cadastre" msgstr "Pianificazione catasto" msgid "" -"Settlements, anthropology, archaeology, education, traditional beliefs, " -"manners and customs, demographic data, recreational areas and activities, " -"social impact assessments, crime and justice, census information. Economic " -"activities, conditions and employment." +"Settlements, anthropology, archaeology, education, traditional beliefs, manners and customs, " +"demographic data, recreational areas and activities, social impact assessments, crime and " +"justice, census information. Economic activities, conditions and employment." msgstr "" -"Insediamenti, antropologia, archeologia, educazione, credenze tradizionali, " -"modi e costumi, dati demografici, aree e attività ricreative, valutazioni di " -"impatto sociale, criminalità e giustizia, informazioni sul censimento. " -"Attività economiche, condizioni e occupazione." +"Insediamenti, antropologia, archeologia, educazione, credenze tradizionali, modi e costumi, " +"dati demografici, aree e attività ricreative, valutazioni di impatto sociale, criminalità e " +"giustizia, informazioni sul censimento. Attività economiche, condizioni e occupazione." msgid "Population" msgstr "Popolazione" msgid "" -"Characteristics of society and cultures. Examples: settlements, " -"anthropology, archaeology, education, traditional beliefs, manners and " -"customs, demographic data, recreational areas and activities, social impact " -"assessments, crime and justice, census information." +"Characteristics of society and cultures. Examples: settlements, anthropology, archaeology, " +"education, traditional beliefs, manners and customs, demographic data, recreational areas " +"and activities, social impact assessments, crime and justice, census information." msgstr "" -"Caratteristiche della società e delle culture. Esempi: insediamenti, " -"antropologia, archeologia, istruzione, credenze tradizionali, modi e " -"costumi, dati demografici, aree ricreative e attività, valutazioni " -"dell'impatto sociale, criminalità e giustizia, informazioni sul censimento." +"Caratteristiche della società e delle culture. Esempi: insediamenti, antropologia, " +"archeologia, istruzione, credenze tradizionali, modi e costumi, dati demografici, aree " +"ricreative e attività, valutazioni dell'impatto sociale, criminalità e giustizia, " +"informazioni sul censimento." msgid "Society" msgstr "Società" msgid "" -"Man-made construction. Examples: buildings, museums, churches, factories, " -"housing, monuments, shops, towers." +"Man-made construction. Examples: buildings, museums, churches, factories, housing, " +"monuments, shops, towers." msgstr "" -"Costruzione artificiale. Esempi: edifici, musei, chiese, fabbriche, " -"abitazioni, monumenti, negozi, torri." +"Costruzione artificiale. Esempi: edifici, musei, chiese, fabbriche, abitazioni, monumenti, " +"negozi, torri." msgid "Structure" msgstr "Strutture" msgid "" -"Means and aids for conveying persons and/or goods. Examples: roads, airports/" -"airstrips, shipping routes, tunnels, nautical charts, vehicle or vessel " -"location, aeronautical charts, railways." +"Means and aids for conveying persons and/or goods. Examples: roads, airports/airstrips, " +"shipping routes, tunnels, nautical charts, vehicle or vessel location, aeronautical charts, " +"railways." msgstr "" -"Mezzi e aiuti per il trasporto di persone e/o merci. Esempi: strade, " -"aeroporti/sciviole, rotte marittime, gallerie, carte nautiche, posizione di " -"veicoli o navi, carte aeronautiche, ferrovie." +"Mezzi e aiuti per il trasporto di persone e/o merci. Esempi: strade, aeroporti/sciviole, " +"rotte marittime, gallerie, carte nautiche, posizione di veicoli o navi, carte aeronautiche, " +"ferrovie." msgid "Transportation" msgstr "Trasporti" msgid "" -"Energy, water and waste systems and communications infrastructure and " -"services. Examples: hydroelectricity, geothermal, solar and nuclear sources " -"of energy, water purification and distribution, sewage collection and " -"disposal, electricity and gas distribution, data communication, " -"telecommunication, radio, communication networks." +"Energy, water and waste systems and communications infrastructure and services. Examples: " +"hydroelectricity, geothermal, solar and nuclear sources of energy, water purification and " +"distribution, sewage collection and disposal, electricity and gas distribution, data " +"communication, telecommunication, radio, communication networks." msgstr "" -"Sistemi energetici, idrici e dei rifiuti, infrastrutture e servizi di " -"comunicazione. Esempi: fonti idroelettriche, geotermiche, solari e nucleari " -"di energia, depurazione e distribuzione dell'acqua, raccolta e smaltimento " -"delle acque reflue, distribuzione di elettricità e gas, comunicazione dei " -"dati, telecomunicazioni, radio, reti di comunicazione." +"Sistemi energetici, idrici e dei rifiuti, infrastrutture e servizi di comunicazione. Esempi: " +"fonti idroelettriche, geotermiche, solari e nucleari di energia, depurazione e distribuzione " +"dell'acqua, raccolta e smaltimento delle acque reflue, distribuzione di elettricità e gas, " +"comunicazione dei dati, telecomunicazioni, radio, reti di comunicazione." msgid "Utilities Communication" msgstr "Servizi Comunicazioni" @@ -1051,15 +1010,13 @@ msgstr "Procedura guidata" msgid "Advanced Edit" msgstr "Modifica avanzata" -#, fuzzy -#| msgid "Document" msgid "Document" msgid_plural "Documents" msgstr[0] "Documento" -msgstr[1] "Documento" +msgstr[1] "Documenti" msgid "Clone" -msgstr "" +msgstr "Clona" msgid "Replace" msgstr "Sostituisci" @@ -1098,8 +1055,7 @@ msgid "Permissions" msgstr "Permessi" msgid "Click the button below to change the permissions of this document." -msgstr "" -"Clicca il pulsante qui sotto per modificare i permessi di questo documento." +msgstr "Clicca il pulsante qui sotto per modificare i permessi di questo documento." msgid "Change Document Permissions" msgstr "Cambia i permessi del Documento" @@ -1124,9 +1080,7 @@ msgid "Check Schema mandatory fields" msgstr "Controllare i campi obbligatori dello schema" msgid "Error updating metadata. Please check the following fields: " -msgstr "" -"Errore nell'aggiornamento dei metadati. Per favore controlla i seguenti " -"campi: " +msgstr "Errore nell'aggiornamento dei metadati. Per favore controlla i seguenti campi: " msgid "Done" msgstr "Fatto" @@ -1156,7 +1110,7 @@ msgid "Return to Document" msgstr "Ritorna al documento" msgid "Deleting" -msgstr "Sto cancellando" +msgstr "Sto eliminando" msgid "Remove Document" msgstr "Rimuovi documento" @@ -1164,13 +1118,13 @@ msgstr "Rimuovi documento" #, python-format msgid "" "\n" -" Are you sure you want to remove %(document_title)s?\n" +" Are you sure you want to remove %(document_title)s?\n" " " msgstr "" "\n" -" Sei sicuro di voler rimuovere %(document_title)s?\n" +" Sei sicuro di voler rimuovere %(document_title)s?\n" " " msgid "Yes, I am sure" @@ -1300,7 +1254,7 @@ msgid "Log in to add/delete Favorites." msgstr "Accedi per aggiungere/eliminare preferiti." msgid "Delete from Favorites" -msgstr "Cancella dai preferiti" +msgstr "Elimina dai preferiti" msgid "Favorites" msgstr "Preferiti" @@ -1400,12 +1354,11 @@ msgid "Embed Iframe" msgstr "Incorpora Iframe" msgid "" -"To embed this map, add the following code snippet and customize its " -"properties (scrolling, width, height) based on your needs to your site" +"To embed this map, add the following code snippet and customize its properties (scrolling, " +"width, height) based on your needs to your site" msgstr "" -"Per incorporare questa mappa, aggiungere il frammento di codice seguente e " -"personalizzarne le proprietà (scorrimento, larghezza, altezza) in base alle " -"proprie esigenze nel sito" +"Per incorporare questa mappa, aggiungere il frammento di codice seguente e personalizzarne " +"le proprietà (scorrimento, larghezza, altezza) in base alle proprie esigenze nel sito" msgid "Download" msgstr "Scarica" @@ -1425,22 +1378,19 @@ msgid "for %(map_title)s" msgstr "per %(map_title)s" msgid "" -"Note: this geoapp's orginal metadata was populated by importing a metadata " -"XML file.\n" -" GeoNode's metadata import supports a subset of ISO, FGDC, and Dublin " -"Core metadata elements.\n" +"Note: this geoapp's orginal metadata was populated by importing a metadata XML file.\n" +" GeoNode's metadata import supports a subset of ISO, FGDC, and Dublin Core metadata " +"elements.\n" " Some of your original metadata may have been lost." msgstr "" -"Nota: i metadati orginali di questa geoapp sono stati popolati importando un " -"file XML di metadati.\n" -" L'importazione dei metadati di GeoNode supporta un sottoinsieme di " -"elementi di metadati ISO, FGDC e Dublin Core.\n" +"Nota: i metadati orginali di questa geoapp sono stati popolati importando un file XML di " +"metadati.\n" +" L'importazione dei metadati di GeoNode supporta un sottoinsieme di elementi di " +"metadati ISO, FGDC e Dublin Core.\n" " Alcuni dei metadati originali potrebbero essere stati persi." -#, fuzzy -#| msgid "Explore Maps" msgid "Explore Apps" -msgstr "Esplora mappe" +msgstr "Esplora apps" #, python-format msgid "" @@ -1452,10 +1402,8 @@ msgstr "" " Modifica dei dettagli per il %(map_title)s\n" " " -#, fuzzy -#| msgid "Return to Map" msgid "Return to GeoApp" -msgstr "Ritorna alla mappa" +msgstr "Ritorna alla GeoApp" msgid "New Map" msgstr "Nuova mappa" @@ -1463,13 +1411,13 @@ msgstr "Nuova mappa" #, python-format msgid "" "\n" -" Are you sure you want to remove %(resource_title)s?\n" +" Are you sure you want to remove %(resource_title)s?\n" " " msgstr "" "\n" -" Siete sicuri di voler rimuovere %(resource_title)s?\n" +" Siete sicuri di voler rimuovere %(resource_title)s?\n" " " msgid "Other Settings" @@ -1523,39 +1471,29 @@ msgstr "Linee" msgid "Polygons" msgstr "Poligoni" -#, fuzzy -#| msgid "dataset" msgid "Dataset name" -msgstr "dataset" +msgstr "Nome del dataset" -#, fuzzy -#| msgid "Dataset Attributes" msgid "Dataset title" -msgstr "Attributi del dataset" +msgstr "Titolo del dataset" msgid "Geometry type" msgstr "Tipo di geometria" -#, fuzzy -#| msgid "Featured Datasets" msgid "Create Dataset" -msgstr "Dataset in primo piano" +msgstr "Crea Dataset" -#, fuzzy -#| msgid "Explore Layers" msgid "Explore Datasets" -msgstr "Esplora livelli" +msgstr "Esplora dataset" -#, fuzzy -#| msgid "Create an empty layer" msgid "Create an empty dataset" -msgstr "Creare un livello vuoto" +msgstr "Crea un dataset vuoto" msgid "Add Attribute" -msgstr "Aggiungere attributo" +msgstr "Aggiungi attributo" msgid "Create" -msgstr "Creare" +msgstr "Crea" msgid "String" msgstr "Stringa" @@ -1616,18 +1554,16 @@ msgstr "PNG" msgid "You don't have permissions to change style for this layer" msgstr "" -"Non si dispone delle autorizzazioni necessarie per modificare lo stile di " -"questo livello" +"Non si dispone delle autorizzazioni necessarie per modificare lo stile di questo livello" msgid "Bad HTTP Authorization Credentials." msgstr "Credenziali di autorizzazione HTTP errate." msgid "" -"a short version of the name consisting only of letters, numbers, underscores " -"and hyphens." +"a short version of the name consisting only of letters, numbers, underscores and hyphens." msgstr "" -"una versione abbreviata del nome consiste solo di lettere, numeri, caratteri " -"di sottolineatura e trattini." +"una versione abbreviata del nome consiste solo di lettere, numeri, caratteri di " +"sottolineatura e trattini." msgid "A group already exists with that slug." msgstr "Un gruppo esiste già con questo slug." @@ -1642,10 +1578,8 @@ msgid "Assign manager role" msgstr "Assegnare il ruolo di manager" #, python-format -msgid "" -"The following are not valid usernames: %(errors)s; not added to the group" -msgstr "" -"I seguenti non sono nomi utente validi: %(errors)s; non aggiunto al gruppo" +msgid "The following are not valid usernames: %(errors)s; not added to the group" +msgstr "I seguenti non sono nomi utente validi: %(errors)s; non aggiunto al gruppo" msgid "Group Categories" msgstr "Categorie gruppo" @@ -1660,24 +1594,23 @@ msgid "Private" msgstr "Privato" msgid "" -"Public: Any registered user can view and join a public group.
Public " -"(invite-only):Any registered user can view the group. Only invited users " -"can join.
Private: Registered users cannot see any details about the " -"group, including membership. Only invited users can join." +"Public: Any registered user can view and join a public group.
Public (invite-only):Any " +"registered user can view the group. Only invited users can join.
Private: Registered " +"users cannot see any details about the group, including membership. Only invited users can " +"join." msgstr "" -"Pubblico: Qualsiasi utente registrato può visualizzare e unirsi a un gruppo " -"pubblico.
(Solo su invito) Pubblico: Qualsiasi utente registrato può " -"visualizzare il gruppo. Solo gli utenti invitati possono partecipare.
" -"Per uso privato: Gli utenti registrati non possono vedere tutti i dettagli " -"sul gruppo, compresa l'appartenenza. Solo gli utenti invitati possono " -"partecipare." +"Pubblico: Qualsiasi utente registrato può visualizzare e unirsi a un gruppo pubblico.
" +"(Solo su invito) Pubblico: Qualsiasi utente registrato può visualizzare il gruppo. Solo gli " +"utenti invitati possono partecipare.
Per uso privato: Gli utenti registrati non possono " +"vedere tutti i dettagli sul gruppo, compresa l'appartenenza. Solo gli utenti invitati " +"possono partecipare." msgid "" -"Email used to contact one or all group members, such as a mailing list, " -"shared email, or exchange group." +"Email used to contact one or all group members, such as a mailing list, shared email, or " +"exchange group." msgstr "" -"E-mail utilizzato per contattare gli utenti di uno o di tutti i gruppi, come " -"ad esempio una mailing list, e-mail condivisa, o un gruppo di scambio." +"E-mail utilizzato per contattare gli utenti di uno o di tutti i gruppi, come ad esempio una " +"mailing list, e-mail condivisa, o un gruppo di scambio." msgid "Logo" msgstr "Logo" @@ -1777,9 +1710,7 @@ msgid "Join Group" msgstr "Iscriviti al gruppo" msgid "Anyone may view this group but membership is by invitation only." -msgstr "" -"Chiunque può visualizzare questo gruppo, ma l'iscrizione è possibile solo su " -"invito." +msgstr "Chiunque può visualizzare questo gruppo, ma l'iscrizione è possibile solo su invito." msgid "Membership is by invitation only." msgstr "L'iscrizione è possibile solo su invito." @@ -1862,30 +1793,25 @@ msgstr "Inserimento risorse..." msgid "checking-availability" msgstr "" -#, fuzzy -#| msgid "Harvested" msgid "Harvester name" -msgstr "Raccolte" +msgstr "Nome dell'harvester" msgid "Base URL of the remote service that is to be harvested" msgstr "" msgid "" -"Whether to periodically schedule this harvester to look for resources on the " -"remote service" +"Whether to periodically schedule this harvester to look for resources on the remote service" msgstr "" msgid "" -"How often (in minutes) should new harvesting sessions be automatically " -"scheduled? Setting this value to zero has the same effect as setting " -"`scheduling_enabled` to False " +"How often (in minutes) should new harvesting sessions be automatically scheduled? Setting " +"this value to zero has the same effect as setting `scheduling_enabled` to False " msgstr "" msgid "Whether the remote service is known to be available or not" msgstr "" -msgid "" -"How often (in minutes) should the remote service be checked for availability?" +msgid "How often (in minutes) should the remote service be checked for availability?" msgstr "" msgid "Last time the remote server was checked for availability" @@ -1894,26 +1820,20 @@ msgstr "" msgid "Last time the remote server was checked for harvestable resources" msgstr "" -#, fuzzy -#| msgid "version of the cited resource" msgid "Default owner of harvested resources" -msgstr "versione della risorsa citata" +msgstr "Proprietario di default della risorsa harvestata" -#, fuzzy -#| msgid "Set permissions on selected resources" msgid "Default access permissions of harvested resources" -msgstr "Impostare le autorizzazioni per le risorse selezionate" +msgstr "Autorizzazioni di default per le risorse harvestate" -msgid "" -"Should new resources be harvested automatically without explicit selection?" +msgid "Should new resources be harvested automatically without explicit selection?" msgstr "" msgid "" -"Orphan resources are those that have previously been created by means of a " -"harvesting operation but that GeoNode can no longer find on the remote " -"service being harvested. Should these resources be deleted from GeoNode " -"automatically? This also applies to when a harvester configuration is " -"deleted, in which case all of the resources that originated from that " +"Orphan resources are those that have previously been created by means of a harvesting " +"operation but that GeoNode can no longer find on the remote service being harvested. Should " +"these resources be deleted from GeoNode automatically? This also applies to when a harvester " +"configuration is deleted, in which case all of the resources that originated from that " "harvester are now considered to be orphan." msgstr "" @@ -1921,14 +1841,14 @@ msgid "Date of last update to the harvester configuration." msgstr "" msgid "" -"Harvester class used to perform harvesting sessions. New harvester types can " -"be added by an admin by changing the main GeoNode `settings.py` file" +"Harvester class used to perform harvesting sessions. New harvester types can be added by an " +"admin by changing the main GeoNode `settings.py` file" msgstr "" msgid "" -"Configuration specific to each harvester type. Please consult GeoNode " -"documentation on harvesting for more info. This field is mandatory, so at " -"the very least an empty object (i.e. {}) must be supplied." +"Configuration specific to each harvester type. Please consult GeoNode documentation on " +"harvesting for more info. This field is mandatory, so at the very least an empty object (i." +"e. {}) must be supplied." msgstr "" msgid "Periodic task used to configure harvest scheduling" @@ -1950,17 +1870,15 @@ msgid "harvesting-resource" msgstr "Inserimento risorse..." msgid "" -"Identifier that allows referencing the resource on its remote service in a " -"unique fashion. This is usually automatically filled by the harvester " -"worker. The harvester worker needs to know how to either read or generate " -"this from each remote resource in order to be able to compare the " -"availability of resources between consecutive harvesting sessions." +"Identifier that allows referencing the resource on its remote service in a unique fashion. " +"This is usually automatically filled by the harvester worker. The harvester worker needs to " +"know how to either read or generate this from each remote resource in order to be able to " +"compare the availability of resources between consecutive harvesting sessions." msgstr "" msgid "" -"Type of the resource in the remote service. Each harvester worker knows how " -"to fill this field, in accordance with the resources for which harvesting is " -"supported" +"Type of the resource in the remote service. Each harvester worker knows how to fill this " +"field, in accordance with the resources for which harvesting is supported" msgstr "" #, python-brace-format @@ -1973,9 +1891,7 @@ msgstr "L'indirizzo e-mail '{email}' ha già accettato un invito." #, python-brace-format msgid "An active user is already using the e-mail address '{email}'" -msgstr "" -"Un utente attivo sta già utilizzando l'indirizzo di posta elettronica " -"'{email}'" +msgstr "Un utente attivo sta già utilizzando l'indirizzo di posta elettronica '{email}'" msgid "E-mail" msgstr "E-mail" @@ -1986,11 +1902,11 @@ msgstr "Inviti mandati con successo a '%(email)s'" #, python-format msgid "" -"Sorry, it was not possible to invite '%(email)s' due to the following isse: " -"%(error)s (%(type)s)" +"Sorry, it was not possible to invite '%(email)s' due to the following isse: %(error)s " +"(%(type)s)" msgstr "" -"Spiacenti, non è stato possibile invitare '%(email)s' a causa di quanto " -"segue: %(error)s (%(type)s)" +"Spiacenti, non è stato possibile invitare '%(email)s' a causa di quanto segue: %(error)s " +"(%(type)s)" msgid "Layer Created" msgstr "Livello creato" @@ -2074,8 +1990,7 @@ msgid "use featureinfo custom template?" msgstr "utilizzare il modello personalizzato featureinfo?" msgid "specifies wether or not use a custom GetFeatureInfo template." -msgstr "" -"specifica se si utilizza o meno un modello GetFeatureInfo personalizzato." +msgstr "specifica se si utilizza o meno un modello GetFeatureInfo personalizzato." msgid "featureinfo custom template" msgstr "featureinfo modello personalizzato" @@ -2119,11 +2034,8 @@ msgstr "specifica se l'attributo deve essere visualizzato nei risultati" msgid "display order" msgstr "mostra ordine" -msgid "" -"specifies the order in which attribute should be displayed in identify " -"results" -msgstr "" -"specifica l'ordine con cui l'attributo deve essere visualizzato nei risultati" +msgid "specifies the order in which attribute should be displayed in identify results" +msgstr "specifica l'ordine con cui l'attributo deve essere visualizzato nei risultati" msgid "Label" msgstr "Etichetta" @@ -2159,11 +2071,10 @@ msgid "featureinfo type" msgstr "tipo featureinfo" msgid "" -"specifies if the attribute should be rendered with an HTML widget on " -"GetFeatureInfo template." +"specifies if the attribute should be rendered with an HTML widget on GetFeatureInfo template." msgstr "" -"specifica se il rendering dell'attributo deve essere eseguito con un widget " -"HTML nel modello GetFeatureInfo." +"specifica se il rendering dell'attributo deve essere eseguito con un widget HTML nel modello " +"GetFeatureInfo." msgid "count" msgstr "conta" @@ -2216,15 +2127,11 @@ msgstr "ultimo modificato" msgid "date when attribute statistics were last updated" msgstr "data in cui le statistiche degli attributi sono stati aggiornati" -#, fuzzy -#| msgid "Append Data" msgid "Append to Dataset" -msgstr "Accoda dati" +msgstr "Aggiungi al dataset" -#, fuzzy -#| msgid "Return to Layer" msgid "Return to Dataset" -msgstr "Torna al livello" +msgstr "Torna al dataset" msgid "Preserve Metadata XML" msgstr "Preserva metadati XML" @@ -2241,10 +2148,8 @@ msgstr "Modifica contatto" msgid "Assign a new point of contact to the layers below:" msgstr "Assegna un nuovo contatto ai layer sottostanti:" -#, fuzzy -#| msgid "Layer WMS GetCapabilities document" msgid "Dataset WMS GetCapabilities document" -msgstr "Documento WMS GetCapabilities del livello" +msgstr "Documento WMS GetCapabilities del dataset" msgid "Filter Granules" msgstr "Filtra granuli" @@ -2300,18 +2205,14 @@ msgstr "Mediana" msgid "Standard Deviation" msgstr "Deviazione standard" -#, fuzzy -#| msgid "Rate this layer" msgid "Rate this dataset" -msgstr "Giudica questo livello" +msgstr "Valuta questo dataset" msgid "Analyze with" msgstr "Analizza con" -#, fuzzy -#| msgid "Download the" msgid "Download Dataset" -msgstr "Scarica il" +msgstr "Scarica dataset" msgid "Images" msgstr "Immagini" @@ -2356,11 +2257,10 @@ msgid "Pick your download format:" msgstr "Seleziona formato da scaricare:" msgid "" -"No data available for this resource. Please contact a system administrator " -"or a manager." +"No data available for this resource. Please contact a system administrator or a manager." msgstr "" -"Nessun dato disponibile per questa risorsa. Contattare un amministratore di " -"sistema o un responsabile." +"Nessun dato disponibile per questa risorsa. Contattare un amministratore di sistema o un " +"responsabile." msgid "Warning" msgstr "Attenzione" @@ -2377,11 +2277,9 @@ msgstr "Stili" msgid "Manage" msgstr "Gestisci" -#, fuzzy -#| msgid "Layers" msgid "Layer" msgid_plural "Layers" -msgstr[0] "Livelli" +msgstr[0] "Livello" msgstr[1] "Livelli" msgid "Append Data" @@ -2390,10 +2288,8 @@ msgstr "Accoda dati" msgid "Edit data" msgstr "Modifica dati" -#, fuzzy -#| msgid "View Metadata" msgid "View Dataset" -msgstr "Metadati" +msgstr "Vedi Dataset" msgid "Attribute Information" msgstr "Informazioni sugli attributi" @@ -2404,44 +2300,26 @@ msgstr "ISO Feature Catalogue" msgid "Legend" msgstr "Legenda" -#, fuzzy -#| msgid "Maps using this layer" msgid "Maps using this dataset" -msgstr "Mappe che usano questo livello" +msgstr "Mappe che usano questo dataset" -#, fuzzy -#| msgid "List of maps using this layer:" msgid "List of maps using this dataset:" -msgstr "Elenco di mappe che usano questo livello:" +msgstr "Elenco delle mappe che usano questo dataset:" -#, fuzzy -#| msgid "This layer is not currently used in any maps." msgid "This dataset is not currently used in any maps." -msgstr "Questo livello non è al momento utilizzato da alcuna mappa." +msgstr "Questo dataset non è al momento utilizzato da alcuna mappa." -#, fuzzy -#| msgid "Create a map using this layer" msgid "Create a map using this dataset" -msgstr "Crea una mappa usando questo livello" +msgstr "Crea una mappa usando questo dataset" -#, fuzzy -#| msgid "Click the button below to generate a new map based on this layer." msgid "Click the button below to generate a new map based on this dataset." -msgstr "" -"Clicca il pulsante qui sotto per generare una nuova mappa basata su questo " -"livello." +msgstr "Clicca il pulsante qui sotto per generare una nuova mappa basata su questo dataset." -#, fuzzy -#| msgid "Add the layer to an existing map" msgid "Add the dataset to an existing map" -msgstr "Aggiungi il livello ad una mappa esistente" +msgstr "Aggiungi il dataset ad una mappa esistente" -#, fuzzy -#| msgid "Click the button below to add the layer to the selected map." msgid "Click the button below to add the dataset to the selected map." -msgstr "" -"Fare clic sul pulsante sottostante per aggiungere il livello alla mappa " -"selezionata." +msgstr "Fare clic sul pulsante sottostante per aggiungere il dataset alla mappa selezionata." msgid "Add to Map" msgstr "Aggiungi alla mappa" @@ -2453,11 +2331,11 @@ msgid "List of documents related to this layer:" msgstr "Elenco di documenti correlati a questo livello:" msgid "" -"The following styles are associated with this layer. Choose a style to view " -"it in the preview map." +"The following styles are associated with this layer. Choose a style to view it in the " +"preview map." msgstr "" -"I seguenti stili sono associati a questo livello. Scegli uno stile da " -"visualizzare nell'anteprima della mappa." +"I seguenti stili sono associati a questo livello. Scegli uno stile da visualizzare " +"nell'anteprima della mappa." msgid "(default style)" msgstr "(stile predefinito)" @@ -2476,17 +2354,17 @@ msgstr "Aggiorna attributi e statistiche di questo livello" #, fuzzy #| msgid "" -#| "Click the button below to allow GeoNode refreshing the list of available " -#| "Layer Attributes. If the option 'WPS_ENABLED' has been also set on the " -#| "backend, it will recalculate their statistics too." +#| "Click the button below to allow GeoNode refreshing the list of available Layer " +#| "Attributes. If the option 'WPS_ENABLED' has been also set on the backend, it will " +#| "recalculate their statistics too." msgid "" -"Click the button below to allow GeoNode refreshing the list of available " -"Dataset Attributes. If the option 'WPS_ENABLED' has been also set on the " -"backend, it will recalculate their statistics too." +"Click the button below to allow GeoNode refreshing the list of available Dataset Attributes. " +"If the option 'WPS_ENABLED' has been also set on the backend, it will recalculate their " +"statistics too." msgstr "" -"Clicca il pulsante sotto per permettere a GeoNode di aggiornare la lista " -"degli attributi del livello. Se l'opzione 'WPS_ENABLED' è stata settata sul " -"backend, verranno anche ricalcolate le statistiche del livello." +"Clicca il pulsante sotto per permettere a GeoNode di aggiornare la lista degli attributi del " +"livello. Se l'opzione 'WPS_ENABLED' è stata settata sul backend, verranno anche ricalcolate " +"le statistiche del livello." msgid "Refresh Attributes and Statistics" msgstr "Aggiorna attributi e statistiche" @@ -2495,8 +2373,7 @@ msgid "Clear the Server Cache of this layer" msgstr "Pulisci la Cache Server di questo livello" msgid "Click the button below to wipe the tile-cache of this layer." -msgstr "" -"Clicca il pulsante qui sotto per eliminare la tile-cache di questo livello." +msgstr "Clicca il pulsante qui sotto per eliminare la tile-cache di questo livello." #, fuzzy #| msgid "Empty Tiled-Layer Cache" @@ -2504,13 +2381,10 @@ msgid "Empty Tiled-Dataset Cache" msgstr "Svuota la tile-cache del livello" msgid "Click the button below to change the permissions of this layer." -msgstr "" -"Clicca il pulsante qui sotto per modificare i permessi di questo livello." +msgstr "Clicca il pulsante qui sotto per modificare i permessi di questo livello." -#, fuzzy -#| msgid "Change Layer Permissions" msgid "Change Dataset Permissions" -msgstr "Cambia i permessi del livello" +msgstr "Cambia i permessi del dataset" msgid "Remove Mosaic Granules" msgstr "Rimuovere i granuli del mosaico" @@ -2518,13 +2392,13 @@ msgstr "Rimuovere i granuli del mosaico" #, python-format msgid "" "\n" -" Are you sure you want to remove Granule %(granule_id)s of the " -"Mosaic %(layer_title)s?\n" +" Are you sure you want to remove Granule %(granule_id)s of the Mosaic %(layer_title)s?\n" " " msgstr "" "\n" -" Si è sicuri di voler rimuovere il granulo %(granule_id)s del " -"mosaico %(layer_title)s?\n" +" Si è sicuri di voler rimuovere il granulo %(granule_id)s del mosaico %(layer_title)s?\n" " " msgid "This action affects the following maps:" @@ -2533,35 +2407,32 @@ msgstr "Questa azione avrà effetto sulle seguenti mappe:" msgid "No maps are using this layer" msgstr "Nessuna mappa utilizza questo livello" -#, fuzzy -#| msgid "Upload status" msgid "Upload Datasets" -msgstr "Stato caricamento" +msgstr "Carica dataset" #, python-format msgid "for %(layer_title)s" msgstr "per %(layer_title)s" msgid "" -"Note: this layer's orginal metadata was populated and preserved by importing " -"a metadata XML file.\n" +"Note: this layer's orginal metadata was populated and preserved by importing a metadata XML " +"file.\n" " This metadata cannot be edited." msgstr "" -"Nota: i metadati originali di questo livello sono stati popolati e " -"conservati importando un file XML di metadati.\n" +"Nota: i metadati originali di questo livello sono stati popolati e conservati importando un " +"file XML di metadati.\n" " Questi metadati non possono essere modificati." msgid "" -"Note: this layer's orginal metadata was populated by importing a metadata " -"XML file.\n" -" GeoNode's metadata import supports a subset of ISO, FGDC, and " -"Dublin Core metadata elements.\n" +"Note: this layer's orginal metadata was populated by importing a metadata XML file.\n" +" GeoNode's metadata import supports a subset of ISO, FGDC, and Dublin Core metadata " +"elements.\n" " Some of your original metadata may have been lost." msgstr "" -"Note: I metadati originali di questo livello sono stati popolati importando " -"un file di metadati XML.\n" -" L'import dei metadati di GeoNode supporta un sottinsieme di elementi " -"di metadata Dublin Core, ISO e FGDC.\n" +"Note: I metadati originali di questo livello sono stati popolati importando un file di " +"metadati XML.\n" +" L'import dei metadati di GeoNode supporta un sottinsieme di elementi di metadata " +"Dublin Core, ISO e FGDC.\n" " Alcuni dei tuoi metadati originali potrebbero essere andati persi." #, python-format @@ -2608,13 +2479,13 @@ msgstr "Rimuovi i livelli" #, python-format msgid "" "\n" -" Are you sure you want to remove %(layer_title)s?\n" +" Are you sure you want to remove %(layer_title)s?\n" " " msgstr "" "\n" -" Sei sicuro di voler eliminare %(layer_title)s?\n" +" Sei sicuro di voler eliminare %(layer_title)s?\n" " " #, fuzzy @@ -2628,29 +2499,25 @@ msgstr "Gestisci stili" #, python-format msgid "" "\n" -" Manage Available Styles for " -"%(layer_title)s\n" +" Manage Available Styles for %(layer_title)s\n" " " msgstr "" "\n" -" Gestisci gli stili disponibili per " -"%(layer_title)s\n" +" Gestisci gli stili disponibili per %(layer_title)s\n" " " -#, fuzzy -#| msgid "Layer Default Style" msgid "Dataset Default Style" -msgstr "Stile predefinito del livello" +msgstr "Stile predefinito del dataset" msgid "Available styles" msgstr "Stili disponibili" msgid "" -"Click on an available style in the upper box to assign it to this layer. " -"Selected styles appear in the lower box." +"Click on an available style in the upper box to assign it to this layer. Selected styles " +"appear in the lower box." msgstr "" -"Fare clic su uno stile disponibile nella casella in alto per assegnare a " -"questo livello. Stili selezionati vengono visualizzati nel riquadro in basso." +"Fare clic su uno stile disponibile nella casella in alto per assegnare a questo livello. " +"Stili selezionati vengono visualizzati nel riquadro in basso." msgid "Update Available Styles" msgstr "Aggiorna gli stili disponibili" @@ -2658,10 +2525,8 @@ msgstr "Aggiorna gli stili disponibili" msgid "Return to Layer" msgstr "Torna al livello" -#, fuzzy -#| msgid "Upload Layer Style" msgid "Upload Dataset Style" -msgstr "Carica stile del livello" +msgstr "Carica stile del dataset" msgid "(SLD - Style Layer Descriptor 1.0, 1.1)" msgstr "(SLD - Descrittore di stile del livello 1.0, 1.1)" @@ -2670,8 +2535,7 @@ msgid "WARNING" msgstr "ATTENZIONE" msgid "This will most probably overwrite the current default style!" -msgstr "" -"Questo molto probabilmente sovrascriverà lo stile predefinito corrente!" +msgstr "Questo molto probabilmente sovrascriverà lo stile predefinito corrente!" msgid "Preview" msgstr "Anteprima" @@ -2679,34 +2543,29 @@ msgstr "Anteprima" msgid "Dataset Attributes" msgstr "Attributi del dataset" -#, fuzzy -#| msgid "Upload status" msgid "Upload Dataset" -msgstr "Stato caricamento" +msgstr "Carica dataset" msgid "Upload status" msgstr "Stato caricamento" msgid "" -"This file needs additional configuration to complete the upload process. " -"Please click on the button to fill the required configuration" +"This file needs additional configuration to complete the upload process. Please click on the " +"button to fill the required configuration" msgstr "" -"Questo file necessita di una configurazione aggiuntiva per completare il " -"processo di caricamento. Clicca sul pulsante per riempire la configurazione " -"richiesta" +"Questo file necessita di una configurazione aggiuntiva per completare il processo di " +"caricamento. Clicca sul pulsante per riempire la configurazione richiesta" msgid "Delete" -msgstr "Cancella" +msgstr "Elimina" msgid "Upload process completed" msgstr "Processo di caricamento completato" -msgid "" -"The upload process cannot be completed because the original file is no more " -"available" +msgid "The upload process cannot be completed because the original file is no more available" msgstr "" -"Impossibile completare il processo di caricamento perché il file originale " -"non è più disponibile" +"Impossibile completare il processo di caricamento perché il file originale non è più " +"disponibile" msgid "Created" msgstr "Creato" @@ -2726,10 +2585,8 @@ msgstr "Eliminare caricamenti incompleti" msgid "Are you sure you want to remove this incomplete upload?" msgstr "Rimuovere questo caricamento incompleto?" -#, fuzzy -#| msgid "Upload Layer Step: Set SRS" msgid "Upload Dataset Step: Set SRS" -msgstr "Carica livello, passo: Imposta SRS" +msgstr "Carica dataset, passo: Imposta SRS" msgid "Provide CRS for " msgstr "Fornire CRS per " @@ -2740,34 +2597,28 @@ msgstr "Sistema di riferimento delle coordinate" #, fuzzy #| msgid "" #| "\n" -#| " A coordinate reference system for this layer could not be " -#| "determined.\n" -#| " Locate or enter the appropriate ESPG code for this layer " -#| "below.\n" +#| " A coordinate reference system for this layer could not be determined.\n" +#| " Locate or enter the appropriate ESPG code for this layer below.\n" #| " One way to do this is do visit:\n" -#| " prj2epsg\n" +#| " prj2epsg\n" #| " and enter the following:\n" #| " " msgid "" "\n" -" A coordinate reference system for this dataset could not be " -"determined.\n" -" Locate or enter the appropriate ESPG code for this dataset " -"below.\n" +" A coordinate reference system for this dataset could not be determined.\n" +" Locate or enter the appropriate ESPG code for this dataset below.\n" " One way to do this is do visit:\n" -" prj2epsg\n" +" prj2epsg\n" " and enter the following:\n" " " msgstr "" "\n" -" Un sistema di riferimento di coordinate per questo strato " -"non poteva essere determinata.\n" -" Individuare o immettere il codice ESPG appropriato per " -"questo livello sottostante.\n" -" Un modo per farlo è fare visitare: prj2epsg\n" +" Un sistema di riferimento di coordinate per questo strato non poteva essere " +"determinata.\n" +" Individuare o immettere il codice ESPG appropriato per questo livello " +"sottostante.\n" +" Un modo per farlo è fare visitare: prj2epsg\n" " e immettere il seguente:\n" " " @@ -2784,27 +2635,24 @@ msgid "Select a Source SRS" msgstr "Seleziona un SRS di origine" msgid "" -"Source SRS EPSG Code is mandatory and represents the native data Spatial " -"Reference System.\n" +"Source SRS EPSG Code is mandatory and represents the native data Spatial Reference System.\n" "

\n" -" This must be coherent with the Geometry values (lon/lat " -"coordinates as an instance) stored on the geospatial dataset.\n" +" This must be coherent with the Geometry values (lon/lat coordinates as " +"an instance) stored on the geospatial dataset.\n" "

\n" -" If not specified on the geospatial data itself, it must " -"be manually declared by the operator.\n" +" If not specified on the geospatial data itself, it must be manually " +"declared by the operator.\n" "

\n" -" More information is provided at the bottom of the page " -"in the \"Additional Help\" sections.\n" +" More information is provided at the bottom of the page in the " +"\"Additional Help\" sections.\n" " " msgstr "" -"Source SRS EPSG Code è obbligatorio e rappresenta il sistema di riferimento " -"spaziale dei dati nativi.

Deve essere coerente " -"con i valori Geometry (coordinate lon/lat come istanza) archiviati nel set " -"di dati geospaziali.

Se non specificato nei dati " -"geospaziali stessi, deve essere dichiarato manualmente " -"dall'operatore.

Ulteriori informazioni sono " -"disponibili nella parte inferiore della pagina nelle sezioni \"Guida " -"aggiuntiva\". " +"Source SRS EPSG Code è obbligatorio e rappresenta il sistema di riferimento spaziale dei " +"dati nativi.

Deve essere coerente con i valori Geometry " +"(coordinate lon/lat come istanza) archiviati nel set di dati geospaziali. " +"

Se non specificato nei dati geospaziali stessi, deve essere dichiarato manualmente " +"dall'operatore.

Ulteriori informazioni sono disponibili nella " +"parte inferiore della pagina nelle sezioni \"Guida aggiuntiva\". " msgid "Next" msgstr "Prossimo" @@ -2831,140 +2679,130 @@ msgid "Spatial Reference System" msgstr "Sistema di riferimento spaziale" msgid "" -"A spatial reference system (SRS) or coordinate reference system (CRS) is a " -"coordinate-based local,\n" -" regional or global system used to locate " -"geographical entities. A spatial reference system defines a specific map\n" -" projection, as well as transformations between " -"different spatial reference systems. Spatial reference systems are\n" -" defined by the OGC's Simple feature access using " -"well-known text, and support has been implemented by several\n" -" standards-based geographic information systems. " -"Spatial reference systems can be referred to using a SRID integer,\n" -" including EPSG codes defined by the " -"International Association of Oil and Gas Producers.\n" -" It is specified in ISO 19111:2007 Geographic " -"information—Spatial referencing by coordinates, also published as\n" -" OGC Abstract Specification, Topic 2: Spatial " -"referencing by coordinate." -msgstr "" -"Un sistema di riferimento spaziale (SRS) o un sistema di riferimento delle " -"coordinate (CRS) è un sistema di riferimento\n" -" sistema regionale o globale utilizzato per " -"individuare le entità geografiche. Un sistema di riferimento spaziale " -"definisce una mappa specifica\n" -" proiezioni, nonché trasformazioni tra diversi " -"sistemi di riferimento spaziale. I sistemi di riferimento spaziali sono\n" -" definito dall'accesso alle funzionalità semplice " -"dell'OGC utilizzando testo noto, e il supporto è stato implementato da " -"diversi\n" -" sistemi di informazione geografica basati su " -"standard. I sistemi di riferimento spaziale possono essere indicati " -"utilizzando un numero intero SRID,\n" -" compresi i codici EPSG definiti " -"dall'Associazione internazionale dei produttori di petrolio e gas.\n" -" È specificato in ISO 19111:2007 Informazioni " -"geografiche: riferimento spaziale in base alle coordinate, pubblicato anche " -"come\n" -" Specifica astratta OGC, Argomento 2: Riferimento " -"spaziale per coordinata." +"A spatial reference system (SRS) or coordinate reference system (CRS) is a coordinate-based " +"local,\n" +" regional or global system used to locate geographical entities. " +"A spatial reference system defines a specific map\n" +" projection, as well as transformations between different spatial " +"reference systems. Spatial reference systems are\n" +" defined by the OGC's Simple feature access using well-known " +"text, and support has been implemented by several\n" +" standards-based geographic information systems. Spatial " +"reference systems can be referred to using a SRID integer,\n" +" including EPSG codes defined by the International Association of " +"Oil and Gas Producers.\n" +" It is specified in ISO 19111:2007 Geographic information—Spatial " +"referencing by coordinates, also published as\n" +" OGC Abstract Specification, Topic 2: Spatial referencing by " +"coordinate." +msgstr "" +"Un sistema di riferimento spaziale (SRS) o un sistema di riferimento delle coordinate (CRS) " +"è un sistema di riferimento\n" +" sistema regionale o globale utilizzato per individuare le entità " +"geografiche. Un sistema di riferimento spaziale definisce una mappa specifica\n" +" proiezioni, nonché trasformazioni tra diversi sistemi di " +"riferimento spaziale. I sistemi di riferimento spaziali sono\n" +" definito dall'accesso alle funzionalità semplice dell'OGC " +"utilizzando testo noto, e il supporto è stato implementato da diversi\n" +" sistemi di informazione geografica basati su standard. I sistemi " +"di riferimento spaziale possono essere indicati utilizzando un numero intero SRID,\n" +" compresi i codici EPSG definiti dall'Associazione internazionale " +"dei produttori di petrolio e gas.\n" +" È specificato in ISO 19111:2007 Informazioni geografiche: " +"riferimento spaziale in base alle coordinate, pubblicato anche come\n" +" Specifica astratta OGC, Argomento 2: Riferimento spaziale per " +"coordinata." msgid "Identifiers" msgstr "Identificatori" msgid "" "\n" -" A Spatial Reference System Identifier (SRID) is a " -"unique value used to unambiguously identify projected, unprojected,\n" -" and local spatial coordinate system definitions. " -"These coordinate systems form the heart of all GIS applications.\n" +" A Spatial Reference System Identifier (SRID) is a unique value used " +"to unambiguously identify projected, unprojected,\n" +" and local spatial coordinate system definitions. These coordinate " +"systems form the heart of all GIS applications.\n" "\n" -" Virtually all major spatial vendors have created " -"their own SRID implementation or refer to those of an authority,\n" +" Virtually all major spatial vendors have created their own SRID " +"implementation or refer to those of an authority,\n" " such as the European Petroleum Survey Group (EPSG).\n" " " msgstr "" "\n" -"Un identificatore SRID (Spatial Reference System Identifier) è un valore " -"univoco utilizzato per identificare in modo inequivocabile\n" -" e le definizioni del sistema di coordinate spaziali " -"locali. Questi sistemi di coordinate costituiscono il cuore di tutte le " -"applicazioni GIS.\n" +"Un identificatore SRID (Spatial Reference System Identifier) è un valore univoco utilizzato " +"per identificare in modo inequivocabile\n" +" e le definizioni del sistema di coordinate spaziali locali. Questi " +"sistemi di coordinate costituiscono il cuore di tutte le applicazioni GIS.\n" "\n" -"Praticamente tutti i principali fornitori spaziali hanno creato la propria " -"implementazione SRID o si riferiscono a quelli di un'autorità,\n" +"Praticamente tutti i principali fornitori spaziali hanno creato la propria implementazione " +"SRID o si riferiscono a quelli di un'autorità,\n" " come l'European Petroleum Survey Group (EPSG).\n" " " msgid "" -"NOTE: As of 2005 the EPSG SRID values are now maintained by the " -"International\n" -" Association of Oil & Gas Producers (OGP) " -"Surveying & Positioning Committee" +"NOTE: As of 2005 the EPSG SRID values are now maintained by the International\n" +" Association of Oil & Gas Producers (OGP) Surveying & Positioning " +"Committee" msgstr "" -"NOTA: a partire dal 2005 i valori EPSG SRID sono ora mantenuti " -"dall'International\n" -" Comitato di rilevamento e posizionamento " -"dell'Associazione dei produttori di petrolio e gas (OGP)" +"NOTA: a partire dal 2005 i valori EPSG SRID sono ora mantenuti dall'International\n" +" Comitato di rilevamento e posizionamento dell'Associazione dei " +"produttori di petrolio e gas (OGP)" msgid "" "\n" -" SRIDs are the primary key for the Open Geospatial " -"Consortium (OGC) spatial_ref_sys metadata table for the Simple\n" -" Features for SQL Specification, Versions 1.1 and " -"1.2, which is defined as follows:\n" +" SRIDs are the primary key for the Open Geospatial Consortium (OGC) " +"spatial_ref_sys metadata table for the Simple\n" +" Features for SQL Specification, Versions 1.1 and 1.2, which is " +"defined as follows:\n" " " msgstr "" "\n" -"Gli S SRID sono la chiave primaria per l'Open Geospatial Consortium (OGC) " -"spatial_ref_sys di metadati per il\n" -" Funzionalità per la specifica SQL, le versioni 1.1 e " -"1.2, definite come segue:\n" +"Gli S SRID sono la chiave primaria per l'Open Geospatial Consortium (OGC) spatial_ref_sys di " +"metadati per il\n" +" Funzionalità per la specifica SQL, le versioni 1.1 e 1.2, definite " +"come segue:\n" " " msgid "" "\n" -" In spatially enabled databases (such as IBM DB2, IBM " -"Informix, Microsoft SQL Server, MySQL, Oracle RDBMS, Teradata, PostGIS and\n" -" SQL Anywhere), SRIDs are used to uniquely identify " -"the coordinate systems used to define columns of spatial data or individual\n" -" spatial objects in a spatial column (depending on " -"the spatial implementation). SRIDs are typically associated with a well " -"known\n" -" text (WKT) string definition of the coordinate " -"system (SRTEXT, above). From the Well Known Text Wikipedia page\n" +" In spatially enabled databases (such as IBM DB2, IBM Informix, " +"Microsoft SQL Server, MySQL, Oracle RDBMS, Teradata, PostGIS and\n" +" SQL Anywhere), SRIDs are used to uniquely identify the coordinate " +"systems used to define columns of spatial data or individual\n" +" spatial objects in a spatial column (depending on the spatial " +"implementation). SRIDs are typically associated with a well known\n" +" text (WKT) string definition of the coordinate system (SRTEXT, " +"above). From the Well Known Text Wikipedia page\n" " " msgstr "" "\n" -"Nei database abilitati per l'ambiente (come IBM DB2, IBM Informix, Microsoft " -"SQL Server, MySQL, Oracle RDBMS, Teradata, PostGIS e\n" -" SQL Anywhere), gli SED vengono utilizzati per " -"identificare in modo univoco i sistemi di coordinate utilizzati per definire " -"colonne di dati spaziali o singoli\n" +"Nei database abilitati per l'ambiente (come IBM DB2, IBM Informix, Microsoft SQL Server, " +"MySQL, Oracle RDBMS, Teradata, PostGIS e\n" +" SQL Anywhere), gli SED vengono utilizzati per identificare in modo " +"univoco i sistemi di coordinate utilizzati per definire colonne di dati spaziali o singoli\n" " oggetti spaziali in una colonna spaziale (a seconda " "dell'implementazione spaziale). I SED sono in genere associati a un\n" -" text (WKT) definizione di stringa del sistema di " -"coordinate (SRTEXT, sopra). Dalla pagina Wikipedia di Ben Known Text\n" +" text (WKT) definizione di stringa del sistema di coordinate (SRTEXT, " +"sopra). Dalla pagina Wikipedia di Ben Known Text\n" " " msgid "" -"“A WKT string for a spatial reference system describes the datum, geoid, " -"coordinate system,\n" +"“A WKT string for a spatial reference system describes the datum, geoid, coordinate system,\n" " and map projection of the spatial objects”." msgstr "" -"\"Una stringa WKT per un sistema di riferimento spaziale descrive il sistema " -"di coordinate di riferimento, geoide,\n" +"\"Una stringa WKT per un sistema di riferimento spaziale descrive il sistema di coordinate " +"di riferimento, geoide,\n" " e la proiezione mappa degli oggetti spaziali\"." msgid "" "\n" -" Here are two common coordinate systems with their " -"EPSG SRID value followed by their well known text:\n" +" Here are two common coordinate systems with their EPSG SRID value " +"followed by their well known text:\n" " " msgstr "" "\n" -" Qui ci sono due sistemi di coordinate comuni con il " -"loro valore di EPSG SRID seguita dal loro WKT:\n" +" Qui ci sono due sistemi di coordinate comuni con il loro valore di " +"EPSG SRID seguita dal loro WKT:\n" " " msgid "" @@ -2987,87 +2825,76 @@ msgstr "" msgid "" "\n" -" SRID values associated with spatial data can be used " -"to constrain spatial operations — for instance, spatial operations cannot be " -"performed\n" -" between spatial objects with differing SRIDs in some " -"systems, or trigger coordinate system transformations between spatial " -"objects in others.\n" +" SRID values associated with spatial data can be used to constrain " +"spatial operations — for instance, spatial operations cannot be performed\n" +" between spatial objects with differing SRIDs in some systems, or " +"trigger coordinate system transformations between spatial objects in others.\n" " " msgstr "" "\n" -"I valori SRID associati ai dati spaziali possono essere utilizzati per " -"vincolare le operazioni spaziali, ad esempio le operazioni spaziali non " -"possono essere eseguite\n" -" tra oggetti spaziali con SRID diversi in alcuni " -"sistemi o attivare trasformazioni del sistema di coordinate tra oggetti " -"spaziali in altri.\n" +"I valori SRID associati ai dati spaziali possono essere utilizzati per vincolare le " +"operazioni spaziali, ad esempio le operazioni spaziali non possono essere eseguite\n" +" tra oggetti spaziali con SRID diversi in alcuni sistemi o attivare " +"trasformazioni del sistema di coordinate tra oggetti spaziali in altri.\n" " " msgid "" -"Source SRS EPSG Code is mandatory and represents the native data Spatial " -"Reference System. This must be coherent with the\n" -" Geometry values (lon/lat coordinates as an " -"instance) stored on the geospatial dataset. If not specified on the " -"geospatial data itself, it\n" +"Source SRS EPSG Code is mandatory and represents the native data Spatial Reference System. " +"This must be coherent with the\n" +" Geometry values (lon/lat coordinates as an instance) stored on " +"the geospatial dataset. If not specified on the geospatial data itself, it\n" " must be manually declared by the operator." msgstr "" -"Source SRS EPSG Code è obbligatorio e rappresenta il sistema di riferimento " -"spaziale dei dati nativi. Questo deve essere coerente con il\n" -" Valori geometrici (coordinate lon/lat come " -"istanza) archiviati nel set di dati geospaziali. Se non viene specificato " -"sui dati geospaziali stessi,\n" -" deve essere dichiarato manualmente " -"dall'operatore." +"Source SRS EPSG Code è obbligatorio e rappresenta il sistema di riferimento spaziale dei " +"dati nativi. Questo deve essere coerente con il\n" +" Valori geometrici (coordinate lon/lat come istanza) archiviati " +"nel set di dati geospaziali. Se non viene specificato sui dati geospaziali stessi,\n" +" deve essere dichiarato manualmente dall'operatore." msgid "" -"Target SRS EPSG Code is optional. This must be used only if we need to re-" -"project the coordinates from Source SRS to another one.\n" +"Target SRS EPSG Code is optional. This must be used only if we need to re-project the " +"coordinates from Source SRS to another one.\n" " " msgstr "" -"Target SRS EPSG Code è facoltativo. Questo deve essere utilizzato solo se è " -"necessario ri-proiettare le coordinate da Source SRS ad un altro.\n" +"Target SRS EPSG Code è facoltativo. Questo deve essere utilizzato solo se è necessario ri-" +"proiettare le coordinate da Source SRS ad un altro.\n" " " #, fuzzy #| msgid "Upload Layer Step: CSV Field Mapping" msgid "Upload Dataset Step: CSV Field Mapping" -msgstr "Upload Layer Step: CSV Field Mapping" +msgstr "Carica dataset, passo: campi CSV" msgid "Geospatial Data" msgstr "Dati geospaziali" msgid "" -"Please indicate which attributes contain the latitude and longitude " -"coordinates in the CSV data." +"Please indicate which attributes contain the latitude and longitude coordinates in the CSV " +"data." msgstr "" -"Si prega di indicare quali attributi contengono le coordinate di latitudine " -"e longitudine nei dati CSV." +"Si prega di indicare quali attributi contengono le coordinate di latitudine e longitudine " +"nei dati CSV." msgid "" "With this data, GeoNode was able to guess which attributes contain the\n" -" latitude and longitude coordinates, but please confirm that " -"the correct\n" +" latitude and longitude coordinates, but please confirm that the correct\n" " attributes are selected below." msgstr "" -"Con questi dati, GeoNode è stato in grado di capire quali attributi " -"contengono\n" -" latitudine e longitudine, ma si prega di verificare che le " -"coordinate siano corrette\n" +"Con questi dati, GeoNode è stato in grado di capire quali attributi contengono\n" +" latitudine e longitudine, ma si prega di verificare che le coordinate siano " +"corrette\n" " per gli attributi sono selezionati di seguito." msgid "Select an attribute" msgstr "Seleziona un attributo" msgid "" -"We did not detect columns that could be used for the latitude and " -"longitude.\n" -" Please verify that you have two columns in your csv file that can be " -"used for\n" +"We did not detect columns that could be used for the latitude and longitude.\n" +" Please verify that you have two columns in your csv file that can be used for\n" " the latitude and longitude." msgstr "" -"Non abbiamo rilevato colonne che potessero essere utilizzate per latitudine " -"e la longitudine.\n" +"Non abbiamo rilevato colonne che potessero essere utilizzate per latitudine e la " +"longitudine.\n" " Verificare di avere due colonne nel file csv che possono essere\n" " latitudine e la longitudine." @@ -3080,10 +2907,8 @@ msgstr "Torna a" msgid "Upload Form" msgstr "Carica Form" -#, fuzzy -#| msgid "Upload Layer Step: Time" msgid "Upload Dataset Step: Time" -msgstr "Caricamento livello: Dimensione temporale" +msgstr "Caricamento dataset: Dimensione temporale" msgid "Inspect data for " msgstr "Esamina dati per " @@ -3092,26 +2917,25 @@ msgid "Configure as Time-Series" msgstr "Configura come serie temporale" msgid "" -"Toggling this selector allows you to configure (or not) this data as a time " -"series; in this case you will also have to select an attribute\n" +"Toggling this selector allows you to configure (or not) this data as a time series; in this " +"case you will also have to select an attribute\n" " to drive the time dimension.\n" "

\n" -" If GeoNode is not able to parse any of the values for " -"the selected attribute red markers will appear to highlight the problems.\n" +" If GeoNode is not able to parse any of the values for the selected " +"attribute red markers will appear to highlight the problems.\n" "

\n" -" More information is provided at the bottom of the page " -"in the \"Additional Help\" sections.\n" +" More information is provided at the bottom of the page in the " +"\"Additional Help\" sections.\n" " " msgstr "" -"L'attivazione/attivazione/attivazione/configurazione di questo selettore " -"consente di configurare (o meno) questi dati come serie di tempo; in questo " -"caso si dovrà anche selezionare un attributo\n" -" per guidare la dimensione " -"temporale.

Se GeoNode non è in grado di " -"analizzare nessuno dei valori per l'attributo selezionato, gli indicatori " -"rossi verranno visualizzati per evidenziare i problemi. " -"

Ulteriori informazioni sono disponibili nella parte inferiore della " -"pagina nelle sezioni \"Guida aggiuntiva\". " +"L'attivazione/attivazione/attivazione/configurazione di questo selettore consente di " +"configurare (o meno) questi dati come serie di tempo; in questo caso si dovrà anche " +"selezionare un attributo\n" +" per guidare la dimensione temporale.

Se " +"GeoNode non è in grado di analizzare nessuno dei valori per l'attributo selezionato, gli " +"indicatori rossi verranno visualizzati per evidenziare i problemi. " +"

Ulteriori informazioni sono disponibili nella parte inferiore della pagina nelle " +"sezioni \"Guida aggiuntiva\". " msgid "No" msgstr "No" @@ -3122,12 +2946,9 @@ msgstr "Utilizzare un attributo timestamp esistente nei dati" msgid "Yes: with an existing Time-Attribute" msgstr "Sì: con un attributo Time esistente" -msgid "" -"Yes: by converting data to a timestamp using standard date/time " -"representation" +msgid "Yes: by converting data to a timestamp using standard date/time representation" msgstr "" -"Sì: convertendo i dati in un timestamp utilizzando la rappresentazione " -"standard di data/ora" +"Sì: convertendo i dati in un timestamp utilizzando la rappresentazione standard di data/ora" msgid "Convert a number field into a year" msgstr "Convertire un campo numerico in un anno" @@ -3135,12 +2956,10 @@ msgstr "Convertire un campo numerico in un anno" msgid "Yes: by converting a number as Year" msgstr "Sì: convertendo un numero come Anno" -msgid "" -"Convert data to a timestamp using standard date/time representation or a " -"custom format" +msgid "Convert data to a timestamp using standard date/time representation or a custom format" msgstr "" -"Convertire i dati in un timestamp utilizzando la rappresentazione di data/" -"ora standard o un formato personalizzato" +"Convertire i dati in un timestamp utilizzando la rappresentazione di data/ora standard o un " +"formato personalizzato" msgid "Start Importer" msgstr "Avvia importazione" @@ -3187,12 +3006,10 @@ msgstr "definito dalla risoluzione" msgid "Continuous Intervals" msgstr "Intervalli continui" -msgid "" -"for data that is frequently updated, resolution describes the frequency of " -"updates" +msgid "for data that is frequently updated, resolution describes the frequency of updates" msgstr "" -"per i dati che viene aggiornato di frequente, risoluzione descrive la " -"frequenza degli aggiornamenti" +"per i dati che viene aggiornato di frequente, risoluzione descrive la frequenza degli " +"aggiornamenti" msgid "Resolution of time attribute" msgstr "Risoluzione di un attributo tempo" @@ -3200,31 +3017,20 @@ msgstr "Risoluzione di un attributo tempo" msgid "Enabling Time" msgstr "Tempo di attivazione" -#, fuzzy -#| msgid "" -#| "A layer can support one or two time attributes. If a single\n" -#| " attribute is used, the layer is considered to " -#| "contain data that is valid at single points in time. If two\n" -#| " attributes are used, the second attribute " -#| "represents the end of a valid period hence the layer is considered\n" -#| " to contain data that is valid at certain periods " -#| "in time." msgid "" "A dataset can support one or two time attributes. If a single\n" -" attribute is used, the dataset is considered to " -"contain data that is valid at single points in time. If two\n" -" attributes are used, the second attribute represents " -"the end of a valid period hence the dataset is considered\n" -" to contain data that is valid at certain periods in " -"time." -msgstr "" -"Un layer può supportare uno o due attributi temporali. Se un singolo\n" -" attributo viene utilizzato, il layer è considerato " -"contenere dati validi in singoli punti nel tempo. Se due\n" -" attributi, il secondo attributo rappresenta la fine " -"di un periodo valido, quindi il layer è considerato\n" -" per contenere dati validi in determinati periodi di " -"tempo." +" attribute is used, the dataset is considered to contain data that is " +"valid at single points in time. If two\n" +" attributes are used, the second attribute represents the end of a " +"valid period hence the dataset is considered\n" +" to contain data that is valid at certain periods in time." +msgstr "" +"Un dataset può supportare uno o due attributi temporali. Se un singolo\n" +" attributo viene utilizzato, il layer è considerato contenere dati " +"validi in singoli punti nel tempo. Se due\n" +" attributi, il secondo attributo rappresenta la fine di un periodo " +"valido, quindi il layer è considerato\n" +" per contenere dati validi in determinati periodi di tempo." msgid "Selecting an Attribute" msgstr "Seleziona un attributo" @@ -3243,30 +3049,24 @@ msgstr "Un numero che rappresenta l'anno" msgid "" "\n" -" For text attributes, one can specify a custom format " -"(as part of the \"Advanced Options\") or use the 'best guess' approach which " -"will try to\n" -" automatically translate well-known recognized " -"patterns into valid times.\n" +" For text attributes, one can specify a custom format (as part of the " +"\"Advanced Options\") or use the 'best guess' approach which will try to\n" +" automatically translate well-known recognized patterns into valid " +"times.\n" " " msgstr "" "\n" -" Per gli attributi di testo, è possibile specificare " -"un formato personalizzato (come parte delle \"Opzioni avanzate\") o " -"utilizzare l'approccio \"best guess\" che cercherà di\n" -" convertire automaticamente i patterns riconosciuti " -"in date valide.\n" +" Per gli attributi di testo, è possibile specificare un formato " +"personalizzato (come parte delle \"Opzioni avanzate\") o utilizzare l'approccio \"best " +"guess\" che cercherà di\n" +" convertire automaticamente i patterns riconosciuti in date valide.\n" " " msgid "The 'best guess' will handle date and optional time variants of" -msgstr "" -"L''ipotesi migliore' in grado di gestire le varianti di data e ora opzionale " -"di" +msgstr "L''ipotesi migliore' in grado di gestire le varianti di data e ora opzionale di" msgid "In terms of the formatting flags noted above, these are" -msgstr "" -"Per quanto riguarda le bandiere di formattazione osservato in precedenza, si " -"tratta di" +msgstr "Per quanto riguarda le bandiere di formattazione osservato in precedenza, si tratta di" msgid "Modal Header" msgstr "Intestazione modale" @@ -3295,11 +3095,9 @@ msgstr "Scelta sbagliata" msgid "Please, select one Time Attribute to test!" msgstr "Si prega di selezionare un Attributo Temporale!" -msgid "" -"Returning to the upload starting page in 5seconds " +msgid "Returning to the upload starting page in 5seconds " msgstr "" -"Sarai rediretto alla pagina iniziale di caricamento entro 5 secondi " +"Sarai rediretto alla pagina iniziale di caricamento entro 5 secondi " msgid " Or just go " msgstr " O semplicemente andare " @@ -3318,21 +3116,13 @@ msgid "You are attempting to {action_type} a raster dataset with a vector." msgstr "Si sta tentando di {action_type} un livello raster con un vettore." #, fuzzy, python-brace-format -#| msgid "" -#| "You are attempting to {action_type} a vector layer with an unknown format." -msgid "" -"You are attempting to {action_type} a vector dataset with an unknown format." -msgstr "" -"Si sta tentando di {action_type} un livello vettoriale con un formato " -"sconosciuto." +#| msgid "You are attempting to {action_type} a vector layer with an unknown format." +msgid "You are attempting to {action_type} a vector dataset with an unknown format." +msgstr "Si sta tentando di {action_type} un livello vettoriale con un formato sconosciuto." #, python-brace-format -msgid "" -"Please ensure the name is consistent with the file you are trying to " -"{action_type}." -msgstr "" -"Assicurarsi che il nome sia coerente con il file che si sta tentando di " -"{action_type}." +msgid "Please ensure the name is consistent with the file you are trying to {action_type}." +msgstr "Assicurarsi che il nome sia coerente con il file che si sta tentando di {action_type}." #, fuzzy #| msgid "Local GeoNode layer has no geometry type." @@ -3341,83 +3131,55 @@ msgstr "Il livello GeoNode locale non ha alcun tipo di geometria." #, python-brace-format msgid "" -"Please ensure there is at least one geometry " -"type that is consistent with the file you are " -"trying to {action_type}." +"Please ensure there is at least one geometry type that is " +"consistent with the file you are trying to {action_type}." msgstr "" -"Assicurarsi che sia disponibile almeno un tipo di geometria coerente con il " -"file che si sta tentando di {action_type}." +"Assicurarsi che sia disponibile almeno un tipo di geometria coerente con il file che si sta " +"tentando di {action_type}." -#, fuzzy -#| msgid "The selected Layer does not exists in the catalog." msgid "The selected Dataset does not exists in the catalog." -msgstr "Il livello selezionato non esiste nel catalogo." +msgstr "Il dataset selezionato non esiste nel catalogo." -#, fuzzy -#| msgid "Please ensure that the layer structure is consistent " msgid "Please ensure that the dataset structure is consistent " -msgstr "Assicurarsi che la struttura del livello sia coerente " +msgstr "Assicurarsi che la struttura del dataset sia coerente " -msgid "" -"Some error occurred while trying to access the uploaded schema: {str(e)}" +msgid "Some error occurred while trying to access the uploaded schema: {str(e)}" msgstr "" -"Si è verificato un errore durante il tentativo di accesso allo schema " -"caricato: {str(e)}" +"Si è verificato un errore durante il tentativo di accesso allo schema caricato: {str(e)}" msgid "" -"There was an error while attempting to upload your data. Please try again, " -"or contact and administrator if the problem continues." +"There was an error while attempting to upload your data. Please try again, or contact and " +"administrator if the problem continues." msgstr "" -"E' occorso un errore durante l'invio dei tuoi dati. Riprova o contatta un " -"amministratore se il problema persiste." +"E' occorso un errore durante l'invio dei tuoi dati. Riprova o contatta un amministratore se " +"il problema persiste." -#, fuzzy -#| msgid "" -#| "Note: this layer's orginal metadata was populated and preserved by " -#| "importing a metadata XML file. This metadata cannot be edited." msgid "" -"Note: this dataset's orginal metadata was populated and preserved by " -"importing a metadata XML file. This metadata cannot be edited." +"Note: this dataset's orginal metadata was populated and preserved by importing a metadata " +"XML file. This metadata cannot be edited." msgstr "" -"Nota: i metadati originali di questo livello sono stati popolati e " -"conservati importando un file XML di metadati. Questi metadati non possono " -"essere modificati." +"Nota: i metadati originali di questo dataset sono stati popolati e conservati importando un " +"file XML di metadati. Questi metadati non possono essere modificati." -#, fuzzy -#| msgid "You are not permitted to delete this layer" msgid "You are not permitted to delete this dataset" -msgstr "Non hai il permesso di eliminare questo livello" +msgstr "Non hai il permesso di eliminare questo dataset" -#, fuzzy -#| msgid "You do not have permissions for this layer." msgid "You do not have permissions for this dataset." -msgstr "Non hai i permessi per questo livello." +msgstr "Non hai i permessi per questo dataset." -#, fuzzy -#| msgid "You are not permitted to modify this layer" msgid "You are not permitted to modify this dataset" -msgstr "Non hai i permessi per modificare questo livello" +msgstr "Non hai i permessi per modificare questo dataset" -#, fuzzy -#| msgid "You are not permitted to modify this layer's metadata" msgid "You are not permitted to modify this dataset's metadata" -msgstr "Non hai il permesso di modificare i metadati di questo livello" +msgstr "Non hai il permesso di modificare i metadati di questo dataset" -#, fuzzy -#| msgid "You are not permitted to view this layer" msgid "You are not permitted to view this dataset" -msgstr "Non hai il permesso di visualizzare questo livello" +msgstr "Non hai il permesso di visualizzare questo dataset" -#, fuzzy -#| msgid "" -#| "This layer is a member of a layer group, you must remove the layer from " -#| "the group before deleting." msgid "" -"This dataset is a member of a layer group, you must remove the dataset from " -"the group before deleting." -msgstr "" -"Questo livello è membro di un gruppo, devi rimuoverlo dal gruppo prima di " -"eliminarlo." +"This dataset is a member of a layer group, you must remove the dataset from the group before " +"deleting." +msgstr "Questo dataset è membro di un gruppo, devi rimuoverlo dal gruppo prima di eliminarlo." #, python-format msgid "couldn't generate thumbnail: %s" @@ -3516,11 +3278,9 @@ msgstr "Valuta questa mappa" msgid "Download Map" msgstr "Scarica la mappa" -#, fuzzy -#| msgid "Maps" msgid "Map" msgid_plural "Maps" -msgstr[0] "Mappe" +msgstr[0] "Mappa" msgstr[1] "Mappe" msgid "Map Layers" @@ -3560,25 +3320,23 @@ msgstr "" msgid "" "\n" -"

Could not find downloadable layers " -"for this map. You can go back to \n" +"
Could not find downloadable layers for this map. " +"You can go back to \n" " " msgstr "" "\n" -"
Impossibile trovare livelli " -"scaricabili per questa mappa. Si può tornare a \n" +"
Impossibile trovare livelli scaricabili per questa " +"mappa. Si può tornare a \n" " " msgid "" "\n" -" Additionally, the map contains these layers which will not be " -"downloaded\n" +" Additionally, the map contains these layers which will not be downloaded\n" " due to security restrictions:\n" " " msgstr "" "\n" -" Inoltre, la mappa contiene questi livelli che non possono essere " -"scaricati\n" +" Inoltre, la mappa contiene questi livelli che non possono essere scaricati\n" " per vincoli di sicurezza:\n" " " @@ -3589,8 +3347,7 @@ msgid "" " " msgstr "" "\n" -" Infine, la mappa contiene questi livelli che non possono essere " -"scaricati\n" +" Infine, la mappa contiene questi livelli che non possono essere scaricati\n" " perchè non sono disponibili direttamente da questo GeoNode:\n" " " @@ -3616,16 +3373,14 @@ msgid "Explore Maps" msgstr "Esplora mappe" msgid "" -"Note: this map's orginal metadata was populated by importing a metadata XML " -"file.\n" -" GeoNode's metadata import supports a subset of ISO, FGDC, and Dublin " -"Core metadata elements.\n" +"Note: this map's orginal metadata was populated by importing a metadata XML file.\n" +" GeoNode's metadata import supports a subset of ISO, FGDC, and Dublin Core metadata " +"elements.\n" " Some of your original metadata may have been lost." msgstr "" -"Nota: i metadati originale di questa mappa era popolato importando un file " -"XML di metadati. Metadati importazione di GeoNode supporta un sottoinsieme " -"di elementi di metadati ISO, FGDC, e Dublin Core. Alcuni dei i metadati " -"originale Potrebbero essere state compromesse." +"Nota: i metadati originale di questa mappa era popolato importando un file XML di metadati. " +"Metadati importazione di GeoNode supporta un sottoinsieme di elementi di metadati ISO, FGDC, " +"e Dublin Core. Alcuni dei i metadati originale Potrebbero essere state compromesse." msgid "Return to Map" msgstr "Ritorna alla mappa" @@ -3636,13 +3391,13 @@ msgstr "Rimuovi mappa" #, python-format msgid "" "\n" -" Are you sure you want to remove %(map_title)s?\n" +" Are you sure you want to remove %(map_title)s?\n" " " msgstr "" "\n" -" Sei sicuro di voler rimuovere " -"%(map_title)s?\n" +" Sei sicuro di voler rimuovere %(map_title)s?\n" " " msgid "You are not permitted to delete this map." @@ -3688,18 +3443,18 @@ msgid "Show list of services" msgstr "Elenco dei servizi" msgid "" -"Process data since specific timestamp (YYYY-MM-DD HH:MM:SS format). If not " -"provided, last sync will be used." +"Process data since specific timestamp (YYYY-MM-DD HH:MM:SS format). If not provided, last " +"sync will be used." msgstr "" -"Elaborare i dati dal timestamp specifico (formato AAAA-MM-GG HH:MM:SS). Se " -"non viene specificato, verrà utilizzata l'ultima sincronizzazione." +"Elaborare i dati dal timestamp specifico (formato AAAA-MM-GG HH:MM:SS). Se non viene " +"specificato, verrà utilizzata l'ultima sincronizzazione." msgid "" -"Process data until specific timestamp (YYYY-MM-DD HH:MM:SS format). If not " -"provided, now will be used." +"Process data until specific timestamp (YYYY-MM-DD HH:MM:SS format). If not provided, now " +"will be used." msgstr "" -"Elaborare i dati fino al timestamp specifico (formato AAAA-MM-GG HH:MM:SS). " -"Se non specificato, verrà utilizzata l'ora attuale." +"Elaborare i dati fino al timestamp specifico (formato AAAA-MM-GG HH:MM:SS). Se non " +"specificato, verrà utilizzata l'ora attuale." msgid "Force check" msgstr "Controllo forzato" @@ -3708,11 +3463,11 @@ msgid "Format of audit log (xml, json)" msgstr "Formato del log di audit (xml, json)" msgid "" -"Should old data be preserved (default: no, data older than settings." -"MONITORING_DATA_TTL will be removed)" +"Should old data be preserved (default: no, data older than settings.MONITORING_DATA_TTL will " +"be removed)" msgstr "" -"Conservare i dati precedenti (impostazione predefinita: no, i dati più " -"vecchi di settings.MONITORING_DATA_TTL verranno rimossi)" +"Conservare i dati precedenti (impostazione predefinita: no, i dati più vecchi di settings." +"MONITORING_DATA_TTL verranno rimossi)" msgid "Should stop on first error occured (default: no)" msgstr "Interruzione al primo errore (impostazione predefinita: no)" @@ -3739,8 +3494,7 @@ msgid "Metric name" msgstr "Nome metrica" msgid "Show data for specific resource in resource_type=resource_name format" -msgstr "" -"Mostra i dati per una risorsa specifica nel formato tipo_risorsa-nome_risorsa" +msgstr "Mostra i dati per una risorsa specifica nel formato tipo_risorsa-nome_risorsa" msgid "Show data for specific resource" msgstr "Mostra dati per risorsa specifica" @@ -3752,12 +3506,10 @@ msgstr "Mostra dati per un'etichetta specifica" msgid "Write result to file, default GEOIP_PATH: {settings.GEOIP_PATH}" msgstr "Scritto risultato su file, default GEOIP_PATH: {settings.GEOIP_PATH}" -msgid "" -"Fetch database from specific url. If nothing provided, default {} will be " -"used" +msgid "Fetch database from specific url. If nothing provided, default {} will be used" msgstr "" -"Recuperare il database da un URL specifico. Se non viene fornito nulla, " -"verrà utilizzato il valore predefinito {}" +"Recuperare il database da un URL specifico. Se non viene fornito nulla, verrà utilizzato il " +"valore predefinito {}" msgid "Overwrite file if exists" msgstr "Sovrascrivi file se esiste già" @@ -3908,8 +3660,7 @@ msgid "Last update must not be older than" msgstr "L'ultimo aggiornamento non deve essere più vecchio di" msgid "Max timeout for given metric before error should be raised" -msgstr "" -"Timeout massimo per la metrica specificata prima che venga generato l'errore" +msgstr "Timeout massimo per la metrica specificata prima che venga generato l'errore" msgid "Monitoring & Analytics" msgstr "Monitoraggio & Analytics" @@ -3972,29 +3723,22 @@ msgstr "Cambia password: %s" msgid "party who authored the resource" msgstr "gruppo che ha creato la risorsa" -msgid "" -"party who has processed the data in a manner such that the resource has been " -"modified" -msgstr "" -"gruppo che ha elaborato i dati in modo tale che la risorsa sia stata " -"modificata" +msgid "party who has processed the data in a manner such that the resource has been modified" +msgstr "gruppo che ha elaborato i dati in modo tale che la risorsa sia stata modificata" msgid "party who published the resource" msgstr "gruppo che ha pubblicato la risorsa" msgid "" -"party that accepts accountability and responsibility for the data and " -"ensures appropriate care and maintenance of the resource" +"party that accepts accountability and responsibility for the data and ensures " +"appropriate care and maintenance of the resource" msgstr "" -"partito che accetta la responsabilità e la responsabilità per i dati e " -"garantisce la cura e la manutenzione appropriata della risorsa" +"partito che accetta la responsabilità e la responsabilità per i dati e garantisce la cura e " +"la manutenzione appropriata della risorsa" -msgid "" -"party who can be contacted for acquiring knowledge about or acquisition of " -"the resource" +msgid "party who can be contacted for acquiring knowledge about or acquisition of the resource" msgstr "" -"parte che può essere contattata per l'acquisizione di conoscenza o " -"acquisizione della risorsa" +"parte che può essere contattata per l'acquisizione di conoscenza o acquisizione della risorsa" msgid "party who distributes the resource" msgstr "parte che distribuisce la risorsa" @@ -4013,8 +3757,7 @@ msgstr "parte che possiede la risorsa" msgid "key party responsible for gathering information and conducting research" msgstr "" -"parte chiave responsabile della raccolta di informazioni e della conduzione " -"della ricerca" +"parte chiave responsabile della raccolta di informazioni e della conduzione della ricerca" msgid "Email Address" msgstr "Indirizzo email" @@ -4041,30 +3784,23 @@ msgid "Voice" msgstr "Voce" msgid "" -"telephone number by which individuals can speak to the responsible " -"organization or individual" +"telephone number by which individuals can speak to the responsible organization or individual" msgstr "" -"numero di telefono con cui le persone possono parlare con l'organizzazione o " -"l'individuo responsabile" +"numero di telefono con cui le persone possono parlare con l'organizzazione o l'individuo " +"responsabile" msgid "Facsimile" msgstr "Fax" -msgid "" -"telephone number of a facsimile machine for the responsible organization or " -"individual" -msgstr "" -"numero di telefono o fax per l'organizzazione o l'individuo responsabile" +msgid "telephone number of a facsimile machine for the responsible organization or individual" +msgstr "numero di telefono o fax per l'organizzazione o l'individuo responsabile" msgid "Delivery Point" msgstr "Informazioni di contatto" -msgid "" -"physical and email address at which the organization or individual may be " -"contacted" +msgid "physical and email address at which the organization or individual may be contacted" msgstr "" -"indirizzo fisico e email ai quali l'organizzazione o l'individuo possono " -"essere contattati" +"indirizzo fisico e email ai quali l'organizzazione o l'individuo possono essere contattati" msgid "City" msgstr "Città" @@ -4091,11 +3827,11 @@ msgid "country of the physical address" msgstr "paese dell'indirizzo fisico" msgid "" -"commonly used word(s) or formalised word(s) or phrase(s) used to describe " -"the subject (space or comma-separated" +"commonly used word(s) or formalised word(s) or phrase(s) used to describe the " +"subject (space or comma-separated" msgstr "" -"parola di uso comune (s) o una parola formalizzata (s) o la frase (s) " -"utilizzati per descrivere il soggetto (spazio o separato da virgole" +"parola di uso comune (s) o una parola formalizzata (s) o la frase (s) utilizzati per " +"descrivere il soggetto (spazio o separato da virgole" msgid "Timezone" msgstr "Fuso orario" @@ -4113,11 +3849,11 @@ msgid "Forgot Username" msgstr "Username dimenticato" msgid "" -"Enter your email address and click the submit button.
Your username " -"will be sent to you." +"Enter your email address and click the submit button.
Your username will be sent to " +"you." msgstr "" -"Inserisci il tuo indirizzo email e clicca il bottone submit.
Il tuo " -"username sarà spedito all'indirizzo specificato." +"Inserisci il tuo indirizzo email e clicca il bottone submit.
Il tuo username sarà " +"spedito all'indirizzo specificato." msgid "Create Profile" msgstr "Crea profilo" @@ -4129,7 +3865,7 @@ msgid "Not provided." msgstr "Non fornito." msgid "Position" -msgstr "Posizione" +msgstr "Incarico" msgid "Organization" msgstr "Organizzazione" @@ -4219,12 +3955,8 @@ msgstr "Non è consentito salvare o modificare questa risorsa." msgid "You are not authorized to download this resource." msgstr "Non è consentito scaricare questa risorsa." -msgid "" -"No files have been found for this resource. Please, contact a system " -"administrator." -msgstr "" -"Nessun file trovato per questa risorsa. Contattare un amministratore di " -"sistema." +msgid "No files have been found for this resource. Please, contact a system administrator." +msgstr "Nessun file trovato per questa risorsa. Contattare un amministratore di sistema." msgid "No files found." msgstr "Nessun file trovato." @@ -4232,10 +3964,8 @@ msgstr "Nessun file trovato." msgid "Not Authorized" msgstr "Non autorizzato" -#, fuzzy -#| msgid "GeoNode Themes Library" msgid "GeoNode Resource Processing Library" -msgstr "Libreria dei temi GeoNode" +msgstr "Libreria dei processamenti GeoNode" msgid "Disabling this Task will make the Processing Workflow to skip it." msgstr "" @@ -4253,11 +3983,11 @@ msgid "Error updating permissions :(" msgstr "Errore durante l'aggiornamento delle autorizzazioni :(" msgid "" -"User {username} has download permissions but cannot access the resource. " -"Please update permission consistently!" +"User {username} has download permissions but cannot access the resource. Please update " +"permission consistently!" msgstr "" -"L'utente {username} ha le autorizzazioni di scaricamento ma non può " -"visualizzare la risorsa. Controllare che le autorizzazioni siano consistenti." +"L'utente {username} ha le autorizzazioni di scaricamento ma non può visualizzare la risorsa. " +"Controllare che le autorizzazioni siano consistenti." msgid "You are not allowed to change permissions for this resource" msgstr "Non ti è consentito modificare le autorizzazioni per questa risorsa" @@ -4480,10 +4210,8 @@ msgstr "La Risorsa {resource_id} è in fase di elaborazione" msgid "Service rescanned successfully" msgstr "Il servizio è stato analizzato di nuovo correttamente" -#, fuzzy -#| msgid "You are not permitted to save or edit this resource." msgid "You dont have enougth rigths to see the resource detail" -msgstr "Non è consentito salvare o modificare questa risorsa." +msgstr "Non hai i privilegi necessari per vedere i dettagli di questa risorsa" msgid "You are not permitted to change this service." msgstr "Non è consentito modificare questo servizio." @@ -4545,13 +4273,12 @@ msgstr "Pagina non trovata" msgid "" "\n" -" The page you requested does not exist. Perhaps you are using an " -"outdated bookmark?\n" +" The page you requested does not exist. Perhaps you are using an outdated " +"bookmark?\n" " " msgstr "" "\n" -" La pagina richiesta non esiste. Forse si utilizza un segnalibro " -"obsoleto?\n" +" La pagina richiesta non esiste. Forse si utilizza un segnalibro obsoleto?\n" " " msgid "Toggle navigation" @@ -4578,31 +4305,28 @@ msgid "You are using an outdated browser that is not supported by GeoNode." msgstr "Stai usando un browser obsoleto che non è supportato da GeoNode." msgid "" -"Please use a modern browser like Mozilla Firefox, Google " -"Chrome or Safari." +"Please use a modern browser like Mozilla Firefox, Google Chrome or Safari." msgstr "" -"Utilizza un browser moderno come Mozilla Firefox, Google " -"Chrome o Safari." +"Utilizza un browser moderno come Mozilla Firefox, Google Chrome o Safari." msgid "There was a problem loading this page" msgstr "C'è stato un problema nel caricare la pagina" msgid "" "\n" -" Please contact your GeoNode administrator (they may have " -"received an email automatically if they configured it properly).\n" -" If you are the site administrator, enable debug mode to see the " -"actual error and fix it or file an issue in
GeoNode's issue tracker\n" +" Please contact your GeoNode administrator (they may have received an email " +"automatically if they configured it properly).\n" +" If you are the site administrator, enable debug mode to see the actual error and " +"fix it or file an issue in GeoNode's " +"issue tracker\n" " " msgstr "" "\n" -" Si prega di contattare l'amministratore di GeoNode (può aver " -"ricevuto un'e-mail automaticamente se ha configurato correttamente).\n" -" Se sei l'amministratore del sito, attivare la modalità di debug " -"visualizzare l'errore effettivo e risolvere il problema o un problema del " -"file nel tracciatore di " -"problemi di GeoNode\n" +" Si prega di contattare l'amministratore di GeoNode (può aver ricevuto un'e-mail " +"automaticamente se ha configurato correttamente).\n" +" Se sei l'amministratore del sito, attivare la modalità di debug visualizzare " +"l'errore effettivo e risolvere il problema o un problema del file nel tracciatore di problemi di GeoNode\n" " " #, fuzzy @@ -4703,21 +4427,16 @@ msgstr "Chi può scaricarlo?" msgid "Who can change metadata for it?" msgstr "Chi può modificare i metadati per questo?" -#, fuzzy -#| msgid "Who can edit data for this layer?" msgid "Who can edit data for this dataset?" -msgstr "Chi può modificare i dati di questo livello?" +msgstr "Chi può modificare i dati di questo dataset?" -#, fuzzy -#| msgid "Who can edit styles for this layer?" msgid "Who can edit styles for this dataset?" -msgstr "Chi può modificare gli stili di questo livello?" +msgstr "Chi può modificare gli stili di questo dataset?" -msgid "" -"Who can manage it? (update, delete, change permissions, publish/unpublish it)" +msgid "Who can manage it? (update, delete, change permissions, publish/unpublish it)" msgstr "" -"Chi può gestirlo? (Aggiornare, cancellare, modificare le autorizzazioni, " -"pubblicare / annullare la pubblicazione di esso)" +"Chi può gestirlo? (Aggiornare, eliminare, modificare le autorizzazioni, pubblicare / " +"annullare la pubblicazione di esso)" msgid "Set permissions for this resource" msgstr "Impostare le autorizzazioni per questa risorsa" @@ -4753,12 +4472,11 @@ msgid "Mandatory files : SHP , DBF" msgstr "File obbligatori: .shp, .dbf" msgid "" -"Upload a ZIP file containing an ESRI Shapefile. If the ZIP provides also a ." -"prj file, you don't have to specify the EPSG SRID" +"Upload a ZIP file containing an ESRI Shapefile. If the ZIP provides also a .prj file, you " +"don't have to specify the EPSG SRID" msgstr "" -"Carica uo file ZIP contenente uno Shapegile ESRI. Se lo ZIP fornisceanche un " -"file .prj non è necessario specificare il Sistema di Riferimento (Codice " -"EPSG)" +"Carica uo file ZIP contenente uno Shapegile ESRI. Se lo ZIP fornisceanche un file .prj non è " +"necessario specificare il Sistema di Riferimento (Codice EPSG)" msgid "Choose" msgstr "Seleziona" @@ -4782,32 +4500,31 @@ msgid "Load SHP-ZIP" msgstr "Carica uno Shapefile .zip" msgid "" -"

This will remove the Geo Limits currently drawn on the map.

In " -"order to store the Geo Limits you will need to save them " -"anyway.

Do you want to proceed?

" +"

This will remove the Geo Limits currently drawn on the map.

In order to store the " +"Geo Limits you will need to save them anyway.

Do you want to proceed?" +"

" msgstr "" -"

Questa azione rimuoverà i limiti spaziali attualmente disegnati " -"sullamappa.

Vuoi procedere?

" +"

Questa azione rimuoverà i limiti spaziali attualmente disegnati sullamappa.

Vuoi " +"procedere?

" msgid "" -"

This will override the current stored Geo Limits on the DB.

To " -"apply them you will need to click on Apply Changes button " -"anyway.

WARNING: This operation cannot be reverted!

Do you want to proceed?

" +"

This will override the current stored Geo Limits on the DB.

To apply them you will " +"need to click on Apply Changes button anyway.

WARNING: This operation cannot be reverted!

Do you want to proceed?

" msgstr "" -"

Questa azione sovrascriverà i limiti spaziali attualmente salvantinel DB." -"

Per procedere clicca su Esegui.

ATTENZIONE: Questa azione è irreversibile!

" +"

Questa azione sovrascriverà i limiti spaziali attualmente salvantinel DB.

Per " +"procedere clicca su Esegui.

ATTENZIONE: Questa " +"azione è irreversibile!

" msgid "Save Geo Limit" msgstr "Salve limiti spaziali" msgid "" -"

Geometry successfully saved!

To apply them you " -"will need to click on Apply Changes button.

" +"

Geometry successfully saved!

To apply them you will need to click " +"on Apply Changes button.

" msgstr "" -"

Geometria salvata con successo!

Per applicarli è " -"necessario fare clic sul pulsante Applica modifiche.

" +"

Geometria salvata con successo!

Per applicarli è necessario fare " +"clic sul pulsante Applica modifiche.

" msgid "

Error while trying to save the Geometry!

" msgstr "

Errore nel salvataggio della geometria!

" @@ -4822,13 +4539,13 @@ msgid "

No Geometry found!

" msgstr "

Nessuna geometria trovata!

" msgid "" -"

This will permanently remove the Geo Limits from the DB." -"

To apply them you will need to click on Apply Changes button.

Do you want to proceed?

" +"

This will permanently remove the Geo Limits from the DB.

To apply " +"them you will need to click on Apply Changes button.

Do you want to " +"proceed?

" msgstr "" -"

In questo modo i limiti geografici verranno rimossi " -"definitivamente dal database.

Per applicarli è necessario fare clic " -"sul pulsante Applica modifiche.

Vuoi procedere?

" +"

In questo modo i limiti geografici verranno rimossi definitivamente dal " +"database.

Per applicarli è necessario fare clic sul pulsante Applica modifiche." +"

Vuoi procedere?

" msgid "Delete Geo Limit" msgstr "Elimina limite geografico" @@ -4836,11 +4553,9 @@ msgstr "Elimina limite geografico" msgid "

Geo Limitsy successfully deleted!

" msgstr "

Geo Limite eliminato con successo!

" -msgid "" -"

Error occurred while trying to delete Geo Limits:

" +msgid "

Error occurred while trying to delete Geo Limits:

" msgstr "" -"

Errore durante il tentativo di eliminazione dei limiti " -"geografici:

" +"

Errore durante il tentativo di eliminazione dei limiti geografici:

" msgid "Choose users..." msgstr "Scegli gli utenti ..." @@ -4858,43 +4573,37 @@ msgid "About GeoNode" msgstr "A proposito di GeoNode" msgid "" -"GeoNode is a geospatial content management system, a platform for the " -"management and publication of geospatial data. It brings together mature and " -"stable open-source software projects under a consistent and easy-to-use " -"interface allowing non-specialized users to share data and create " -"interactive maps." +"GeoNode is a geospatial content management system, a platform for the management and " +"publication of geospatial data. It brings together mature and stable open-source software " +"projects under a consistent and easy-to-use interface allowing non-specialized users to " +"share data and create interactive maps." msgstr "" -"GeoNode è un sistema di gestione dei contenuti geospaziali, una piattaforma " -"per la gestione e la pubblicazione di dati geospaziali. Riunisce progetti " -"software open source maturi e stabili sotto un'interfaccia coerente e facile " -"da usare che consente agli utenti non specializzati di condividere dati e " -"creare mappe interattive." +"GeoNode è un sistema di gestione dei contenuti geospaziali, una piattaforma per la gestione " +"e la pubblicazione di dati geospaziali. Riunisce progetti software open source maturi e " +"stabili sotto un'interfaccia coerente e facile da usare che consente agli utenti non " +"specializzati di condividere dati e creare mappe interattive." msgid "" -"Data management tools built into GeoNode allow for integrated creation of " -"data, metadata, and map visualizations. Each dataset in the system can be " -"shared publicly or restricted to allow access to only specific users. Social " -"features like user profiles and commenting and rating systems allow for the " -"development of communities around each platform to facilitate the use, " -"management, and quality control of the data the GeoNode instance contains." +"Data management tools built into GeoNode allow for integrated creation of data, metadata, " +"and map visualizations. Each dataset in the system can be shared publicly or restricted to " +"allow access to only specific users. Social features like user profiles and commenting and " +"rating systems allow for the development of communities around each platform to facilitate " +"the use, management, and quality control of the data the GeoNode instance contains." msgstr "" -"Gli strumenti di gestione dei dati integrati in GeoNode consentono la " -"creazione integrata di dati, metadati e visualizzazioni mappa. Ogni set di " -"dati nel sistema può essere condiviso pubblicamente o limitato per " -"consentire l'accesso solo a utenti specifici. Le funzionalità di social " -"networking come i profili utente e i sistemi di commenti e classificazione " -"consentono lo sviluppo di comunità intorno a ogni piattaforma per facilitare " -"l'utilizzo, la gestione e il controllo qualità dei dati contenuti " -"nell'istanza GeoNode." +"Gli strumenti di gestione dei dati integrati in GeoNode consentono la creazione integrata di " +"dati, metadati e visualizzazioni mappa. Ogni set di dati nel sistema può essere condiviso " +"pubblicamente o limitato per consentire l'accesso solo a utenti specifici. Le funzionalità " +"di social networking come i profili utente e i sistemi di commenti e classificazione " +"consentono lo sviluppo di comunità intorno a ogni piattaforma per facilitare l'utilizzo, la " +"gestione e il controllo qualità dei dati contenuti nell'istanza GeoNode." msgid "" -"It is also designed to be a flexible platform that software developers can " -"extend, modify or integrate against to meet requirements in their own " -"applications." +"It is also designed to be a flexible platform that software developers can extend, modify or " +"integrate against to meet requirements in their own applications." msgstr "" -"È inoltre progettato per essere una piattaforma flessibile che gli " -"sviluppatori di software possono estendere, modificare o integrare per " -"soddisfare i requisiti nelle proprie applicazioni." +"È inoltre progettato per essere una piattaforma flessibile che gli sviluppatori di software " +"possono estendere, modificare o integrare per soddisfare i requisiti nelle proprie " +"applicazioni." msgid "Account Inactive" msgstr "Account non attivo" @@ -4907,13 +4616,11 @@ msgstr "Account in attesa di approvazione" #, python-format msgid "" -"We have sent the administrators a notice to approve your account associated " -"with %(email)s. If the account is approved, you will receive a " -"confirmation notice." +"We have sent the administrators a notice to approve your account associated with " +"%(email)s. If the account is approved, you will receive a confirmation notice." msgstr "" -"Abbiamo inviato agli amministratori un avviso per approvare il tuo account " -"associato a %(email)s. Se l'account viene approvato, riceverai un " -"avviso di conferma." +"Abbiamo inviato agli amministratori un avviso per approvare il tuo account associato a " +"%(email)s. Se l'account viene approvato, riceverai un avviso di conferma." msgid "Account" msgstr "Account" @@ -4943,12 +4650,12 @@ msgid "Warning:" msgstr "Avviso:" msgid "" -"You currently do not have any e-mail address set up. You should really add " -"an e-mail address so you can receive notifications, reset your password, etc." +"You currently do not have any e-mail address set up. You should really add an e-mail address " +"so you can receive notifications, reset your password, etc." msgstr "" -"Al momento non è stato impostato alcun indirizzo di posta elettronica. Si " -"dovrebbe davvero aggiungere un indirizzo e-mail in modo da poter ricevere " -"notifiche, reimpostare la password, ecc." +"Al momento non è stato impostato alcun indirizzo di posta elettronica. Si dovrebbe davvero " +"aggiungere un indirizzo e-mail in modo da poter ricevere notifiche, reimpostare la password, " +"ecc." msgid "Add E-mail Address" msgstr "Aggiungi indirizzo e-mail" @@ -4963,15 +4670,15 @@ msgstr "Vuoi davvero rimuovere l'indirizzo e-mail selezionato?" msgid "" "Hello from %(site_name)s!\n" "\n" -"You're receiving this e-mail because user %(user_display)s has given yours " -"as an e-mail address to connect their account.\n" +"You're receiving this e-mail because user %(user_display)s has given yours as an e-mail " +"address to connect their account.\n" "\n" "To confirm this is correct, go to %(activate_url)s\n" msgstr "" "Ciao da %(site_name)s!\n" "\n" -"Ricevi questa e-mail perché utente %(user_display)s ha dato la tua come un " -"indirizzo di posta elettronica per collegare il proprio account.\n" +"Ricevi questa e-mail perché utente %(user_display)s ha dato la tua come un indirizzo di " +"posta elettronica per collegare il proprio account.\n" "\n" "Per confermare che questo è corretto, andare al s %(activate_url)s\n" @@ -4995,8 +4702,7 @@ msgstr "Sei stato invitato a registrarti all'indirizzo %(site_name)s." msgid "Create an account on %(site_name)s" msgstr "Creare un account su %(site_name)s" -msgid "" -"This is the email notification to confirm your password has been changed on" +msgid "This is the email notification to confirm your password has been changed on" msgstr "Questa è la notifica e-mail per confermare che la password è stata" msgid "Change password email notification" @@ -5006,17 +4712,17 @@ msgstr "Modifica password" msgid "" "Hello from %(site_name)s!\n" "\n" -"You're receiving this e-mail because you or someone else has requested a " -"password for your user account.\n" -"It can be safely ignored if you did not request a password reset. Click the " -"link below to reset your password." +"You're receiving this e-mail because you or someone else has requested a password for your " +"user account.\n" +"It can be safely ignored if you did not request a password reset. Click the link below to " +"reset your password." msgstr "" "Ciao da %(site_name)s!\n" "\n" -"Ricevi questa e-mail perché tu o qualcun altro ha richiesto una password per " -"l'account utente.\n" -"Può essere tranquillamente ignorato se non hai richiesto la reimpostazione " -"della password. Clicca sul link qui sotto per reimpostare la password." +"Ricevi questa e-mail perché tu o qualcun altro ha richiesto una password per l'account " +"utente.\n" +"Può essere tranquillamente ignorato se non hai richiesto la reimpostazione della password. " +"Clicca sul link qui sotto per reimpostare la password." #, python-format msgid "In case you forgot, your username is %(username)s." @@ -5039,23 +4745,22 @@ msgstr "Confermare Indirizzo e-Mail" #, python-format msgid "" -"Please confirm that %(email)s is an e-mail " -"address for user %(user_display)s." +"Please confirm that %(email)s is an e-mail address for user " +"%(user_display)s." msgstr "" -"Verificare che %(email)s sia un indirizzo " -"di posta elettronica per l'utente %(user_display)s." +"Verificare che %(email)s sia un indirizzo di posta " +"elettronica per l'utente %(user_display)s." msgid "Confirm" msgstr "Conferma" #, python-format msgid "" -"This e-mail confirmation link expired or is invalid. Please issue a new e-mail confirmation request." +"This e-mail confirmation link expired or is invalid. Please issue " +"a new e-mail confirmation request." msgstr "" -"Questo link di conferma e-mail è scaduto o non è valido. Si prega di " -"emettere una nuova richiesta di conferma e-mail." +"Questo link di conferma e-mail è scaduto o non è valido. Si prega di emettere una nuova " +"richiesta di conferma e-mail." msgid "Log in" msgstr "Entra" @@ -5066,13 +4771,12 @@ msgstr "Effettua il login con un account esistente" #, python-format msgid "" "Please sign in with one\n" -" of your existing third party accounts. Or, sign up\n" +" of your existing third party accounts. Or, sign up\n" " for a %(site_name)s account and sign in below:" msgstr "" "Si prega di accedere con uno\n" -" degli account di terze parti esistenti. In alternativa, iscriviti\n" +" degli account di terze parti esistenti. In alternativa, iscriviti\n" " per un account %(site_name)s e accedere di seguito:" msgid "or" @@ -5116,32 +4820,27 @@ msgstr "Reimposta password" msgid "Forgotten your password?" msgstr "Hai dimenticato la password?" -msgid "" -"Enter your email address below, and we'll send you an email allowing you to " -"reset it." +msgid "Enter your email address below, and we'll send you an email allowing you to reset it." msgstr "" -"Inserisci sotto il tuo indirizzo email e ti spediremo un'email per " -"permetterti di cambiare la password." +"Inserisci sotto il tuo indirizzo email e ti spediremo un'email per permetterti di cambiare " +"la password." msgid "Reset my password" msgstr "Reimposta la mia password" #, python-format msgid "" -"If you have any trouble resetting your password, contact us at %(THEME_ACCOUNT_CONTACT_EMAIL)s." +"If you have any trouble resetting your password, contact us at %(THEME_ACCOUNT_CONTACT_EMAIL)s." msgstr "" -"Se hai dei problemi nel reimpostare la password, contattaci a %(THEME_ACCOUNT_CONTACT_EMAIL)s." +"Se hai dei problemi nel reimpostare la password, contattaci a %(THEME_ACCOUNT_CONTACT_EMAIL)s." msgid "" -"We have sent you an e-mail. Please contact us if you do not receive it " -"within a few minutes." +"We have sent you an e-mail. Please contact us if you do not receive it within a few minutes." msgstr "" -"Vi abbiamo inviato un'e-mail. Vi preghiamo di contattarci se non lo ricevete " -"entro pochi minuti." +"Vi abbiamo inviato un'e-mail. Vi preghiamo di contattarci se non lo ricevete entro pochi " +"minuti." msgid "Change Password" msgstr "Cambia Password" @@ -5151,13 +4850,12 @@ msgstr "Token non valido" #, python-format msgid "" -"The password reset link was invalid, possibly because it has already been " -"used. Please request a new password reset." +"The password reset link was invalid, possibly because it has already been used. Please " +"request a new password reset." msgstr "" -"Il collegamento per la reimpostazione della password non è valido, " -"probabilmente perché è già stato utilizzato. Si prega di richiedere una nuova reimpostazione della password." +"Il collegamento per la reimpostazione della password non è valido, probabilmente perché è " +"già stato utilizzato. Si prega di richiedere una nuova " +"reimpostazione della password." msgid "change password" msgstr "modifica password" @@ -5208,13 +4906,11 @@ msgstr "Verificare l'indirizzo di posta elettronica" msgid "" "We have sent an email to you for verification. Follow the\n" -" link provided to finalize the signup process. Please contact us if you " -"do\n" +" link provided to finalize the signup process. Please contact us if you do\n" " not receive it within a few minutes" msgstr "" "We have sent an email to you for verification. Follow the\n" -" link provided to finalize the signup process. Please contact us if you " -"do\n" +" link provided to finalize the signup process. Please contact us if you do\n" " not receive it within a few minutes" msgid "Verify Your E-mail Address" @@ -5235,17 +4931,16 @@ msgid "" "contact us if you do not receive it within a few minutes." msgstr "" "Vi abbiamo inviato un'e-mail per\n" -"verifica. Si prega di fare clic sul link all'interno di questa e-mail. per " -"favore\n" +"verifica. Si prega di fare clic sul link all'interno di questa e-mail. per favore\n" "contattarci se non lo ricevi entro pochi minuti." #, python-format msgid "" -"Note: you can still change your e-" -"mail address." +"Note: you can still change your e-mail address." msgstr "" -"Nota: è comunque possibile modificare l'indirizzo e-mail." +"Nota: è comunque possibile modificare l'indirizzo " +"e-mail." msgid "My Activity feed" msgstr "Il mio feed attività" @@ -5292,13 +4987,11 @@ msgstr "Pubblicato da" #, python-format msgid "" "\n" -" Published from %(publish_start)s to %(publish_end)s.\n" +" Published from %(publish_start)s to %(publish_end)s.\n" " " msgstr "" "\n" -" Pubblicato da %(publish_start)s per %(publish_end)s.\n" +" Pubblicato da %(publish_start)s per %(publish_end)s.\n" " " #, python-format @@ -5328,14 +5021,13 @@ msgstr "Si prega di selezionare gli avatar che si desidera eliminare." #, python-format msgid "" -"You have no avatars to delete. Please upload one now." +"You have no avatars to delete. Please upload one now." msgstr "" -"Non ci sono gli avatar da eliminare. Si prega di caricare un ora." +"Non ci sono gli avatar da eliminare. Si prega di caricare " +"un ora." msgid "Delete These" -msgstr "Cancella questi" +msgstr "Elimina questi" msgid "GeoNode Search" msgstr "Ricerca GeoNode" @@ -5361,10 +5053,8 @@ msgstr "Aggiornamento Miniatura..." msgid "Message. Do you want to proceed?" msgstr "Sei sicuro di voler procedere?" -#, fuzzy -#| msgid "Harvesting resources..." msgid "Processing Resource..." -msgstr "Inserimento risorse..." +msgstr "Processamento risorse..." msgid "Information for Developers" msgstr "Informazioni per gli sviluppatori" @@ -5375,160 +5065,141 @@ msgstr "Informazioni utili per gli sviluppatori interessati a GeoNode." #, fuzzy, python-format #| msgid "" #| "\n" -#| "

GeoNode is an open service " -#| "built on open source software. We encourage you to build new applications " -#| "using the components and resources it provides. This page is a starting " -#| "point for developers interesting in taking full advantage of GeoNode. It " -#| "also includes links to the project's source code so anyone can build and " -#| "customize their own GeoNode.

\n" +#| "

GeoNode is an open service built on open " +#| "source software. We encourage you to build new applications using the components and " +#| "resources it provides. This page is a starting point for developers interesting in taking " +#| "full advantage of GeoNode. It also includes links to the project's source code so anyone " +#| "can build and customize their own GeoNode.

\n" #| "\n" #| "

GeoNode Software

\n" #| "\n" -#| "

All the code that runs GeoNode is open source. The code is " -#| "available at http://github." -#| "com/GeoNode/geonode/. The issue tracker for the project is at http://github.com/GeoNode/" -#| "geonode/issues.

\n" +#| "

All the code that runs GeoNode is open source. The code is available at http://github.com/GeoNode/geonode/. The " +#| "issue tracker for the project is at http://github.com/GeoNode/geonode/issues.

\n" #| "\n" -#| "

GeoNode is built using several open source projects, each with its " -#| "own community. If you are interested in contributing new features to the " -#| "GeoNode, we encourage you to do so by contributing to one of the projects " -#| "on which it is built:

\n" +#| "

GeoNode is built using several open source projects, each with its own community. " +#| "If you are interested in contributing new features to the GeoNode, we encourage you to do " +#| "so by contributing to one of the projects on which it is built:

\n" #| "
    \n" -#| "
  • GeoServer - Standards " -#| "based server for geospatial information
  • \n" -#| "
  • GeoWebCache - Cache " -#| "engine for WMS Tiles
  • OpenLayers - Pure JavaScript library powering the maps of GeoExt\n" -#| "
  • pycsw - CSW, OpenSearch and " -#| "OAI-PMH metadata catalogue server
  • \n" +#| "
  • GeoServer - Standards based server for " +#| "geospatial information
  • \n" +#| "
  • GeoWebCache - Cache engine for WMS " +#| "Tiles
  • OpenLayers - Pure JavaScript library " +#| "powering the maps of GeoExt
  • \n" +#| "
  • pycsw - CSW, OpenSearch and OAI-PMH metadata " +#| "catalogue server
  • \n" #| "
\n" #| "\n" #| "

What are OGC Services?

\n" -#| "

The data in this application is served using open standards " -#| "endorsed by ISO and the Open " -#| "Geospatial Consortium; in particular, WMS (Web Map Service) is used " -#| "for accessing maps, WFS (Web Feature Service) is used for accessing " -#| "vector data, and WCS (Web Coverage Service) is used for accessing raster " -#| "data. WMC (Web Map Context Documents) is used for sharing maps. You can " -#| "use these services in your own applications using libraries such as " -#| "OpenLayers, GeoTools, and OGR (all of which are open-source software and " -#| "available at zero cost). Additionally, CSW (Catalog Service for the Web) " -#| "supports access to collections of descriptive information (metadata) " -#| "about data and services.

\n" +#| "

The data in this application is served using open standards endorsed by ISO and " +#| "the Open Geospatial Consortium; in particular, " +#| "WMS (Web Map Service) is used for accessing maps, WFS (Web Feature Service) is used for " +#| "accessing vector data, and WCS (Web Coverage Service) is used for accessing raster data. " +#| "WMC (Web Map Context Documents) is used for sharing maps. You can use these services in " +#| "your own applications using libraries such as OpenLayers, GeoTools, and OGR (all of which " +#| "are open-source software and available at zero cost). Additionally, CSW (Catalog Service " +#| "for the Web) supports access to collections of descriptive information (metadata) about " +#| "data and services.

\n" #| "\n" #| "

What is GeoWebCache?

\n" -#| "

GeoWebCache provides mapping tiles that are compatible with a " -#| "number of mapping engines, including Google Maps, Bing Maps and " -#| "OpenLayers. All the data hosted by GeoNode is also available through " -#| "GeoWebCache. GeoWebCache improves on WMS by caching data and providing " -#| "more responsive maps.

\n" +#| "

GeoWebCache provides mapping tiles that are compatible with a number of mapping " +#| "engines, including Google Maps, Bing Maps and OpenLayers. All the data hosted by GeoNode " +#| "is also available through GeoWebCache. GeoWebCache improves on WMS by caching data and " +#| "providing more responsive maps.

\n" #| "\n" #| "

CSW Example Code

\n" -#| "

To interact with GeoNode's CSW you can use any CSW client (QGIS " -#| "MetaSearch, GRASS, etc.). The following example illustrates a simple " -#| "invocation using the OWSLib Python package:

\n" +#| "

To interact with GeoNode's CSW you can use any CSW client (QGIS MetaSearch, GRASS, " +#| "etc.). The following example illustrates a simple invocation using the OWSLib Python " +#| "package:

\n" #| "

from owslib.csw import CatalogueServiceWeb

\n" #| "

from owslib.fes import PropertyIsLike

\n" -#| "

csw = CatalogueServiceWeb('%(CATALOGUE_BASE_URL)s')\n" -#| "

anytext = PropertyIsLike('csw:AnyText', 'birds')')\n" +#| "

csw = CatalogueServiceWeb('%(CATALOGUE_BASE_URL)s')

\n" +#| "

anytext = PropertyIsLike('csw:AnyText', 'birds')')

\n" #| "

csw.getrecords2(constraints=[anytext])

\n" #| "

print csw.results

\n" #| "

print csw.records

\n" #| "\n" #| "

OpenLayers Example Code

\n" #| "\n" -#| "

To include a GeoNode map layer in an OpenLayers map, first find " -#| "the name for that layer. This is found in the layer's name " -#| "field (not title) of the layer list. For this example, we " -#| "will use the Nicaraguan political boundaries background layer, whose name " -#| "is risk:nicaragua_admin. Then, create an instance of " -#| "OpenLayers.Layer.WMS:

\n" -#| "

var geonodeLayer = new OpenLayers.Layer.WMS(\"GeoNode Risk " -#| "Data\", \"http://demo.geonode.org/geoserver/wms\",{ layers: \"risk:" -#| "nicaragua_admin\" });

\n" +#| "

To include a GeoNode map layer in an OpenLayers map, first find the name for that " +#| "layer. This is found in the layer's name field (not title) of " +#| "the layer list. For this example, we will use the Nicaraguan political boundaries " +#| "background layer, whose name is risk:nicaragua_admin. Then, create an " +#| "instance of OpenLayers.Layer.WMS:

\n" +#| "

var geonodeLayer = new OpenLayers.Layer.WMS(\"GeoNode Risk Data\", \"http://" +#| "demo.geonode.org/geoserver/wms\",{ layers: \"risk:nicaragua_admin\" });

\n" #| "\n" #| "

Google Maps Example Code

\n" -#| "

To include a GeoNode map layer in a Google Map, include the layer " -#| "namein the URL template.

\n" -#| "

var tilelayer = new GTileLayer(null, null, null, " -#| "{tileUrlTemplate: 'http://demo.geonode.org/geoserver/gwc/service/gmaps?" -#| "layers=risk:nicaragua_admin&zoom={Z}&x={X}&y={Y}', isPng:" -#| "true, opacity:0.5 } );

\n" +#| "

To include a GeoNode map layer in a Google Map, include the layer namein the URL " +#| "template.

\n" +#| "

var tilelayer = new GTileLayer(null, null, null, {tileUrlTemplate: 'http://" +#| "demo.geonode.org/geoserver/gwc/service/gmaps?layers=risk:nicaragua_admin&zoom={Z}&" +#| "x={X}&y={Y}', isPng:true, opacity:0.5 } );

\n" #| "\n" #| "

Shapefile/GeoJSON/GML Output

\n" -#| "

To get data from the GeoNode web services use the WFS protocol. " -#| "For example, to get the full Nicaraguan admin boundaries use:

\n" -#| "

http://demo.geonode.org/geoserver/wfs?request=GetFeature&" -#| "typeName=risk:nicaragua_admin&outputformat=SHAPE-ZIP

\n" -#| "

Changing output format to json, GML2, " -#| "GML3, or csv will get data in those formats. " -#| "The WFS protocol also can handle more precise queries, specifying a " -#| "bounding box or various spatial and non-spatial filters based on the " -#| "attributes of the data.

\n" +#| "

To get data from the GeoNode web services use the WFS protocol. For example, to " +#| "get the full Nicaraguan admin boundaries use:

\n" +#| "

http://demo.geonode.org/geoserver/wfs?request=GetFeature&typeName=risk:" +#| "nicaragua_admin&outputformat=SHAPE-ZIP

\n" +#| "

Changing output format to json, GML2, GML3, " +#| "or csv will get data in those formats. The WFS protocol also can handle more " +#| "precise queries, specifying a bounding box or various spatial and non-spatial filters " +#| "based on the attributes of the data.

\n" #| "\n" #| "

GeoTools Example Code

\n" -#| "

Create a DataStore and extract a FeatureType from it, then run a " -#| "Query. It is all documented on the wiki at http://geotools.org/.

\n" +#| "

Create a DataStore and extract a FeatureType from it, then run a Query. It is all " +#| "documented on the wiki at http://geotools.org/.

\n" #| " " msgid "" "\n" -"

GeoNode is an open service built " -"on open source software. We encourage you to build new applications using " -"the components and resources it provides. This page is a starting point for " -"developers interesting in taking full advantage of GeoNode. It also includes " -"links to the project's source code so anyone can build and customize their " -"own GeoNode.

\n" +"

GeoNode is an open service built on open source " +"software. We encourage you to build new applications using the components and resources it " +"provides. This page is a starting point for developers interesting in taking full advantage " +"of GeoNode. It also includes links to the project's source code so anyone can build and " +"customize their own GeoNode.

\n" "\n" "

GeoNode Software

\n" "\n" -"

All the code that runs GeoNode is open source. The code is available " -"at http://github.com/GeoNode/" -"geonode/. The issue tracker for the project is at http://github.com/GeoNode/geonode/" -"issues.

\n" +"

All the code that runs GeoNode is open source. The code is available at http://github.com/GeoNode/geonode/. The issue " +"tracker for the project is at http://" +"github.com/GeoNode/geonode/issues.

\n" "\n" -"

GeoNode is built using several open source projects, each with its " -"own community. If you are interested in contributing new features to the " -"GeoNode, we encourage you to do so by contributing to one of the projects on " -"which it is built:

\n" +"

GeoNode is built using several open source projects, each with its own community. If " +"you are interested in contributing new features to the GeoNode, we encourage you to do so by " +"contributing to one of the projects on which it is built:

\n" "
    \n" -"
  • GeoServer - Standards based " -"server for geospatial information
  • \n" -"
  • GeoWebCache - Cache engine " -"for WMS Tiles
  • OpenLayers - " -"Pure JavaScript library powering the maps of GeoExt
  • \n" -"
  • pycsw - CSW, OpenSearch and OAI-" -"PMH metadata catalogue server
  • \n" +"
  • GeoServer - Standards based server for " +"geospatial information
  • \n" +"
  • GeoWebCache - Cache engine for WMS Tiles
  • OpenLayers - Pure JavaScript library powering " +"the maps of GeoExt
  • \n" +"
  • pycsw - CSW, OpenSearch and OAI-PMH metadata " +"catalogue server
  • \n" "
\n" "\n" "

What are OGC Services?

\n" -"

The data in this application is served using open standards endorsed " -"by ISO and the Open Geospatial " -"Consortium; in particular, WMS (Web Map Service) is used for accessing " -"maps, WFS (Web Feature Service) is used for accessing vector data, and WCS " -"(Web Coverage Service) is used for accessing raster data. WMC (Web Map " -"Context Documents) is used for sharing maps. You can use these services in " -"your own applications using libraries such as OpenLayers, GeoTools, and OGR " -"(all of which are open-source software and available at zero cost). " -"Additionally, CSW (Catalog Service for the Web) supports access to " -"collections of descriptive information (metadata) about data and services.\n" +"

The data in this application is served using open standards endorsed by ISO and the " +"Open Geospatial Consortium; in particular, WMS " +"(Web Map Service) is used for accessing maps, WFS (Web Feature Service) is used for " +"accessing vector data, and WCS (Web Coverage Service) is used for accessing raster data. " +"WMC (Web Map Context Documents) is used for sharing maps. You can use these services in your " +"own applications using libraries such as OpenLayers, GeoTools, and OGR (all of which are " +"open-source software and available at zero cost). Additionally, CSW (Catalog Service for the " +"Web) supports access to collections of descriptive information (metadata) about data and " +"services.

\n" "\n" "

What is GeoWebCache?

\n" -"

GeoWebCache provides mapping tiles that are compatible with a number " -"of mapping engines, including Google Maps, Bing Maps and OpenLayers. All the " -"data hosted by GeoNode is also available through GeoWebCache. GeoWebCache " -"improves on WMS by caching data and providing more responsive maps.

\n" +"

GeoWebCache provides mapping tiles that are compatible with a number of mapping " +"engines, including Google Maps, Bing Maps and OpenLayers. All the data hosted by GeoNode is " +"also available through GeoWebCache. GeoWebCache improves on WMS by caching data and " +"providing more responsive maps.

\n" "\n" "

CSW Example Code

\n" -"

To interact with GeoNode's CSW you can use any CSW client (QGIS " -"MetaSearch, GRASS, etc.). The following example illustrates a simple " -"invocation using the OWSLib Python package:

\n" +"

To interact with GeoNode's CSW you can use any CSW client (QGIS MetaSearch, GRASS, " +"etc.). The following example illustrates a simple invocation using the OWSLib Python " +"package:

\n" "

from owslib.csw import CatalogueServiceWeb

\n" "

from owslib.fes import PropertyIsLike

\n" "

csw = CatalogueServiceWeb('%(CATALOGUE_BASE_URL)s')

\n" @@ -5539,95 +5210,84 @@ msgid "" "\n" "

OpenLayers Example Code

\n" "\n" -"

To include a GeoNode map layer in an OpenLayers map, first find the " -"name for that layer. This is found in the layer's name field " -"(not title) of the layer list. For this example, we will use " -"the Nicaraguan political boundaries background layer, whose name is " -"risk:nicaragua_admin. Then, create an instance of OpenLayers." -"Layer.WMS:

\n" -"

var geonodeLayer = new OpenLayers.Layer.WMS(\"GeoNode Risk Data" -"\", \"http://demo.geonode.org/geoserver/wms\",{ layers: \"risk:" -"nicaragua_admin\" });

\n" +"

To include a GeoNode map layer in an OpenLayers map, first find the name for that " +"layer. This is found in the layer's name field (not title) of the " +"layer list. For this example, we will use the Nicaraguan political boundaries background " +"layer, whose name is risk:nicaragua_admin. Then, create an instance of " +"OpenLayers.Layer.WMS:

\n" +"

var geonodeLayer = new OpenLayers.Layer.WMS(\"GeoNode Risk Data\", \"http://" +"demo.geonode.org/geoserver/wms\",{ layers: \"risk:nicaragua_admin\" });

\n" "\n" "

Google Maps Example Code

\n" -"

To include a GeoNode map layer in a Google Map, include the dataset " -"name in the URL template.

\n" -"

var tilelayer = new GTileLayer(null, null, null, " -"{tileUrlTemplate: 'http://demo.geonode.org/geoserver/gwc/service/gmaps?" -"layers=risk:nicaragua_admin&zoom={Z}&x={X}&y={Y}', isPng:true, " -"opacity:0.5 } );

\n" +"

To include a GeoNode map layer in a Google Map, include the dataset name in the URL " +"template.

\n" +"

var tilelayer = new GTileLayer(null, null, null, {tileUrlTemplate: 'http://" +"demo.geonode.org/geoserver/gwc/service/gmaps?layers=risk:nicaragua_admin&zoom={Z}&" +"x={X}&y={Y}', isPng:true, opacity:0.5 } );

\n" "\n" "

Shapefile/GeoJSON/GML Output

\n" -"

To get data from the GeoNode web services use the WFS protocol. For " -"example, to get the full Nicaraguan admin boundaries use:

\n" -"

http://demo.geonode.org/geoserver/wfs?request=GetFeature&" -"typeName=risk:nicaragua_admin&outputformat=SHAPE-ZIP

\n" -"

Changing output format to json, GML2, " -"GML3, or csv will get data in those formats. The " -"WFS protocol also can handle more precise queries, specifying a bounding box " -"or various spatial and non-spatial filters based on the attributes of the " -"data.

\n" +"

To get data from the GeoNode web services use the WFS protocol. For example, to get " +"the full Nicaraguan admin boundaries use:

\n" +"

http://demo.geonode.org/geoserver/wfs?request=GetFeature&typeName=risk:" +"nicaragua_admin&outputformat=SHAPE-ZIP

\n" +"

Changing output format to json, GML2, GML3, or " +"csv will get data in those formats. The WFS protocol also can handle more " +"precise queries, specifying a bounding box or various spatial and non-spatial filters based " +"on the attributes of the data.

\n" "\n" "

GeoTools Example Code

\n" -"

Create a DataStore and extract a FeatureType from it, then run a " -"Query. It is all documented on the wiki at http://geotools.org/." -"

\n" +"

Create a DataStore and extract a FeatureType from it, then run a Query. It is all " +"documented on the wiki at http://geotools.org/.

\n" " " msgstr "" "\n" -"

GeoNode è un servizio aperto " -"costruito su software open source. Vi incoraggiamo a costruire nuove " -"applicazioni utilizzando i componenti e le risorse che fornisce. Questa " -"pagina è un punto di partenza per gli sviluppatori interessati a trarre il " -"massimo vantaggio da GeoNode. Include anche link al codice sorgente del " -"progetto in modo che chiunque possa costruire e personalizzare il proprio " -"GeoNode.

\n" +"

GeoNode è un servizio aperto costruito su " +"software open source. Vi incoraggiamo a costruire nuove applicazioni utilizzando i " +"componenti e le risorse che fornisce. Questa pagina è un punto di partenza per gli " +"sviluppatori interessati a trarre il massimo vantaggio da GeoNode. Include anche link al " +"codice sorgente del progetto in modo che chiunque possa costruire e personalizzare il " +"proprio GeoNode.

\n" "\n" "

GeoNode Software

\n" "\n" -"

Tutto il codice che esegue GeoNode è open source. Il codice è " -"disponibile all'indirizzo http://github.com/GeoNode/geonode/. Il issue tracker per il progetto " -"è disponibile all'indirizzo http://github.com/GeoNode/geonode/issues.

\n" -"

GeoNode è costruito utilizzando diversi progetti open source, ognuno " -"con la propria comunità. Se siete interessati a contribuire con nuove " -"funzionalità al GeoNode, vi invitiamo a farlo contribuendo ad uno dei " -"progetti su cui è costruito:

\n" +"

Tutto il codice che esegue GeoNode è open source. Il codice è disponibile " +"all'indirizzo http://github.com/GeoNode/" +"geonode/. Il issue tracker per il progetto è disponibile all'indirizzo http://github.com/GeoNode/geonode/issues.

\n" +"

GeoNode è costruito utilizzando diversi progetti open source, ognuno con la propria " +"comunità. Se siete interessati a contribuire con nuove funzionalità al GeoNode, vi invitiamo " +"a farlo contribuendo ad uno dei progetti su cui è costruito:

\n" "
    \n" -"
  • GeoServer - Server basato su " -"standard per informazioni geospaziali
  • \n" -"
  • GeoWebCache - Motore di " -"cache per WMS Tiles
  • OpenLayers " -"- Pura libreria JavaScript che alimenta le mappe di GeoExt
  • \n" -"
  • pycsw - CSW, OpenSearch e OAI-PMH " -"metadata catalogue server
  • \n" +"
  • GeoServer - Server basato su standard per " +"informazioni geospaziali
  • \n" +"
  • GeoWebCache - Motore di cache per WMS " +"Tiles
  • OpenLayers - Pura libreria JavaScript " +"che alimenta le mappe di GeoExt
  • \n" +"
  • pycsw - CSW, OpenSearch e OAI-PMH metadata " +"catalogue server
  • \n" "
\n" "\n" "

Che cosa sono i servizi OGC?

\n" -"

I dati in questa applicazione sono serviti utilizzando standard " -"aperti approvati dall'ISO e dal Open " -"Geospatial Consortium; in particolare, il WMS (Web Map Service) è " -"utilizzato per l'accesso alle mappe, il WFS (Web Feature Service) è " -"utilizzato per l'accesso ai dati vettoriali, e il WCS (Web Coverage Service) " -"è utilizzato per l'accesso ai dati raster. Il WMC (Web Map Context " -"Documents) viene utilizzato per la condivisione delle mappe. Questi servizi " -"possono essere utilizzati nelle proprie applicazioni utilizzando librerie " -"come OpenLayers, GeoTools e OGR (tutti software open-source e disponibili a " -"costo zero). Inoltre, CSW (Catalog Service for the Web) supporta l'accesso a " -"raccolte di informazioni descrittive (metadati) su dati e servizi.

\n" +"

I dati in questa applicazione sono serviti utilizzando standard aperti approvati " +"dall'ISO e dal Open Geospatial Consortium; in " +"particolare, il WMS (Web Map Service) è utilizzato per l'accesso alle mappe, il WFS (Web " +"Feature Service) è utilizzato per l'accesso ai dati vettoriali, e il WCS (Web Coverage " +"Service) è utilizzato per l'accesso ai dati raster. Il WMC (Web Map Context Documents) " +"viene utilizzato per la condivisione delle mappe. Questi servizi possono essere utilizzati " +"nelle proprie applicazioni utilizzando librerie come OpenLayers, GeoTools e OGR (tutti " +"software open-source e disponibili a costo zero). Inoltre, CSW (Catalog Service for the Web) " +"supporta l'accesso a raccolte di informazioni descrittive (metadati) su dati e servizi.

\n" "\n" "

Che cos'è GeoWebCache?

\n" -"

GeoWebCache fornisce tiles di mappatura compatibili con una serie di " -"motori di mappatura, tra cui Google Maps, Bing Maps e OpenLayers. Tutti i " -"dati ospitati da GeoNode sono disponibili anche attraverso GeoWebCache. " -"GeoWebCache migliora il WMS mettendo in cache i dati e fornendo mappe più " -"reattive.

\n" +"

GeoWebCache fornisce tiles di mappatura compatibili con una serie di motori di " +"mappatura, tra cui Google Maps, Bing Maps e OpenLayers. Tutti i dati ospitati da GeoNode " +"sono disponibili anche attraverso GeoWebCache. GeoWebCache migliora il WMS mettendo in cache " +"i dati e fornendo mappe più reattive.

\n" "\n" "

CSW Codice d'esempio

\n" -"

Per interagire con il CSW di GeoNode è possibile utilizzare qualsiasi " -"client CSW (QGIS MetaSearch, GRASS, ecc.). Il seguente esempio illustra una " -"semplice invocazione utilizzando il pacchetto Python di OWSLib:

\n" +"

Per interagire con il CSW di GeoNode è possibile utilizzare qualsiasi client CSW " +"(QGIS MetaSearch, GRASS, ecc.). Il seguente esempio illustra una semplice invocazione " +"utilizzando il pacchetto Python di OWSLib:

\n" "

from owslib.csw import CatalogueServiceWeb

\n" "

from owslib.fes import PropertyIsLike

\n" "

csw = CatalogueServiceWeb('%(CATALOGUE_BASE_URL)s')

\n" @@ -5638,39 +5298,33 @@ msgstr "" "\n" "

Codice d'esempio OpenLayers

\n" "\n" -"

Per includere un livello di mappa GeoNode in una mappa OpenLayers, " -"trovare prima il nome di quel livello. Questo si trova nel campo nome del livello (non titolo) della lista dei livelli. Per " -"questo esempio, useremo il livello di sfondo dei confini politici del " -"Nicaragua, il cui nome è risk:nicaragua_admin. Quindi, creare " -"un'istanza di OpenLayers.Layer.WMS:

\n" -"

var geonodeLayer = new OpenLayers.Layer.WMS(\"GeoNode Risk Data" -"\", \"http://demo.geonode.org/geoserver/wms\",{ strati: \"risk:" -"nicaragua_admin\" });

\n" +"

Per includere un livello di mappa GeoNode in una mappa OpenLayers, trovare prima il " +"nome di quel livello. Questo si trova nel campo nome del livello (non " +"titolo) della lista dei livelli. Per questo esempio, useremo il livello di " +"sfondo dei confini politici del Nicaragua, il cui nome è risk:nicaragua_admin. " +"Quindi, creare un'istanza di OpenLayers.Layer.WMS:

\n" +"

var geonodeLayer = new OpenLayers.Layer.WMS(\"GeoNode Risk Data\", \"http://" +"demo.geonode.org/geoserver/wms\",{ strati: \"risk:nicaragua_admin\" });

\n" "\n" "

Codice di esempio di Google Maps

\n" -"

Per includere un livello di mappa GeoNode in una Google Map, " -"includere il nome del livello nel modello URL.

\n" -"

var tilelayer = new GTileLayer(null, null, null, null, " -"{tileUrlTemplate: 'http://demo.geonode.org/geoserver/gwc/service/gmaps?" -"layers=risk:nicaragua_admin&zoom={Z}&x={X}&y={Y}', isPng:true, " -"opacity:0.5 }', isPng:true, opacity:0.5 }. );

\n" +"

Per includere un livello di mappa GeoNode in una Google Map, includere il nome del " +"livello nel modello URL.

\n" +"

var tilelayer = new GTileLayer(null, null, null, null, {tileUrlTemplate: " +"'http://demo.geonode.org/geoserver/gwc/service/gmaps?layers=risk:nicaragua_admin&zoom={Z}" +"&x={X}&y={Y}', isPng:true, opacity:0.5 }', isPng:true, opacity:0.5 }. );

\n" "\n" "

Formafile/GeoJSON/GML Output

\n" -"

Per ottenere dati dai servizi web GeoNode utilizzare il protocollo " -"WFS. Per esempio, per ottenere i confini amministrativi completi del " -"Nicaragua usare:

\n" -"

http://demo.geonode.org/geoserver/wfs?request=GetFeature&" -"typeName=rischio:nicaragua_admin&outputformat=SHAPE-ZIP

\n" -"

Cambiando il formato di uscita in json, GML2, GML3, o csv si ottengono dati in questi " -"formati. Il protocollo WFS può anche gestire query più precise, specificando " -"un bounding box o vari filtri spaziali e non spaziali in base agli attributi " -"dei dati.

\n" +"

Per ottenere dati dai servizi web GeoNode utilizzare il protocollo WFS. Per esempio, " +"per ottenere i confini amministrativi completi del Nicaragua usare:

\n" +"

http://demo.geonode.org/geoserver/wfs?request=GetFeature&typeName=rischio:" +"nicaragua_admin&outputformat=SHAPE-ZIP

\n" +"

Cambiando il formato di uscita in json, GML2, GML3, o csv si ottengono dati in questi formati. Il protocollo WFS può anche " +"gestire query più precise, specificando un bounding box o vari filtri spaziali e non " +"spaziali in base agli attributi dei dati.

\n" "

Codice d'esempio di GeoTools

\n" -"

Creare un DataStore ed estrarre un FeatureType da esso, quindi " -"eseguire una query. È tutto documentato sul wiki all'indirizzo http://" -"geotools.org/.

\n" +"

Creare un DataStore ed estrarre un FeatureType da esso, quindi eseguire una query. È " +"tutto documentato sul wiki all'indirizzo http://geotools.org/.

\n" " " msgid "GeoNode's Web Services" @@ -5707,86 +5361,79 @@ msgid "GeoNode Help" msgstr "Aiuto su GeoNode" msgid "" -"This page provides helpful information about how to use the GeoNode. You can " -"use the sidebar links to navigate to what you want to know." +"This page provides helpful information about how to use the GeoNode. You can use the sidebar " +"links to navigate to what you want to know." msgstr "" -"Questa pagina fornisce informazioni di aiuto sull'utilizzo di GeoNode. Puoi " -"utilizzare i collegamenti nella barra laterale per ricercare quello che ti " -"interessa." +"Questa pagina fornisce informazioni di aiuto sull'utilizzo di GeoNode. Puoi utilizzare i " +"collegamenti nella barra laterale per ricercare quello che ti interessa." msgid "" "\n" -"

The GeoNode provides access to data sets and a map editing " -"application allows users to browse existing maps and contribute their own.\n" +"

The GeoNode provides access to data sets and a map editing application allows users " +"to browse existing maps and contribute their own.

\n" "\n" "

Browsing Layers

\n" "

The Layers tab allows you to browse data " "uploaded to this GeoNode.

\n" -"

All data can be downloaded in a variety of formats, for use in other " -"applications.

\n" +"

All data can be downloaded in a variety of formats, for use in other applications.\n" "\n" "

Developer Access

\n" -"

The Developer page is the place for " -"developers to get started building applications against the GeoNode. It " -"includes instructions on using the web services, links to the source code of " -"the GeoNode, and information about the open source projects used to create " -"it.

\n" +"

The Developer page is the place for developers " +"to get started building applications against the GeoNode. It includes instructions on using " +"the web services, links to the source code of the GeoNode, and information about the open " +"source projects used to create it.

\n" "\n" "

Browsing Maps

\n" -"

The GeoNode allows users to create and share maps with one another.\n" +"

The GeoNode allows users to create and share maps with one another.

\n" "

The Maps tab is a gateway to map exploration on " -"GeoNode. From here you can search for a map or " -"create a map, which will open the Map " -"Composer.

\n" +"GeoNode. From here you can search for a map or create a map, which will open the Map Composer.

\n" "
Google Earth Mode
\n" -"

Any map viewed in the interactive map editor can be seen in 3D mode " -"with the Google Earth plugin. To switch to 3D mode select the Google Earth " -"globe logo, the rightmost button on top toolbar. If you do not have the " -"Google Earth plugin installed you will be prompted to install it.

\n" +"

Any map viewed in the interactive map editor can be seen in 3D mode with the Google " +"Earth plugin. To switch to 3D mode select the Google Earth globe logo, the rightmost button " +"on top toolbar. If you do not have the Google Earth plugin installed you will be prompted to " +"install it.

\n" "\n" "

Creating a Map

\n" "

To create a new map go to the Contributed Maps " "tab and click the create your own map link.

\n" -"

This will take you to the Map Composer with " -"a base layer loaded.

\n" -"

To add data layers from the GeoNode click on the green plus button " -"located below the layers tab on the left hand side of the screen. This will " -"open a dialog listing all the layers available on the GeoNode.

\n" -"

To add layers to your map select them and hit the Add Layers button. When finished you may hit Done to close the " -"dialog and go back to the map.

\n" +"

This will take you to the Map Composer with a base " +"layer loaded.

\n" +"

To add data layers from the GeoNode click on the green plus button located below the " +"layers tab on the left hand side of the screen. This will open a dialog listing all the " +"layers available on the GeoNode.

\n" +"

To add layers to your map select them and hit the Add Layers button. " +"When finished you may hit Done to close the dialog and go back to the map.\n" "\n" "

Reordering and Removing Layers
\n" -"

Change the display order of the layers listed in the data tab by " -"simply dragging and dropping their names. The order in the map will be " -"updated to reflect that. To turn a layer's visibility off simply uncheck it, " -"and to remove it entirely select it and hit the red minus button.

\n" +"

Change the display order of the layers listed in the data tab by simply dragging and " +"dropping their names. The order in the map will be updated to reflect that. To turn a " +"layer's visibility off simply uncheck it, and to remove it entirely select it and hit the " +"red minus button.

\n" "\n" "
Saving your map
\n" -"

Once a suitable set of layers and zoom level has been found it's time " -"to save it so others can see it. Click the Save button--the left most icon " -"on the top toolbar, an image of a map with a disk--on the top menu and fill " -"out the title and abstract of the map.

\n" +"

Once a suitable set of layers and zoom level has been found it's time to save it so " +"others can see it. Click the Save button--the left most icon on the top toolbar, an image " +"of a map with a disk--on the top menu and fill out the title and abstract of the map.

\n" "\n" "

Publishing a Map

\n" -"

Any map from the GeoNode can be embedded for use in another site or " -"blog. To publish a map:

\n" +"

Any map from the GeoNode can be embedded for use in another site or blog. To publish " +"a map:

\n" "
    \n" -"
  1. Select the from the list of maps on the community or map page and " -"then hit the 'Publish map' button.
  2. \n" -"
  3. Choose your desired height and width for the widget in the wizard." -"
  4. \n" -"
  5. Copy the HTML snippet provided in the wizard to any HTML page or " -"iFrame-supporting blog post.
  6. \n" +"
  7. Select the from the list of maps on the community or map page and then hit the " +"'Publish map' button.
  8. \n" +"
  9. Choose your desired height and width for the widget in the wizard.
  10. \n" +"
  11. Copy the HTML snippet provided in the wizard to any HTML page or iFrame-supporting " +"blog post.
  12. \n" "
\n" -"

This will put an interactive widget showing you map in your web page " -"or blog post.

\n" -"

Note that the Map Composer also has a " -"button to publish the map. Just be sure to save the map before publishing if " -"there are changes that you want others to see. It publishes the last saved " -"version, not the last viewed version.

\n" +"

This will put an interactive widget showing you map in your web page or blog post.\n" +"

Note that the Map Composer also has a button to " +"publish the map. Just be sure to save the map before publishing if there are changes that " +"you want others to see. It publishes the last saved version, not the last viewed version.\n" "\n" "

Remixing a Map

\n" "

Any map available can serve as a starting point for a new map.

\n" @@ -5794,86 +5441,80 @@ msgid "" "
  • Open the map in the Map Composer.
  • \n" "
  • Add and remove layers as you like.
  • \n" "
  • Pan and zoom to highlight the area of interest.
  • \n" -"
  • IMPORTANT: Update the title, abstract, contact " -"info and tags to reflect the new map.
  • \n" +"
  • IMPORTANT: Update the title, abstract, contact info and tags to " +"reflect the new map.
  • \n" "
  • Save the map.
  • \n" " \n" -"

    You will be able to see your new map when you search for it from the " -"Maps tab.

    \n" +"

    You will be able to see your new map when you search for it from the Maps tab.

    \n" " " msgstr "" "\n" -"

    The GeoNode provides access to data sets and a map editing " -"application allows users to browse existing maps and contribute their own.\n" +"

    The GeoNode provides access to data sets and a map editing application allows users " +"to browse existing maps and contribute their own.

    \n" "\n" "

    Browsing Layers

    \n" "

    The Layers tab allows you to browse data " "uploaded to this GeoNode.

    \n" -"

    All data can be downloaded in a variety of formats, for use in other " -"applications.

    \n" +"

    All data can be downloaded in a variety of formats, for use in other applications.\n" "\n" "

    Developer Access

    \n" -"

    The Developer page is the place for " -"developers to get started building applications against the GeoNode. It " -"includes instructions on using the web services, links to the source code of " -"the GeoNode, and information about the open source projects used to create " -"it.

    \n" +"

    The Developer page is the place for developers " +"to get started building applications against the GeoNode. It includes instructions on using " +"the web services, links to the source code of the GeoNode, and information about the open " +"source projects used to create it.

    \n" "\n" "

    Browsing Maps

    \n" -"

    The GeoNode allows users to create and share maps with one another.\n" +"

    The GeoNode allows users to create and share maps with one another.

    \n" "

    The Maps tab is a gateway to map exploration on " -"GeoNode. From here you can search for a map or " -"create a map, which will open the Map " -"Composer.

    \n" +"GeoNode. From here you can search for a map or create a map, which will open the Map Composer.

    \n" "
    Google Earth Mode
    \n" -"

    Any map viewed in the interactive map editor can be seen in 3D mode " -"with the Google Earth plugin. To switch to 3D mode select the Google Earth " -"globe logo, the rightmost button on top toolbar. If you do not have the " -"Google Earth plugin installed you will be prompted to install it.

    \n" +"

    Any map viewed in the interactive map editor can be seen in 3D mode with the Google " +"Earth plugin. To switch to 3D mode select the Google Earth globe logo, the rightmost button " +"on top toolbar. If you do not have the Google Earth plugin installed you will be prompted to " +"install it.

    \n" "\n" "

    Creating a Map

    \n" "

    To create a new map go to the Contributed Maps " "tab and click the create your own map link.

    \n" -"

    This will take you to the Map Composer with " -"a base layer loaded.

    \n" -"

    To add data layers from the GeoNode click on the green plus button " -"located below the layers tab on the left hand side of the screen. This will " -"open a dialog listing all the layers available on the GeoNode.

    \n" -"

    To add layers to your map select them and hit the Add Layers button. When finished you may hit Done to close the " -"dialog and go back to the map.

    \n" +"

    This will take you to the Map Composer with a base " +"layer loaded.

    \n" +"

    To add data layers from the GeoNode click on the green plus button located below the " +"layers tab on the left hand side of the screen. This will open a dialog listing all the " +"layers available on the GeoNode.

    \n" +"

    To add layers to your map select them and hit the Add Layers button. " +"When finished you may hit Done to close the dialog and go back to the map.\n" "\n" "

    Reordering and Removing Layers
    \n" -"

    Change the display order of the layers listed in the data tab by " -"simply dragging and dropping their names. The order in the map will be " -"updated to reflect that. To turn a layer's visibility off simply uncheck it, " -"and to remove it entirely select it and hit the red minus button.

    \n" +"

    Change the display order of the layers listed in the data tab by simply dragging and " +"dropping their names. The order in the map will be updated to reflect that. To turn a " +"layer's visibility off simply uncheck it, and to remove it entirely select it and hit the " +"red minus button.

    \n" "\n" "
    Saving your map
    \n" -"

    Once a suitable set of layers and zoom level has been found it's time " -"to save it so others can see it. Click the Save button--the left most icon " -"on the top toolbar, an image of a map with a disk--on the top menu and fill " -"out the title and abstract of the map.

    \n" +"

    Once a suitable set of layers and zoom level has been found it's time to save it so " +"others can see it. Click the Save button--the left most icon on the top toolbar, an image " +"of a map with a disk--on the top menu and fill out the title and abstract of the map.

    \n" "\n" "

    Publishing a Map

    \n" -"

    Any map from the GeoNode can be embedded for use in another site or " -"blog. To publish a map:

    \n" +"

    Any map from the GeoNode can be embedded for use in another site or blog. To publish " +"a map:

    \n" "
      \n" -"
    1. Select the from the list of maps on the community or map page and " -"then hit the 'Publish map' button.
    2. \n" -"
    3. Choose your desired height and width for the widget in the wizard." -"
    4. \n" -"
    5. Copy the HTML snippet provided in the wizard to any HTML page or " -"iFrame-supporting blog post.
    6. \n" +"
    7. Select the from the list of maps on the community or map page and then hit the " +"'Publish map' button.
    8. \n" +"
    9. Choose your desired height and width for the widget in the wizard.
    10. \n" +"
    11. Copy the HTML snippet provided in the wizard to any HTML page or iFrame-supporting " +"blog post.
    12. \n" "
    \n" -"

    This will put an interactive widget showing you map in your web page " -"or blog post.

    \n" -"

    Note that the Map Composer also has a " -"button to publish the map. Just be sure to save the map before publishing if " -"there are changes that you want others to see. It publishes the last saved " -"version, not the last viewed version.

    \n" +"

    This will put an interactive widget showing you map in your web page or blog post.\n" +"

    Note that the Map Composer also has a button to " +"publish the map. Just be sure to save the map before publishing if there are changes that " +"you want others to see. It publishes the last saved version, not the last viewed version.\n" "\n" "

    Remixing a Map

    \n" "

    Any map available can serve as a starting point for a new map.

    \n" @@ -5881,12 +5522,12 @@ msgstr "" "
  • Open the map in the Map Composer.
  • \n" "
  • Add and remove layers as you like.
  • \n" "
  • Pan and zoom to highlight the area of interest.
  • \n" -"
  • IMPORTANT: Update the title, abstract, contact " -"info and tags to reflect the new map.
  • \n" +"
  • IMPORTANT: Update the title, abstract, contact info and tags to " +"reflect the new map.
  • \n" "
  • Save the map.
  • \n" " \n" -"

    You will be able to see your new map when you search for it from the " -"Maps tab.

    \n" +"

    You will be able to see your new map when you search for it from the Maps tab.

    \n" " " msgid "Sections" @@ -5923,30 +5564,25 @@ msgid "Advanced Search" msgstr "Ricerca Avanzata" msgid "" -"Click to search for geospatial data published by other users, organizations " -"and public sources. Download data in standard formats." +"Click to search for geospatial data published by other users, organizations and public " +"sources. Download data in standard formats." msgstr "" -"Clicca per la ricerca di dati geospaziali pubblicati da altri utenti, " -"organizzazioni e fonti pubbliche. Scaricare i dati in formati standard." +"Clicca per la ricerca di dati geospaziali pubblicati da altri utenti, organizzazioni e fonti " +"pubbliche. Scaricare i dati in formati standard." -#, fuzzy -#| msgid "dataset" msgid "Add datasets" -msgstr "dataset" +msgstr "Aggiungi dataset" -#, fuzzy -#| msgid "Explore Layers" msgid "Explore datasetss" -msgstr "Esplora livelli" +msgstr "Esplora i dataset" msgid "" -"Data is available for browsing, aggregating and styling to generate maps " -"which can be saved, downloaded, shared publicly or restricted to specify " -"users only." +"Data is available for browsing, aggregating and styling to generate maps which can be saved, " +"downloaded, shared publicly or restricted to specify users only." msgstr "" -"I dati sono disponibili per la navigazione, l'aggregazione e lo styling per " -"generare mappe che possono essere salvate, scaricate, condivise " -"pubblicamente o limitate per specificare solo gli utenti." +"I dati sono disponibili per la navigazione, l'aggregazione e lo styling per generare mappe " +"che possono essere salvate, scaricate, condivise pubblicamente o limitate per specificare " +"solo gli utenti." msgid "Create maps" msgstr "Creare mappe" @@ -5955,11 +5591,11 @@ msgid "Explore maps" msgstr "Esplora mappe" msgid "" -"As for the layers and maps GeoNode allows to publish tabular and text data, " -"manage their metadata and associated documents." +"As for the layers and maps GeoNode allows to publish tabular and text data, manage their " +"metadata and associated documents." msgstr "" -"Per quanto riguarda i livelli e le mappe, GeoNode consente di pubblicare " -"dati tabulari e di testo, gestirne i metadati e i documenti associati." +"Per quanto riguarda i livelli e le mappe, GeoNode consente di pubblicare dati tabulari e di " +"testo, gestirne i metadati e i documenti associati." msgid "Add documents" msgstr "Aggiungi documenti" @@ -5968,11 +5604,11 @@ msgid "Explore documents" msgstr "Esplora documenti" msgid "" -"Geonode allows registered users to easily upload geospatial data and various " -"documents in several formats." +"Geonode allows registered users to easily upload geospatial data and various documents in " +"several formats." msgstr "" -"GeoNode permette agli utenti registrati di caricare facilmente i dati " -"geospaziali e vari documenti in diversi formati." +"GeoNode permette agli utenti registrati di caricare facilmente i dati geospaziali e vari " +"documenti in diversi formati." msgid "See users" msgstr "Vedi utenti" @@ -5997,19 +5633,13 @@ msgid "is inviting you to join (%(site_name)s)." msgstr "ti sta invitando a registrati su (%(site_name)s)." #, python-format -msgid "" -"To do so, please register at %(site_name)s " -"Registration." +msgid "To do so, please register at %(site_name)s Registration." msgstr "" -"A tale scopo, registrarsi all'indirizzo " -"%(site_name)s Registrazione." +"A tale scopo, registrarsi all'indirizzo %(site_name)s " +"Registrazione." -msgid "" -"Once you receive the confirmation that your account is activated, you can " -"notify" -msgstr "" -"Una volta ricevuta la conferma dell'attivazione dell'account, è possibile " -"notificare" +msgid "Once you receive the confirmation that your account is activated, you can notify" +msgstr "Una volta ricevuta la conferma dell'attivazione dell'account, è possibile notificare" msgid "that you wish to join her/his group(s) through" msgstr "che si desidera unirsi al suo gruppo/i attraverso" @@ -6018,8 +5648,7 @@ msgid "this link" msgstr "questo link" #, python-format -msgid "" -"%(inviter_name)s is a member of the following group(s):" +msgid "%(inviter_name)s is a member of the following group(s):" msgstr "%(inviter_name)s è un membro dei seguenti gruppi:" msgid "We look forward to seeing you on the platform," @@ -6136,11 +5765,9 @@ msgstr "Il tuo account è ora attivo" msgid "has requested access to the site." msgstr "ha richiesto l'accesso a questo sito." -msgid "" -"You can enable access by setting the user as active on the admin section" +msgid "You can enable access by setting the user as active on the admin section" msgstr "" -"Puoi abilitare l'accesso all'utente impostandolo come attivo nella sezione " -"di Amministrazione" +"Puoi abilitare l'accesso all'utente impostandolo come attivo nella sezione di Amministrazione" msgid "A user has requested access to the site" msgstr "Un utente ha richiesto l'accesso al sito" @@ -6176,10 +5803,10 @@ msgid "A document has been uploaded" msgstr "Un documento è stato caricato" msgid "The following document was deleted" -msgstr "Il seguente documento è stato cancellato" +msgstr "Il seguente documento è stato eliminato" msgid "A document has been deleted" -msgstr "Un documento è stato cancellato" +msgstr "Un documento è stato eliminato" msgid "The following document was published" msgstr "Il seguente documento è stato pubblicato" @@ -6314,17 +5941,16 @@ msgid "" "\n" "

    \n" " Note:\n" -" You do not have a verified email address to which notices can be " -"sent. Add one now.\n" +" You do not have a verified email address to which notices can be sent. Add one now.\n" "

    \n" " " msgstr "" "\n" "

    \n" " Nota:\n" -" Non si dispone di un indirizzo e-mail verificato a cui gli " -"avvisi possono essere inviati. Aggiungerne uno " -"now.\n" +" Non si dispone di un indirizzo e-mail verificato a cui gli avvisi possono essere " +"inviati. Aggiungerne uno now.\n" "

    \n" " " @@ -6334,11 +5960,9 @@ msgstr "Tipo di notifica" msgid "requested you to download this resource" msgstr "ti ha richiesto di scaricare questa risorsa" -msgid "" -"Please go to resource page and assign the download permissions if you wish" +msgid "Please go to resource page and assign the download permissions if you wish" msgstr "" -"Per favore vai alla pagina delle risorse ed assegna i permessi di " -"scaricamento se desideri" +"Per favore vai alla pagina delle risorse ed assegna i permessi di scaricamento se desideri" msgid "A resource's download has been requested" msgstr "Richiesto lo scaricamento di una risorsa" @@ -6419,16 +6043,13 @@ msgstr "Selezione" msgid "No list items selected. Use the selection fields to add." msgstr "" -"Nessun elemento selezionato. Usare i pulsanti di selezione per aggiungere " -"gli elementi." +"Nessun elemento selezionato. Usare i pulsanti di selezione per aggiungere gli elementi." msgid "Filters" msgstr "Filtri" -#, fuzzy -#| msgid "Layers found" msgid "Datasets found" -msgstr "Livelli trovati" +msgstr "Dataset trovati" msgid "Maps found" msgstr "Mappe trovate" @@ -6466,10 +6087,8 @@ msgstr "Più popolari" msgid "Text" msgstr "Testo" -#, fuzzy -#| msgid "Share This" msgid "Share This Dataset" -msgstr "Condividi" +msgstr "Condividi questo dataset" msgid "Share This" msgstr "Condividi" @@ -6477,11 +6096,10 @@ msgstr "Condividi" msgid "Social Network Login Failure" msgstr "Errore di accesso al social network" -msgid "" -"An error occurred while attempting to login via your social network account." +msgid "An error occurred while attempting to login via your social network account." msgstr "" -"Si è verificato un errore durante il tentativo di accesso tramite l'account " -"del social network." +"Si è verificato un errore durante il tentativo di accesso tramite l'account del social " +"network." msgid "Code" msgstr "Codice" @@ -6493,16 +6111,14 @@ msgid "Account Connections" msgstr "Connessioni account" msgid "" -"You can sign in to your account using any of the following already connected " -"third party accounts:" +"You can sign in to your account using any of the following already connected third party " +"accounts:" msgstr "" -"Puoi accedere al tuo account utilizzando uno dei seguenti account di terze " -"parti già connessi:" +"Puoi accedere al tuo account utilizzando uno dei seguenti account di terze parti già " +"connessi:" -msgid "" -"You currently have no social network accounts connected to this account." -msgstr "" -"Al momento non hai account di social network connessi a questo account." +msgid "You currently have no social network accounts connected to this account." +msgstr "Al momento non hai account di social network connessi a questo account." msgid "Add a 3rd Party Account" msgstr "Aggiungere un account di terze parti" @@ -6512,13 +6128,12 @@ msgstr "Accesso annullato" #, python-format msgid "" -"You decided to cancel logging in to our site using one of your existing " -"accounts. If this was a mistake, please proceed to sign in." +"You decided to cancel logging in to our site using one of your existing accounts. If this " +"was a mistake, please proceed to sign in." msgstr "" -"Hai deciso di annullare l'accesso al nostro sito utilizzando uno dei tuoi " -"account esistenti. Se questo è stato un errore, si prega di procedere per accedere." +"Hai deciso di annullare l'accesso al nostro sito utilizzando uno dei tuoi account esistenti. " +"Se questo è stato un errore, si prega di procedere per accedere." msgid "Signup" msgstr "Registrati" @@ -6598,9 +6213,7 @@ msgid "Check this if the jumbotron background image already contains text" msgstr "Verificare se l'immagine di sfondo jumbotron contiene già testo" msgid "Disabling this slide will hide it from the slide show" -msgstr "" -"Se si disattiva questa diapositiva, questa verrà nascondeta dalla " -"presentazione" +msgstr "Se si disattiva questa diapositiva, questa verrà nascondeta dalla presentazione" msgid "Choose between using jumbotron background and slide show" msgstr "Scegliere tra l'utilizzo dello sfondo jumbotron e della presentazione" @@ -6611,19 +6224,11 @@ msgstr "Contenuto" msgid "Could not access to uploaded data." msgstr "Impossibile accedere ai dati caricati." -msgid "" -"One or more XML files was provided, but no matching files were found for " -"them." -msgstr "" -"È stato fornito uno o più file XML, ma non sono stati trovati file " -"corrispondenti." +msgid "One or more XML files was provided, but no matching files were found for them." +msgstr "È stato fornito uno o più file XML, ma non sono stati trovati file corrispondenti." -msgid "" -"One or more SLD files was provided, but no matching files were found for " -"them." -msgstr "" -"È stato fornito uno o più file SLD, ma non sono stati trovati file " -"corrispondenti." +msgid "One or more SLD files was provided, but no matching files were found for them." +msgstr "È stato fornito uno o più file SLD, ma non sono stati trovati file corrispondenti." msgid "Layer already exists" msgstr "Il livello esiste già" @@ -6644,8 +6249,7 @@ msgstr "Previsto per trovare il livello denominato '{name}' nel geoserver" msgid "Exception occurred while parsing the provided Metadata file." msgstr "Eccezione durante l'analisi del file di metadati fornito." -msgid "" -"The UUID identifier from the XML Metadata is already in use in this system." +msgid "The UUID identifier from the XML Metadata is already in use in this system." msgstr "L'identificatore UUID dei metadati XML è già in uso in questo sistema." msgid "Error configuring Layer" @@ -6655,9 +6259,7 @@ msgid "Invalid zip file detected" msgstr "Rilevato file zip non valido" msgid "Could not find any valid spatial file inside the uploaded zip" -msgstr "" -"Impossibile trovare alcun file spaziale valido all'interno del file zip " -"caricato" +msgstr "Impossibile trovare alcun file spaziale valido all'interno del file zip caricato" msgid "Invalid kmz file detected" msgstr "Rilevato file kmz non valido" @@ -6680,12 +6282,8 @@ msgstr "È consentito un solo file kml per file zip" msgid "Only one kml file per kmz is allowed" msgstr "È consentito un solo file kml per kmz" -msgid "" -"You are trying to upload multiple GeoTIFFs without a valid 'indexer." -"properties' file." -msgstr "" -"Si sta tentando di caricare più GeoTIFF senza un file 'indexer.properties' " -"valido." +msgid "You are trying to upload multiple GeoTIFFs without a valid 'indexer.properties' file." +msgstr "Si sta tentando di caricare più GeoTIFF senza un file 'indexer.properties' valido." msgid "Only one raster file per ZIP is allowed" msgstr "È consentito un solo file raster per file zip" @@ -6693,11 +6291,9 @@ msgstr "È consentito un solo file raster per file zip" msgid "No multiple rasters allowed" msgstr "Non sono consentiti più raster" -msgid "" -"To support the time step, you must enable the OGC_SERVER DATASTORE option" +msgid "To support the time step, you must enable the OGC_SERVER DATASTORE option" msgstr "" -"Per supportare il passaggio temporale, è necessario attivare l'opzione " -"OGC_SERVER DATASTORE" +"Per supportare il passaggio temporale, è necessario attivare l'opzione OGC_SERVER DATASTORE" #, python-brace-format msgid "Unsupported file type: {e.message}" @@ -6757,9 +6353,7 @@ msgstr "Permesso negato" #~ msgstr "Carica livello" #~ msgid "You are attempting to replace a vector layer with an unknown format." -#~ msgstr "" -#~ "Stai tentando di sostituire un livello vettoriale con un formato " -#~ "sconosiuto." +#~ msgstr "Stai tentando di sostituire un livello vettoriale con un formato sconosiuto." #~ msgid "Failed to upload the layer" #~ msgstr "Impossibile caricare il livello" From b40d44d872a060a6d8321ee56b596a9b18a7c8a8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Jul 2023 12:51:35 +0200 Subject: [PATCH 069/111] Bump pip from 23.2 to 23.2.1 (#11286) * Bump pip from 23.2 to 23.2.1 Bumps [pip](https://github.com/pypa/pip) from 23.2 to 23.2.1. - [Changelog](https://github.com/pypa/pip/blob/main/NEWS.rst) - [Commits](https://github.com/pypa/pip/compare/23.2...23.2.1) --- updated-dependencies: - dependency-name: pip dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * - Aligining "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index a9d73c1d309..ecea10b78ba 100644 --- a/requirements.txt +++ b/requirements.txt @@ -160,7 +160,7 @@ splinter==0.19.0 pytest-splinter==3.3.2 pytest-django==4.5.2 setuptools>=59.1.1,<68.1.0 -pip==23.2 +pip==23.2.1 Twisted==22.10.0 pixelmatch==0.3.0 factory-boy==3.3.0 diff --git a/setup.cfg b/setup.cfg index 3bbf22b9465..557414bae72 100644 --- a/setup.cfg +++ b/setup.cfg @@ -185,7 +185,7 @@ install_requires = pytest-splinter==3.3.2 pytest-django==4.5.2 setuptools>=59.1.1,<68.1.0 - pip==23.2 + pip==23.2.1 Twisted==22.10.0 pixelmatch==0.3.0 factory-boy==3.3.0 From 8744d3c0042a6e004d3362d54925a0cfe1c9e12f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Jul 2023 12:51:52 +0200 Subject: [PATCH 070/111] Bump gunicorn from 21.1.0 to 21.2.0 (#11290) * Bump gunicorn from 21.1.0 to 21.2.0 Bumps [gunicorn](https://github.com/benoitc/gunicorn) from 21.1.0 to 21.2.0. - [Release notes](https://github.com/benoitc/gunicorn/releases) - [Commits](https://github.com/benoitc/gunicorn/compare/21.1.0...21.2.0) --- updated-dependencies: - dependency-name: gunicorn dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * - Aligining "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index ecea10b78ba..91ffa8bd8e0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -144,7 +144,7 @@ pycountry # production uWSGI==2.0.21 -gunicorn==21.1.0 +gunicorn==21.2.0 ipython==8.14.0 docker==6.1.3 invoke==2.2.0 diff --git a/setup.cfg b/setup.cfg index 557414bae72..5627f5b4574 100644 --- a/setup.cfg +++ b/setup.cfg @@ -169,7 +169,7 @@ install_requires = # production uWSGI==2.0.21 - gunicorn==21.1.0 + gunicorn==21.2.0 ipython==8.14.0 docker==6.1.3 invoke==2.2.0 From a0e88e90337ef25cb9adc56db0fdf3562e1c3759 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Jul 2023 12:52:08 +0200 Subject: [PATCH 071/111] Bump drf-spectacular from 0.26.3 to 0.26.4 (#11289) * Bump drf-spectacular from 0.26.3 to 0.26.4 Bumps [drf-spectacular](https://github.com/tfranzel/drf-spectacular) from 0.26.3 to 0.26.4. - [Release notes](https://github.com/tfranzel/drf-spectacular/releases) - [Changelog](https://github.com/tfranzel/drf-spectacular/blob/master/CHANGELOG.rst) - [Commits](https://github.com/tfranzel/drf-spectacular/compare/0.26.3...0.26.4) --- updated-dependencies: - dependency-name: drf-spectacular dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * - Aligining "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 91ffa8bd8e0..ea943007e75 100644 --- a/requirements.txt +++ b/requirements.txt @@ -80,7 +80,7 @@ djangorestframework-gis==1.0 djangorestframework-guardian==0.3.0 drf-extensions==0.7.1 drf-writable-nested==0.7.0 -drf-spectacular==0.26.3 +drf-spectacular==0.26.4 dynamic-rest==2.1.2 Markdown==3.4.3 diff --git a/setup.cfg b/setup.cfg index 5627f5b4574..c197c9aa0a2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -106,7 +106,7 @@ install_requires = djangorestframework-guardian==0.3.0 drf-extensions==0.7.1 drf-writable-nested==0.7.0 - drf-spectacular==0.26.3 + drf-spectacular==0.26.4 dynamic-rest==2.1.2 Markdown==3.4.3 From a2d0981bce1f1a30ad5bf6df457f21b696c4f49f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Jul 2023 12:52:24 +0200 Subject: [PATCH 072/111] Bump webdriver-manager from 3.8.6 to 3.9.1 (#11288) * Bump webdriver-manager from 3.8.6 to 3.9.1 Bumps [webdriver-manager](https://github.com/SergeyPirogov/webdriver_manager) from 3.8.6 to 3.9.1. - [Release notes](https://github.com/SergeyPirogov/webdriver_manager/releases) - [Changelog](https://github.com/SergeyPirogov/webdriver_manager/blob/master/CHANGELOG.md) - [Commits](https://github.com/SergeyPirogov/webdriver_manager/compare/v3.8.6...v3.9.1) --- updated-dependencies: - dependency-name: webdriver-manager dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * - Aligining "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index ea943007e75..5fc7074cac2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -167,7 +167,7 @@ factory-boy==3.3.0 flaky==3.7.0 selenium>=4.1.0,<5.0.0 selenium-requests==2.0.3 -webdriver_manager==3.8.6 +webdriver_manager==3.9.1 # Security and audit mistune==3.0.1 diff --git a/setup.cfg b/setup.cfg index c197c9aa0a2..908a2277867 100644 --- a/setup.cfg +++ b/setup.cfg @@ -192,7 +192,7 @@ install_requires = flaky==3.7.0 selenium>=4.1.0,<5.0.0 selenium-requests==2.0.3 - webdriver_manager==3.8.6 + webdriver_manager==3.9.1 # Security and audit mistune==3.0.1 From d2f5d3df5436d0f49c870db85bf1088a5734e2df Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Jul 2023 12:52:41 +0200 Subject: [PATCH 073/111] Bump boto3 from 1.28.5 to 1.28.9 (#11287) * Bump boto3 from 1.28.5 to 1.28.9 Bumps [boto3](https://github.com/boto/boto3) from 1.28.5 to 1.28.9. - [Release notes](https://github.com/boto/boto3/releases) - [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/boto3/compare/1.28.5...1.28.9) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * - Aligining "setup.cfg" to "requirements.txt" --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 5fc7074cac2..32be7fdb44d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -113,7 +113,7 @@ django-storages==1.13.2 dropbox==11.36.2 google-cloud-storage==2.10.0 google-cloud-core==2.3.3 -boto3==1.28.5 +boto3==1.28.9 # Django Caches python-memcached<=1.59 diff --git a/setup.cfg b/setup.cfg index 908a2277867..f39b8c3d7b8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -138,7 +138,7 @@ install_requires = dropbox==11.36.2 google-cloud-storage==2.10.0 google-cloud-core==2.3.3 - boto3==1.28.5 + boto3==1.28.9 # Django Caches python-memcached<=1.59 From 1b0abd9e1f8c5485ed943e8aee79cf4badf7cb57 Mon Sep 17 00:00:00 2001 From: etj Date: Tue, 25 Jul 2023 12:37:05 +0200 Subject: [PATCH 074/111] [Fixes #11265] Change the WMS URL to a more generic OWS URL --- geonode/geoserver/helpers.py | 2 +- geonode/geoserver/tests/test_helpers.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/geonode/geoserver/helpers.py b/geonode/geoserver/helpers.py index d04fb3e12ff..c5d4742b6cd 100755 --- a/geonode/geoserver/helpers.py +++ b/geonode/geoserver/helpers.py @@ -2287,7 +2287,7 @@ def get_dataset_capabilities_url(layer, version="1.3.0", access_token=None): workspace_layername = layer.alternate.split(":") if ":" in layer.alternate else ("", layer.alternate) wms_url = settings.GEOSERVER_PUBLIC_LOCATION if not layer.remote_service: - wms_url = f"{wms_url}{'/'.join(workspace_layername)}/wms?service=wms&version={version}&request=GetCapabilities" # noqa + wms_url = f"{wms_url}{'/'.join(workspace_layername)}/ows?service=wms&version={version}&request=GetCapabilities" # noqa if access_token: wms_url += f"&access_token={access_token}" else: diff --git a/geonode/geoserver/tests/test_helpers.py b/geonode/geoserver/tests/test_helpers.py index 80a0823bf32..9c71711511a 100644 --- a/geonode/geoserver/tests/test_helpers.py +++ b/geonode/geoserver/tests/test_helpers.py @@ -292,6 +292,6 @@ def test_dataset_capabilties_url(self): ows_url = settings.GEOSERVER_PUBLIC_LOCATION identifier = "geonode:CA" dataset = Dataset.objects.get(alternate=identifier) - expected_url = f"{ows_url}geonode/CA/wms?service=wms&version=1.3.0&request=GetCapabilities" + expected_url = f"{ows_url}geonode/CA/ows?service=wms&version=1.3.0&request=GetCapabilities" capabilities_url = get_dataset_capabilities_url(dataset) self.assertEqual(capabilities_url, expected_url, capabilities_url) From 1ae4d1200b4384c184734e1c7f0e75f36ce824d2 Mon Sep 17 00:00:00 2001 From: etj Date: Mon, 17 Jul 2023 18:55:40 +0200 Subject: [PATCH 075/111] [Fixes #11262] Faceting: improvement: include count 0 topics --- geonode/facets/models.py | 10 +- geonode/facets/providers/baseinfo.py | 4 + geonode/facets/providers/category.py | 21 +++- geonode/facets/providers/keyword.py | 6 + geonode/facets/providers/region.py | 19 ++- geonode/facets/providers/thesaurus.py | 5 + geonode/facets/providers/users.py | 19 ++- geonode/facets/tests.py | 164 +++++++++++++++++++++++--- geonode/facets/views.py | 19 ++- 9 files changed, 236 insertions(+), 31 deletions(-) diff --git a/geonode/facets/models.py b/geonode/facets/models.py index 1b7f9964f7b..0270720e346 100644 --- a/geonode/facets/models.py +++ b/geonode/facets/models.py @@ -71,7 +71,14 @@ def get_info(self, lang="en", **kwargs) -> dict: pass def get_facet_items( - self, queryset, start: int = 0, end: int = DEFAULT_FACET_PAGE_SIZE, lang="en", topic_contains: str = None + self, + queryset, + start: int = 0, + end: int = DEFAULT_FACET_PAGE_SIZE, + lang="en", + topic_contains: str = None, + keys: set = {}, + **kwargs, ) -> (int, list): """ Return the items of the facets, in a tuple: @@ -87,6 +94,7 @@ def get_facet_items( :param end: int: pagination, the index of the last returned item :param lang: the preferred language for the labels :param topic_contains: only returns matching topics + :param keys: only returns topics with given keys, even if their count is 0 :return: a tuple int:total count of record, list of items """ pass diff --git a/geonode/facets/providers/baseinfo.py b/geonode/facets/providers/baseinfo.py index 7a3ee9b1ffb..f81e7583c66 100644 --- a/geonode/facets/providers/baseinfo.py +++ b/geonode/facets/providers/baseinfo.py @@ -52,6 +52,8 @@ def get_facet_items( end: int = DEFAULT_FACET_PAGE_SIZE, lang="en", topic_contains: str = None, + keys: set = {}, + **kwargs, ) -> (int, list): logger.debug("Retrieving facets for %s", self.name) @@ -120,6 +122,8 @@ def get_facet_items( end: int = DEFAULT_FACET_PAGE_SIZE, lang="en", topic_contains: str = None, + keys: set = {}, + **kwargs, ) -> (int, list): logger.debug("Retrieving facets for %s", self.name) diff --git a/geonode/facets/providers/category.py b/geonode/facets/providers/category.py index 9e027dec1e9..28b27749bbf 100644 --- a/geonode/facets/providers/category.py +++ b/geonode/facets/providers/category.py @@ -52,15 +52,26 @@ def get_facet_items( end: int = DEFAULT_FACET_PAGE_SIZE, lang="en", topic_contains: str = None, + keys: set = {}, + **kwargs, ) -> (int, list): logger.debug("Retrieving facets for %s", self.name) - q = queryset.values("category__identifier", "category__gn_description", "category__fa_class").filter( - category__isnull=False - ) + filters = {"category__isnull": False} + if topic_contains: - q = q.filter(category__gn_description=topic_contains) - q = q.annotate(count=Count("owner")).order_by("-count") + filters["category__gn_description"] = topic_contains + + if keys: + logger.debug("Filtering by keys %r", keys) + filters["category__identifier__in"] = keys + + q = ( + queryset.values("category__identifier", "category__gn_description", "category__fa_class") + .filter(**filters) + .annotate(count=Count("owner")) + .order_by("-count") + ) cnt = q.count() diff --git a/geonode/facets/providers/keyword.py b/geonode/facets/providers/keyword.py index 4d377538a2d..ce9e5f6f8f9 100644 --- a/geonode/facets/providers/keyword.py +++ b/geonode/facets/providers/keyword.py @@ -52,6 +52,8 @@ def get_facet_items( end: int = DEFAULT_FACET_PAGE_SIZE, lang="en", topic_contains: str = None, + keys: set = {}, + **kwargs, ) -> (int, list): logger.debug("Retrieving facets for %s", self.name) @@ -59,6 +61,10 @@ def get_facet_items( if topic_contains: filters["keywords__name__icontains"] = topic_contains + if keys: + logger.debug("Filtering by keys %r", keys) + filters["keywords__slug__in"] = keys + q = ( queryset.filter(**filters) .values("keywords__slug", "keywords__name") diff --git a/geonode/facets/providers/region.py b/geonode/facets/providers/region.py index a4935d35468..c40138e58bb 100644 --- a/geonode/facets/providers/region.py +++ b/geonode/facets/providers/region.py @@ -52,13 +52,26 @@ def get_facet_items( end: int = DEFAULT_FACET_PAGE_SIZE, lang="en", topic_contains: str = None, + keys: set = {}, + **kwargs, ) -> (int, list): logger.debug("Retrieving facets for %s", self.name) - q = queryset.filter(regions__isnull=False).values("regions__code", "regions__name") + filters = {"regions__isnull": False} + if topic_contains: - q = q.filter(regions__name=topic_contains) - q = q.annotate(count=Count("regions__code")).order_by("-count") + filters["regions__name"] = topic_contains + + if keys: + logger.debug("Filtering by keys %r", keys) + filters["regions__code__in"] = keys + + q = ( + queryset.filter(**filters) + .values("regions__code", "regions__name") + .annotate(count=Count("regions__code")) + .order_by("-count") + ) cnt = q.count() diff --git a/geonode/facets/providers/thesaurus.py b/geonode/facets/providers/thesaurus.py index 91823f00ac4..bb40f455924 100644 --- a/geonode/facets/providers/thesaurus.py +++ b/geonode/facets/providers/thesaurus.py @@ -62,6 +62,7 @@ def get_facet_items( end: int = DEFAULT_FACET_PAGE_SIZE, lang="en", topic_contains: str = None, + keys: set = {}, **kwargs, ) -> (int, list): logger.debug("Retrieving facets for %s", self._name) @@ -73,6 +74,10 @@ def get_facet_items( if topic_contains: filter["tkeywords__keyword__label__icontains"] = topic_contains + if keys: + logger.debug("Filtering by keys %r\n", keys) + filter["tkeywords__in"] = keys + q = ( queryset.filter(**filter) .values("tkeywords", "tkeywords__alt_label") diff --git a/geonode/facets/providers/users.py b/geonode/facets/providers/users.py index 80e47c50952..cf2a52cd9ba 100644 --- a/geonode/facets/providers/users.py +++ b/geonode/facets/providers/users.py @@ -52,13 +52,26 @@ def get_facet_items( end: int = DEFAULT_FACET_PAGE_SIZE, lang="en", topic_contains: str = None, + keys: set = {}, + **kwargs, ) -> (int, list): logger.debug("Retrieving facets for OWNER") - q = queryset.values("owner", "owner__username") + filters = dict() + if topic_contains: - q = q.filter(owner__username__icontains=topic_contains) - q = q.annotate(count=Count("owner")).order_by("-count") + filters["owner__username__icontains"] = topic_contains + + if keys: + logger.debug("Filtering by keys %r", keys) + filters["owner__in"] = keys + + q = ( + queryset.values("owner", "owner__username") + .filter(**filters) + .annotate(count=Count("owner")) + .order_by("-count") + ) cnt = q.count() diff --git a/geonode/facets/tests.py b/geonode/facets/tests.py index 127ada7cd7a..cf05e1e3734 100644 --- a/geonode/facets/tests.py +++ b/geonode/facets/tests.py @@ -27,9 +27,20 @@ from django.test import RequestFactory from django.urls import reverse -from geonode.base.models import Thesaurus, ThesaurusLabel, ThesaurusKeyword, ThesaurusKeywordLabel, ResourceBase, Region +from geonode.base.models import ( + Thesaurus, + ThesaurusLabel, + ThesaurusKeyword, + ThesaurusKeywordLabel, + ResourceBase, + Region, + TopicCategory, + HierarchicalKeyword, +) from geonode.facets.models import facet_registry from geonode.facets.providers.baseinfo import FeaturedFacetProvider +from geonode.facets.providers.category import CategoryFacetProvider +from geonode.facets.providers.keyword import KeywordFacetProvider from geonode.facets.providers.region import RegionFacetProvider from geonode.tests.base import GeoNodeBaseTestSupport import geonode.facets.views as views @@ -48,6 +59,8 @@ def setUpClass(cls): cls._create_thesauri() cls._create_regions() + cls._create_categories() + cls._create_keywords() cls._create_resources() cls.rf = RequestFactory() @@ -98,6 +111,28 @@ def _create_regions(cls): ): cls.regions[code] = Region.objects.create(code=code, name=name) + @classmethod + def _create_categories(cls): + cls.cats = {} + + for code, name in ( + ("C0", "Cat0"), + ("C1", "Cat1"), + ("C2", "Cat2"), + ): + cls.cats[code] = TopicCategory.objects.create(identifier=code, description=name, gn_description=name) + + @classmethod + def _create_keywords(cls): + cls.kw = {} + + for code, name in ( + ("K0", "Keyword0"), + ("K1", "Keyword1"), + ("K2", "Keyword2"), + ): + cls.kw[code] = HierarchicalKeyword.objects.create(slug=code, name=name) + @classmethod def _create_resources(self): public_perm_spec = {"users": {"AnonymousUser": ["view_resourcebase"]}, "groups": []} @@ -115,12 +150,12 @@ def _create_resources(self): # These are the assigned keywords to the Resources - # RB00 -> T1K0 R0,R1 FEAT - # RB01 -> T0K0 T1K0 R0 FEAT - # RB02 -> T1K0 R1 FEAT - # RB03 -> T0K0 T1K0 - # RB04 -> T1K0 - # RB05 -> T0K0 T1K0 + # RB00 -> T1K0 R0,R1 FEAT K0 C0 + # RB01 -> T0K0 T1K0 R0 FEAT K1 + # RB02 -> T1K0 R1 FEAT K2 C0 + # RB03 -> T0K0 T1K0 K0 + # RB04 -> T1K0 K0,K1 C0 + # RB05 -> T0K0 T1K0 K0,K2 C1 # RB06 -> T1K0 FEAT # RB07 -> T0K0 T1K0 FEAT # RB08 -> T1K0 T1K1 R1 FEAT @@ -130,11 +165,11 @@ def _create_resources(self): # RB12 -> T1K1 FEAT # RB13 -> T0K0 T0K1 R1 FEAT # RB14 -> FEAT - # RB15 -> T0K0 T0K1 - # RB16 -> + # RB15 -> T0K0 T0K1 C1 + # RB16 -> C1 # RB17 -> T0K0 T0K1 - # RB18 -> FEAT - # RB19 -> T0K0 T0K1 FEAT + # RB18 -> FEAT C2 + # RB19 -> T0K0 T0K1 FEAT C2 if x % 2 == 1: logger.debug(f"ADDING KEYWORDS {self.thesauri_k['0_0']} to RB {d}") @@ -147,13 +182,33 @@ def _create_resources(self): d.tkeywords.add(self.thesauri_k["1_0"]) if 7 < x < 13: d.tkeywords.add(self.thesauri_k["1_1"]) - if x in (0, 1): - d.regions.add(self.regions["R0"]) - if x in (0, 2, 8, 13): - d.regions.add(self.regions["R1"]) + if (x % 6) in (0, 1, 2): d.featured = True + for reg, idx in ( + ("R0", (0, 1)), + ("R1", (0, 2, 8, 13)), + ): + if x in idx: + d.regions.add(self.regions[reg]) + + for kw, idx in ( + ("K0", (0, 3, 4, 5)), + ("K1", [1, 4]), + ("K2", [2, 5]), + ): + if x in idx: + d.keywords.add(self.kw[kw]) + + for cat, idx in ( + ("C0", [0, 2, 4]), + ("C1", [5, 15, 16]), + ("C2", [18, 19]), + ): + if x in idx: + d.category = self.cats[cat] + d.save() d.set_permissions(public_perm_spec) @@ -193,7 +248,23 @@ def test_facets_rich(self): { "name": "category", "topics": { - "total": 0, + "total": 3, + "items": [ + {"label": "Cat0", "count": 3}, + {"label": "Cat1", "count": 3}, + {"label": "Cat2", "count": 2}, + ], + }, + }, + { + "name": "keyword", + "topics": { + "total": 3, + "items": [ + {"label": "Keyword0", "count": 4}, + {"label": "Keyword1", "count": 2}, + {"label": "Keyword2", "count": 2}, + ], }, }, { @@ -306,7 +377,7 @@ def test_bad_lang(self): def test_topics(self): for facet, keys, exp in ( ("t_0", [self.thesauri_k["0_0"].id, self.thesauri_k["0_1"].id, -999], 2), - ("category", ["C1", "C2", "nomatch"], 0), + ("category", ["C1", "C2", "nomatch"], 2), ("owner", [self.user.id, -100], 1), ("region", ["R0", "R1", "nomatch"], 2), ): @@ -409,6 +480,65 @@ def test_config(self): else: self.assertEqual(order, conf["order"], "Unexpected order") + def test_count0(self): + reginfo = RegionFacetProvider().get_info() + regfilter = reginfo["filter"] + regname = reginfo["name"] + + catinfo = CategoryFacetProvider().get_info() + catname = catinfo["name"] + + kwinfo = KeywordFacetProvider().get_info() + kwfilter = kwinfo["filter"] + kwname = kwinfo["name"] + + t0filter = facet_registry.get_provider("t_0").get_info()["filter"] + t1filter = facet_registry.get_provider("t_1").get_info()["filter"] + + def t(tk): + return self.thesauri_k[tk].id + + for facet, params, items in ( + # thesauri + ("t_1", {regfilter: "R0"}, {t("1_0"): 2}), + ("t_1", {regfilter: "R0", "key": [t("1_0")]}, {t("1_0"): 2}), + ("t_1", {regfilter: "R0", t0filter: t("0_1")}, {}), + ("t_1", {regfilter: "R0", t0filter: t("0_1"), "key": [t("1_0")]}, {t("1_0"): None}), + ( + "t_1", + {regfilter: "R0", t0filter: t("0_1"), "key": [t("1_1"), t("1_0")]}, + {t("1_0"): None, t("1_1"): None}, + ), + # regions + (regname, {t1filter: t("1_1")}, {"R1": 1}), + (regname, {t1filter: t("1_1"), "key": ["R0", "R1"]}, {"R1": 1, "R0": None}), + (regname, {t1filter: t("1_1"), "key": ["R0"]}, {"R0": None}), + # category + (catname, {t1filter: t("1_0")}, {"C0": 3, "C1": 1}), + (catname, {t1filter: t("1_0"), "key": ["C0", "C2"]}, {"C0": 3, "C2": None}), + (catname, {kwfilter: "K1"}, {"C0": 1}), + (catname, {kwfilter: "K1", "key": ["C0", "C2"]}, {"C0": 1, "C2": None}), + # keyword + (kwname, {t0filter: t("0_0")}, {"K0": 2, "K1": 1, "K2": 1}), + (kwname, {t0filter: t("0_0"), regfilter: "R0"}, {"K1": 1}), + (kwname, {t0filter: t("0_0"), regfilter: "R0", "key": ["K0"]}, {"K0": None}), + ): + req = self.rf.get(reverse("get_facet", args=[facet]), data=params) + res: JsonResponse = views.get_facet(req, facet) + obj = json.loads(res.content) + # self.assertEqual(totals, obj["topics"]["total"], f"Bad totals for facet '{facet} and params {params}") + + self.assertEqual( + len(items), + len(obj["topics"]["items"]), + f"Bad count for items '{facet} \n PARAMS: {params} \n RESULT: {obj} \n EXPECTED: {items}", + ) + # search item + for item in items.keys(): + found = next((i for i in obj["topics"]["items"] if i["key"] == item), None) + self.assertIsNotNone(found, f"Topic '{item}' not found in facet {facet} -- {obj}") + self.assertEqual(items[item], found.get("count", None), f"Bad count for facet '{facet}:{item}") + def test_user_auth(self): # make sure the user authorization pre-filters the visible resources # TODO test diff --git a/geonode/facets/views.py b/geonode/facets/views.py index b6e876f1a4d..a02c8aa961f 100644 --- a/geonode/facets/views.py +++ b/geonode/facets/views.py @@ -94,6 +94,8 @@ def get_facet(request, facet): include_config = _resolve_boolean(request, PARAM_INCLUDE_CONFIG, False) topic_contains = request.GET.get(PARAM_TOPIC_CONTAINS, None) + keys = set(request.query_params.getlist("key")) + page = int(request.GET.get(PARAM_PAGE, 0)) page_size = int(request.GET.get(PARAM_PAGE_SIZE, DEFAULT_FACET_PAGE_SIZE)) @@ -103,7 +105,7 @@ def get_facet(request, facet): qs = _prefilter_topics(request) topics = _get_topics( - provider, queryset=qs, page=page, page_size=page_size, lang=lang, topic_contains=topic_contains + provider, queryset=qs, page=page, page_size=page_size, lang=lang, topic_contains=topic_contains, keys=keys ) if add_link: @@ -156,11 +158,23 @@ def _get_topics( page_size: int = DEFAULT_FACET_PAGE_SIZE, lang: str = "en", topic_contains: str = None, + keys: set = {}, + **kwargs, ): start = page * page_size end = start + page_size - cnt, items = provider.get_facet_items(queryset, start=start, end=end, lang=lang, topic_contains=topic_contains) + cnt, items = provider.get_facet_items( + queryset, start=start, end=end, lang=lang, topic_contains=topic_contains, keys=keys + ) + + if keys: + keys.difference_update({str(t["key"]) for t in items}) + if keys: + ext = provider.get_topics(keys, lang) + items.extend(ext) + cnt += len(ext) + logger.debug("Extending facets to %d for %s", cnt, provider.name) return {"page": page, "page_size": page_size, "start": start, "total": cnt, "items": items} @@ -175,6 +189,7 @@ def _prefilter_topics(request): """ logger.debug("Filtering by user '%s'", request.user) filters = {k: vlist for k, vlist in request.query_params.lists() if k.startswith("filter{")} + logger.warning(f"FILTERING BY {filters}") if filters: viewset = ResourceBaseViewSet(request=request, format_kwarg={}, kwargs=filters) From 2f4e4af47a76890e79a3b1da43e6b494ecab80ab Mon Sep 17 00:00:00 2001 From: Alessio Fabiani Date: Thu, 27 Jul 2023 10:15:00 +0200 Subject: [PATCH 076/111] =?UTF-8?q?=20-=20Updating=20docker-compose=20tags?= =?UTF-8?q?,=20images=20versions=20and=20.envs=0B=20-=20Bump=20Po=E2=80=A6?= =?UTF-8?q?=20(#11199)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * - Updating docker-compose tags, images versions and .envs - Bump Postgres Image from 13 to 15 * - Fix tests * - Upgrade "letsencrypt" to "alpine:latest" base image * Bump nginx tag to 1.25.1-alpine * - Update images versions - Enable postgresql-client-15 --- .env | 18 ++++++++++-------- .env_dev | 2 ++ .env_local | 4 +++- .env_test | 4 +++- docker-compose.yml | 24 ++++++++++++------------ scripts/docker/base/ubuntu/Dockerfile | 4 ++-- scripts/docker/letsencrypt/Dockerfile | 2 +- scripts/docker/nginx/Dockerfile | 2 +- start_django_async.sh | 2 +- 9 files changed, 35 insertions(+), 27 deletions(-) diff --git a/.env b/.env index 96b166bf2fb..492278acb55 100644 --- a/.env +++ b/.env @@ -29,8 +29,10 @@ NGINX_BASE_URL= POSTGRES_USER=postgres POSTGRES_PASSWORD=postgres GEONODE_DATABASE=geonode +GEONODE_DATABASE_USER=geonode GEONODE_DATABASE_PASSWORD=geonode GEONODE_GEODATABASE=geonode_data +GEONODE_GEODATABASE_USER=geonode_data GEONODE_GEODATABASE_PASSWORD=geonode_data GEONODE_DATABASE_SCHEMA=public GEONODE_GEODATABASE_SCHEMA=public @@ -45,7 +47,7 @@ BROKER_URL=amqp://guest:guest@rabbitmq:5672/ CELERY_BEAT_SCHEDULER=celery.beat:PersistentScheduler ASYNC_SIGNALS=True -SITEURL=http://localhost/ +SITEURL=https://localhost/ ALLOWED_HOSTS=['django', '*'] @@ -67,8 +69,8 @@ GEONODE_LB_PORT=80 # IP or domain name and port where the server can be reached on HTTPS (leave HOST empty if you want to use HTTP only) # port where the server can be reached on HTTPS -HTTP_HOST=localhost -HTTPS_HOST= +HTTP_HOST= +HTTPS_HOST=localhost HTTP_PORT=80 HTTPS_PORT=443 @@ -78,8 +80,8 @@ HTTPS_PORT=443 # disabled : we do not get a certificate at all (a placeholder certificate will be used) # staging : we get staging certificates (are invalid, but allow to test the process completely and have much higher limit rates) # production : we get a normal certificate (default) -LETSENCRYPT_MODE=disabled -# LETSENCRYPT_MODE=staging +# LETSENCRYPT_MODE=disabled +LETSENCRYPT_MODE=staging # LETSENCRYPT_MODE=production RESOLVER=127.0.0.11 @@ -87,8 +89,8 @@ RESOLVER=127.0.0.11 # ################# # geoserver # ################# -GEOSERVER_WEB_UI_LOCATION=http://localhost/geoserver/ -GEOSERVER_PUBLIC_LOCATION=http://localhost/geoserver/ +GEOSERVER_WEB_UI_LOCATION=https://localhost/geoserver/ +GEOSERVER_PUBLIC_LOCATION=https://localhost/geoserver/ GEOSERVER_LOCATION=http://geoserver:8080/geoserver/ GEOSERVER_ADMIN_USER=admin GEOSERVER_ADMIN_PASSWORD=geoserver @@ -186,7 +188,7 @@ BING_API_KEY= GOOGLE_API_KEY= # Monitoring -MONITORING_ENABLED=True +MONITORING_ENABLED=False MONITORING_DATA_TTL=365 USER_ANALYTICS_ENABLED=True USER_ANALYTICS_GZIP=True diff --git a/.env_dev b/.env_dev index 937d630ad4d..88a2b273a4f 100644 --- a/.env_dev +++ b/.env_dev @@ -29,8 +29,10 @@ NGINX_BASE_URL= POSTGRES_USER=postgres POSTGRES_PASSWORD=postgres GEONODE_DATABASE=geonode +GEONODE_DATABASE_USER=geonode GEONODE_DATABASE_PASSWORD=geonode GEONODE_GEODATABASE=geonode_data +GEONODE_GEODATABASE_USER=geonode GEONODE_GEODATABASE_PASSWORD=geonode GEONODE_DATABASE_SCHEMA=public GEONODE_GEODATABASE_SCHEMA=public diff --git a/.env_local b/.env_local index 4bec5dc60bc..a19213926f2 100644 --- a/.env_local +++ b/.env_local @@ -29,8 +29,10 @@ NGINX_BASE_URL= POSTGRES_USER=postgres POSTGRES_PASSWORD=postgres GEONODE_DATABASE=geonode +GEONODE_DATABASE_USER=geonode GEONODE_DATABASE_PASSWORD=geonode GEONODE_GEODATABASE=geonode_data +GEONODE_GEODATABASE_USER=geonode GEONODE_GEODATABASE_PASSWORD=geonode GEONODE_DATABASE_SCHEMA=public GEONODE_GEODATABASE_SCHEMA=public @@ -45,7 +47,7 @@ BROKER_URL=amqp://admin:admin@localhost:5672// CELERY_BEAT_SCHEDULER=celery.beat:PersistentScheduler ASYNC_SIGNALS=False -SITEURL=http://localhost/ +SITEURL=http://localhost:8000/ ALLOWED_HOSTS="['django', '*']" diff --git a/.env_test b/.env_test index a728f8fd4d1..9eed802d452 100644 --- a/.env_test +++ b/.env_test @@ -29,8 +29,10 @@ NGINX_BASE_URL= POSTGRES_USER=postgres POSTGRES_PASSWORD=postgres GEONODE_DATABASE=geonode +GEONODE_DATABASE_USER=geonode GEONODE_DATABASE_PASSWORD=geonode GEONODE_GEODATABASE=geonode_data +GEONODE_GEODATABASE_USER=geonode_data GEONODE_GEODATABASE_PASSWORD=geonode_data GEONODE_DATABASE_SCHEMA=public GEONODE_GEODATABASE_SCHEMA=public @@ -195,7 +197,7 @@ BING_API_KEY= GOOGLE_API_KEY= # Monitoring -MONITORING_ENABLED=True +MONITORING_ENABLED=False MONITORING_DATA_TTL=365 USER_ANALYTICS_ENABLED=True USER_ANALYTICS_GZIP=True diff --git a/docker-compose.yml b/docker-compose.yml index 3af97ca2ebc..d87342e2ad4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,7 +4,7 @@ version: '3.9' x-common-django: &default-common-django image: geonode/geonode:latest-ubuntu-22.10 - restart: on-failure + restart: unless-stopped env_file: - .env volumes: @@ -50,7 +50,7 @@ services: # Nginx is serving django static and media files and proxies to django and geonode geonode: - image: geonode/nginx:4.0 + image: geonode/nginx:4.1.0 build: ./scripts/docker/nginx/ container_name: nginx4${COMPOSE_PROJECT_NAME} environment: @@ -67,11 +67,11 @@ services: - nginx-confd:/etc/nginx - nginx-certificates:/geonode-certificates - statics:/mnt/volumes/statics - restart: on-failure + restart: unless-stopped # Gets and installs letsencrypt certificates letsencrypt: - image: geonode/letsencrypt:4.0 + image: geonode/letsencrypt:4.1.0 build: ./scripts/docker/letsencrypt/ container_name: letsencrypt4${COMPOSE_PROJECT_NAME} environment: @@ -81,7 +81,7 @@ services: - LETSENCRYPT_MODE=${LETSENCRYPT_MODE} volumes: - nginx-certificates:/geonode-certificates - restart: on-failure + restart: unless-stopped # Geoserver backend geoserver: @@ -101,7 +101,7 @@ services: - backup-restore:/backup_restore - data:/data - tmp:/tmp - restart: on-failure + restart: unless-stopped depends_on: db: condition: service_healthy @@ -114,14 +114,14 @@ services: entrypoint: sleep infinity volumes: - geoserver-data-dir:/geoserver_data/data - restart: on-failure + restart: unless-stopped healthcheck: test: "ls -A '/geoserver_data/data' | wc -l" # PostGIS database. db: - # use geonode official postgis 13 image - image: geonode/postgis:13 + # use geonode official postgis 15 image + image: geonode/postgis:15 command: postgres -c "max_connections=${POSTGRESQL_MAX_CONNECTIONS}" container_name: db4${COMPOSE_PROJECT_NAME} env_file: @@ -129,7 +129,7 @@ services: volumes: - dbdata:/var/lib/postgresql/data - dbbackups:/pg_backups - restart: on-failure + restart: unless-stopped healthcheck: test: "pg_isready -d postgres -U postgres" # uncomment to enable remote connections to postgres @@ -138,11 +138,11 @@ services: # Vanilla RabbitMQ service. This is needed by celery rabbitmq: - image: rabbitmq:3.7-alpine + image: rabbitmq:3-alpine container_name: rabbitmq4${COMPOSE_PROJECT_NAME} volumes: - rabbitmq:/var/lib/rabbitmq - restart: on-failure + restart: unless-stopped volumes: statics: diff --git a/scripts/docker/base/ubuntu/Dockerfile b/scripts/docker/base/ubuntu/Dockerfile index 3b36cb6236f..13ca38d20c4 100644 --- a/scripts/docker/base/ubuntu/Dockerfile +++ b/scripts/docker/base/ubuntu/Dockerfile @@ -2,7 +2,7 @@ FROM ubuntu:22.10 RUN mkdir -p /usr/src/geonode -## Enable postgresql-client-13 +## Enable postgresql-client-15 RUN apt-get update -y && apt-get install curl wget unzip gnupg2 -y RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - # will install python3.10 @@ -17,7 +17,7 @@ RUN apt-get install -y \ RUN apt-get update -y && apt-get install -y --no-install-recommends \ gcc vim zip gettext geoip-bin cron \ - postgresql-client-13 \ + postgresql-client-15 \ python3-all-dev python3-dev \ python3-gdal python3-psycopg2 python3-ldap \ python3-pip python3-pil python3-lxml \ diff --git a/scripts/docker/letsencrypt/Dockerfile b/scripts/docker/letsencrypt/Dockerfile index a77f3b0219c..1480342e8f1 100644 --- a/scripts/docker/letsencrypt/Dockerfile +++ b/scripts/docker/letsencrypt/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.16 +FROM alpine:latest RUN apk add --no-cache certbot diff --git a/scripts/docker/nginx/Dockerfile b/scripts/docker/nginx/Dockerfile index 92652f376c6..0a497ce0384 100644 --- a/scripts/docker/nginx/Dockerfile +++ b/scripts/docker/nginx/Dockerfile @@ -1,4 +1,4 @@ -FROM nginx:1-alpine +FROM nginx:1.25.1-alpine RUN apk add --no-cache openssl inotify-tools diff --git a/start_django_async.sh b/start_django_async.sh index 78930d0377b..6b2d25eb84e 100755 --- a/start_django_async.sh +++ b/start_django_async.sh @@ -4,7 +4,7 @@ set -e export RESOURCE_PUBLISHING=True export ADMIN_MODERATE_UPLOADS=True export NOTIFICATION_ENABLED=True -export MONITORING_ENABLED=True +export MONITORING_ENABLED=False export EMAIL_ENABLED=True export BROKER_URL=amqp://guest:guest@localhost:5672/ export ASYNC_SIGNALS=True From 106c42f224c604990e0b027394fd785dcd31092f Mon Sep 17 00:00:00 2001 From: Alessio Fabiani Date: Mon, 31 Jul 2023 14:47:51 +0200 Subject: [PATCH 077/111] [Fixes #11080] Unable to add ArcGIS REST ImageServer as Remote Service (#11309) --- geonode/harvesting/harvesters/arcgis.py | 8 ++++---- requirements.txt | 2 +- setup.cfg | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/geonode/harvesting/harvesters/arcgis.py b/geonode/harvesting/harvesters/arcgis.py index c57202cc404..f64934f5b78 100644 --- a/geonode/harvesting/harvesters/arcgis.py +++ b/geonode/harvesting/harvesters/arcgis.py @@ -205,8 +205,8 @@ def _get_resource_descriptor( epsg_code, spatial_extent = _parse_spatial_extent(layer_representation["extent"]) ows_url = harvestable_resource.unique_identifier.rpartition("/")[0] store = slugify(ows_url) - name = layer_representation["id"] - title = layer_representation["name"] + name = layer_representation.get("id", layer_representation.get("name", "Undefined")) + title = layer_representation.get("name", layer_representation.get("title", "Undefined")) workspace = "remoteWorkspace" alternate = f"{workspace}:{name}" return resourcedescriptor.RecordDescription( @@ -307,8 +307,8 @@ def _get_resource_descriptor( epsg_code, spatial_extent = _parse_spatial_extent(layer_representation["extent"]) ows_url = harvestable_resource.unique_identifier.rpartition("/")[0] store = slugify(ows_url) - name = layer_representation["id"] - title = layer_representation["name"] + name = layer_representation.get("id", layer_representation.get("name", "Undefined")) + title = layer_representation.get("name", layer_representation.get("title", "Undefined")) workspace = "remoteWorkspace" alternate = f"{workspace}:{name}" return resourcedescriptor.RecordDescription( diff --git a/requirements.txt b/requirements.txt index 32be7fdb44d..208e17c1a7f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -96,7 +96,7 @@ geonode-oauth-toolkit==2.2.2 geonode-user-messages==2.0.2 geonode-announcements==2.0.2 geonode-django-activity-stream==0.10.0 -gn-arcrest==10.5.5 +gn-arcrest==10.5.6 geoserver-restconfig==2.0.9 gn-gsimporter==2.0.4 gisdata==0.5.4 diff --git a/setup.cfg b/setup.cfg index f39b8c3d7b8..280ea55eed8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -121,7 +121,7 @@ install_requires = geonode-user-messages==2.0.2 geonode-announcements==2.0.2 geonode-django-activity-stream==0.10.0 - gn-arcrest==10.5.5 + gn-arcrest==10.5.6 geoserver-restconfig==2.0.9 gn-gsimporter==2.0.4 gisdata==0.5.4 From a9d04f6e4911fdb68ac44359786ba90870ffdcc0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Jul 2023 16:04:08 +0200 Subject: [PATCH 078/111] Bump django-sequences from 2.7 to 2.8 (#11301) * Bump django-sequences from 2.7 to 2.8 Bumps [django-sequences](https://github.com/aaugustin/django-sequences) from 2.7 to 2.8. - [Commits](https://github.com/aaugustin/django-sequences/compare/2.7...2.8) --- updated-dependencies: - dependency-name: django-sequences dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Align setup.cfg to requirements.txt --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 208e17c1a7f..2678b560326 100644 --- a/requirements.txt +++ b/requirements.txt @@ -47,7 +47,7 @@ django-tinymce==3.6.1 django-grappelli==3.0.6 django-uuid-upload-path==1.0.0 django-widget-tweaks==1.4.12 -django-sequences==2.7 +django-sequences==2.8 oauthlib==3.2.2 pyopenssl==23.2.0 pyjwt==2.8.0 diff --git a/setup.cfg b/setup.cfg index 280ea55eed8..49f59b5ba56 100644 --- a/setup.cfg +++ b/setup.cfg @@ -73,7 +73,7 @@ install_requires = django-grappelli==3.0.6 django-uuid-upload-path==1.0.0 django-widget-tweaks==1.4.12 - django-sequences==2.7 + django-sequences==2.8 oauthlib==3.2.2 pyopenssl==23.2.0 pyjwt==2.8.0 From bf116689a188312acba09d4f0a7d298e0bbf55bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Jul 2023 16:04:44 +0200 Subject: [PATCH 079/111] Bump boto3 from 1.28.9 to 1.28.15 (#11304) * Bump boto3 from 1.28.9 to 1.28.15 Bumps [boto3](https://github.com/boto/boto3) from 1.28.9 to 1.28.15. - [Release notes](https://github.com/boto/boto3/releases) - [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/boto3/compare/1.28.9...1.28.15) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Align setup.cfg to requirements.txt --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 2678b560326..d5f73f708cf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -113,7 +113,7 @@ django-storages==1.13.2 dropbox==11.36.2 google-cloud-storage==2.10.0 google-cloud-core==2.3.3 -boto3==1.28.9 +boto3==1.28.15 # Django Caches python-memcached<=1.59 diff --git a/setup.cfg b/setup.cfg index 49f59b5ba56..2dad8c7d0f3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -138,7 +138,7 @@ install_requires = dropbox==11.36.2 google-cloud-storage==2.10.0 google-cloud-core==2.3.3 - boto3==1.28.9 + boto3==1.28.15 # Django Caches python-memcached<=1.59 From 4eb1eacb3e80e1058d4bc7ff135684029cf0307e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Jul 2023 16:05:06 +0200 Subject: [PATCH 080/111] Bump flake8 from 6.0.0 to 6.1.0 (#11307) * Bump flake8 from 6.0.0 to 6.1.0 Bumps [flake8](https://github.com/pycqa/flake8) from 6.0.0 to 6.1.0. - [Commits](https://github.com/pycqa/flake8/compare/6.0.0...6.1.0) --- updated-dependencies: - dependency-name: flake8 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Align setup.cfg to requirements.txt --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index d5f73f708cf..920bd63847c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -152,7 +152,7 @@ invoke==2.2.0 # tests coverage==7.2.7 requests-toolbelt==1.0.0 -flake8==6.0.0 +flake8==6.1.0 black==23.7.0 pytest==7.4.0 pytest-bdd==6.1.1 diff --git a/setup.cfg b/setup.cfg index 2dad8c7d0f3..1a03291ef93 100644 --- a/setup.cfg +++ b/setup.cfg @@ -177,7 +177,7 @@ install_requires = # tests coverage==7.2.7 requests-toolbelt==1.0.0 - flake8==6.0.0 + flake8==6.1.0 black==23.7.0 pytest==7.4.0 pytest-bdd==6.1.1 From 32ac5ad4cf6a33594cc17fca6e18a2f3bc57b21d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Jul 2023 16:06:00 +0200 Subject: [PATCH 081/111] Bump webdriver-manager from 3.9.1 to 4.0.0 (#11306) * Bump webdriver-manager from 3.9.1 to 4.0.0 Bumps [webdriver-manager](https://github.com/SergeyPirogov/webdriver_manager) from 3.9.1 to 4.0.0. - [Release notes](https://github.com/SergeyPirogov/webdriver_manager/releases) - [Changelog](https://github.com/SergeyPirogov/webdriver_manager/blob/master/CHANGELOG.md) - [Commits](https://github.com/SergeyPirogov/webdriver_manager/compare/v3.9.1...v4.0.0) --- updated-dependencies: - dependency-name: webdriver-manager dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Align setup.cfg to requirements.txt --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 920bd63847c..d59ded3314c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -167,7 +167,7 @@ factory-boy==3.3.0 flaky==3.7.0 selenium>=4.1.0,<5.0.0 selenium-requests==2.0.3 -webdriver_manager==3.9.1 +webdriver_manager==4.0.0 # Security and audit mistune==3.0.1 diff --git a/setup.cfg b/setup.cfg index 1a03291ef93..81e041141e0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -192,7 +192,7 @@ install_requires = flaky==3.7.0 selenium>=4.1.0,<5.0.0 selenium-requests==2.0.3 - webdriver_manager==3.9.1 + webdriver_manager==4.0.0 # Security and audit mistune==3.0.1 From a2e1a18567a8ae521253ef4645f69da8ca3efced Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Jul 2023 16:34:27 +0200 Subject: [PATCH 082/111] Bump uwsgi from 2.0.21 to 2.0.22 (#11305) * Bump uwsgi from 2.0.21 to 2.0.22 Bumps [uwsgi](https://github.com/unbit/uwsgi-docs) from 2.0.21 to 2.0.22. - [Commits](https://github.com/unbit/uwsgi-docs/commits) --- updated-dependencies: - dependency-name: uwsgi dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Align setup.cfg to requirements.txt --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index d59ded3314c..ea94c549363 100644 --- a/requirements.txt +++ b/requirements.txt @@ -143,7 +143,7 @@ django-ipware<5.1 pycountry # production -uWSGI==2.0.21 +uWSGI==2.0.22 gunicorn==21.2.0 ipython==8.14.0 docker==6.1.3 diff --git a/setup.cfg b/setup.cfg index 81e041141e0..2d495df23bd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -168,7 +168,7 @@ install_requires = pycountry # production - uWSGI==2.0.21 + uWSGI==2.0.22 gunicorn==21.2.0 ipython==8.14.0 docker==6.1.3 From 99ced683ec679c5f1bc0c1fe101602cc9a193638 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Jul 2023 16:35:20 +0200 Subject: [PATCH 083/111] Bump markdown from 3.4.3 to 3.4.4 (#11303) * Bump markdown from 3.4.3 to 3.4.4 Bumps [markdown](https://github.com/Python-Markdown/markdown) from 3.4.3 to 3.4.4. - [Changelog](https://github.com/Python-Markdown/markdown/blob/master/docs/change_log/release-2.6.md) - [Commits](https://github.com/Python-Markdown/markdown/compare/3.4.3...3.4.4) --- updated-dependencies: - dependency-name: markdown dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Align setup.cfg to requirements.txt --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index ea94c549363..a8700f0a047 100644 --- a/requirements.txt +++ b/requirements.txt @@ -82,7 +82,7 @@ drf-extensions==0.7.1 drf-writable-nested==0.7.0 drf-spectacular==0.26.4 dynamic-rest==2.1.2 -Markdown==3.4.3 +Markdown==3.4.4 pinax-notifications==6.0.0 pinax-ratings==4.0.0 diff --git a/setup.cfg b/setup.cfg index 2d495df23bd..882a2e505ff 100644 --- a/setup.cfg +++ b/setup.cfg @@ -108,7 +108,7 @@ install_requires = drf-writable-nested==0.7.0 drf-spectacular==0.26.4 dynamic-rest==2.1.2 - Markdown==3.4.3 + Markdown==3.4.4 pinax-notifications==6.0.0 pinax-ratings==4.0.0 From d70b289ca37c8ff8ff275ea32e90b0f2939c9887 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Jul 2023 16:36:58 +0200 Subject: [PATCH 084/111] Bump wandb from 0.15.5 to 0.15.7 (#11302) * Bump wandb from 0.15.5 to 0.15.7 Bumps [wandb](https://github.com/wandb/wandb) from 0.15.5 to 0.15.7. - [Release notes](https://github.com/wandb/wandb/releases) - [Changelog](https://github.com/wandb/wandb/blob/main/CHANGELOG.md) - [Commits](https://github.com/wandb/wandb/compare/v0.15.5...v0.15.7) --- updated-dependencies: - dependency-name: wandb dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Align setup.cfg to requirements.txt --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index a8700f0a047..10805de234a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -171,7 +171,7 @@ webdriver_manager==4.0.0 # Security and audit mistune==3.0.1 -wandb==0.15.5 +wandb==0.15.7 protobuf==3.20.3 mako==1.2.4 certifi>=2022.12.7 # not directly required, pinned by Snyk to avoid a vulnerability diff --git a/setup.cfg b/setup.cfg index 882a2e505ff..7668acc79c2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -196,7 +196,7 @@ install_requires = # Security and audit mistune==3.0.1 - wandb==0.15.5 + wandb==0.15.7 protobuf==3.20.3 mako==1.2.4 certifi>=2022.12.7 # not directly required, pinned by Snyk to avoid a vulnerability From 3c5729e24fef14c8da0dc4d9575d91a8946d2c21 Mon Sep 17 00:00:00 2001 From: Giovanni Allegri Date: Tue, 1 Aug 2023 12:44:37 +0200 Subject: [PATCH 085/111] [Fixes #11312] Expose dataset ows url (#11313) * expose dataser ows url * Formatting --- geonode/geoserver/helpers.py | 16 ++++++++++++++++ geonode/geoserver/tests/test_helpers.py | 12 ++++++++++++ geonode/layers/api/serializers.py | 1 + geonode/layers/models.py | 6 ++++++ 4 files changed, 35 insertions(+) diff --git a/geonode/geoserver/helpers.py b/geonode/geoserver/helpers.py index c5d4742b6cd..cc9135d243f 100755 --- a/geonode/geoserver/helpers.py +++ b/geonode/geoserver/helpers.py @@ -2296,6 +2296,22 @@ def get_dataset_capabilities_url(layer, version="1.3.0", access_token=None): return wms_url +def get_layer_ows_url(layer, access_token=None): + """ + Generate the layer-specific GetCapabilities URL + """ + workspace_layername = layer.alternate.split(":") if ":" in layer.alternate else ("", layer.alternate) + ows_url = settings.GEOSERVER_PUBLIC_LOCATION + if not layer.remote_service: + ows_url = f"{ows_url}{'/'.join(workspace_layername)}/ows" # noqa + if access_token: + ows_url += f"?access_token={access_token}" + else: + ows_url = f"{layer.remote_service.service_url}" + + return ows_url + + def wps_format_is_supported(_format, dataset_type): return (_format, dataset_type) in WPS_ACCEPTABLE_FORMATS diff --git a/geonode/geoserver/tests/test_helpers.py b/geonode/geoserver/tests/test_helpers.py index 9c71711511a..cf841bdf457 100644 --- a/geonode/geoserver/tests/test_helpers.py +++ b/geonode/geoserver/tests/test_helpers.py @@ -40,6 +40,7 @@ get_dataset_storetype, extract_name_from_sld, get_dataset_capabilities_url, + get_layer_ows_url, ) from geonode.geoserver.ows import _wcs_link, _wfs_link, _wms_link @@ -295,3 +296,14 @@ def test_dataset_capabilties_url(self): expected_url = f"{ows_url}geonode/CA/ows?service=wms&version=1.3.0&request=GetCapabilities" capabilities_url = get_dataset_capabilities_url(dataset) self.assertEqual(capabilities_url, expected_url, capabilities_url) + + @on_ogc_backend(geoserver.BACKEND_PACKAGE) + def test_layer_ows_url(self): + from geonode.layers.models import Dataset + + ows_url = settings.GEOSERVER_PUBLIC_LOCATION + identifier = "geonode:CA" + dataset = Dataset.objects.get(alternate=identifier) + expected_url = f"{ows_url}geonode/CA/ows" + capabilities_url = get_layer_ows_url(dataset) + self.assertEqual(capabilities_url, expected_url, capabilities_url) diff --git a/geonode/layers/api/serializers.py b/geonode/layers/api/serializers.py index fd8736fc4eb..270c469aa82 100644 --- a/geonode/layers/api/serializers.py +++ b/geonode/layers/api/serializers.py @@ -173,6 +173,7 @@ class Meta: "featureinfo_custom_template", "ows_url", "capabilities_url", + "dataset_ows_url", "workspace", "default_style", "styles", diff --git a/geonode/layers/models.py b/geonode/layers/models.py index 11612ed8f09..0d6c425e8e0 100644 --- a/geonode/layers/models.py +++ b/geonode/layers/models.py @@ -261,6 +261,12 @@ def capabilities_url(self): return get_dataset_capabilities_url(self) + @property + def dataset_ows_url(self): + from geonode.geoserver.helpers import get_layer_ows_url + + return get_layer_ows_url(self) + @property def embed_url(self): try: From 2b09aa6df2f2f51f051a8842b50f7fe2cc120c2a Mon Sep 17 00:00:00 2001 From: Giovanni Allegri Date: Fri, 4 Aug 2023 18:48:02 +0200 Subject: [PATCH 086/111] [Fixes #11323] Align the Position labels and translations (#11324) * Align the Position labels and translations * Improvement to it translation --- geonode/locale/en/LC_MESSAGES/django.mo | Bin 143913 -> 143908 bytes geonode/locale/en/LC_MESSAGES/django.po | 2 +- geonode/locale/it/LC_MESSAGES/django.mo | Bin 161119 -> 161121 bytes geonode/locale/it/LC_MESSAGES/django.po | 4 ++-- .../templates/people/profile_detail.html | 16 ++++++++-------- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/geonode/locale/en/LC_MESSAGES/django.mo b/geonode/locale/en/LC_MESSAGES/django.mo index f8663d06708af49835ebb15452c0cb68aec04c63..b7be4680ab464698552a6af0a9b8ec4582b4602f 100644 GIT binary patch delta 5371 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 diff --git a/geonode/locale/en/LC_MESSAGES/django.po b/geonode/locale/en/LC_MESSAGES/django.po index ae445ae92c7..d6ce030a65a 100644 --- a/geonode/locale/en/LC_MESSAGES/django.po +++ b/geonode/locale/en/LC_MESSAGES/django.po @@ -3982,7 +3982,7 @@ msgid "introduce yourself" msgstr "introduce yourself" msgid "Position Name" -msgstr "Position Name" +msgstr "Position" msgid "role or position of the responsible person" msgstr "role or position of the responsible person" diff --git a/geonode/locale/it/LC_MESSAGES/django.mo b/geonode/locale/it/LC_MESSAGES/django.mo index 0a9e88e7f7d43b2150075b76272bf4703f194b8f..ca8e5e1987454996ec2d980155ea22c053d2d976 100644 GIT binary patch delta 7343 zcmXZh33N`^8piSS9fTlMNC^pF5E&35#z@4Fs5yxth*=Dcs3FES<R!i?yRq3j$hF-0qv^A6%?*GZ&*0O$kpJ5O0e)s;4_VmTTGZzE59q;K? zaU5sq6`#`(S7IgHiY>4hWAUst=y#t}k$Ok0g~_&_j@76S#)ok%R={}}j0KpBD^UIY zjUni}>hqlX6zX2}IW@2iR>p4D-dLS_CWhf?R7V9^6PMciMYg^XYjgc$dR=H1aRw?Pt574}hW+pp+=~H!n1OzQ8qg6`$E6s6SFG1D zhI$z)a*==foCX++YPY9nFQlVFm51thp?zSTbrY(Ct*FrLM@66%_1de4TRk^p^QZ(U3*lB#-Z*{K^N!SdI3gLFGLOS zBh-Mtw)HbOiTWL^gX3=boD7_cio_utO8?Gz3hF57Zxe|$Y(zZ|wN}efq27!USb|F0 zQq%yhqdE@1?Q;?^0ktHPQSUv0isW;s$gRe@_`2%!@03tb5*@JqTQ{ixglgb7RPx-$ zH}F3CeKc^#=e$e(^D+iTJ?}2>;S2XnL|(gZmSP8LNe`eRcodbS|3PmEg_9HpW84Gd z)2L)SiOumcHp6yU|?G zzMN+tSb%D1C2DQgqn4xu6`3Ef7oJBQG%a1XJnLJd>U~iirK6T=JZgy-pboAj_Wmob z=ay&dd-ld2)EXZ}CCxQds47)(%a@`$YN^6(JsOqG9Z?;mV=EknQMk<3x1pZ<88wkx z*c9t}6rBR{Q3uT>OvaGPuJb7l zLOoZritDt-DcBv?qWbYpQ&3212AU;^K+PlyHPd)oPqL*6<`&#S5ri@F!~R zt5h}DgHi2;qYk8Is9Z=zc7^BUP*}l*JY0l#@C7WWX4XEiy0I~88+AqvtOu&2bX3Q~ zQ5{W39YFI@2V0@Nz7=&c?!ravl4BU6{XeLNc`+9?lYCV2EkG^Fa#Tle+WYULW?q6y zu7jxLJ%bv^1=Q}jXX~|Uy5;*l3UxmPHIN}#L;HU^1!eU@)XbJ+92TJ-Jcv8-XH+s4 zJ!A&99<_AE)Pi%M7psDuT(ViKd~a8Dvn9dG>)NsD@UccEKyCgXja) z%s)eA@i(XjFQW#09W|gP!REPks7R)w9|xk|n}wR#OTp}a?b8pqpk44eDgx)MS5YA= zv(~C@>QSh*O-6N?gW9%p(2qr^f$u`)#1T}7Cs5B_Lk&E*4*NfbLj5`>B&n!1Pe(1u zB-9LMVG~@8ip1Nf4vJA{{$W&u_pvPogqZ!`8P!e>YN9hxyWnZmdmB7^<9pQ1e?>KP z-Fnwr@nMtI)lnUXqmrzRz1|bm!BEtGpMqMdFHp&O9Q9-NBkH&0roHZ!QBa6|b&YjV zFSJB8kbnwtck5u(04G_eVG{MZ*c^ADCUP2;Lzhq;-#{&Kt$HTHZIOw1PG1UYV645d z4)x;4r~&Lnh5U&1l)Zli^`mqP6_JLaCPE3Qkf);7JQLMU9%}8M!1lNn>uLXgN1-Jb zE~92vzrO4Az{XfUQ`CVn4V4?7t^X6_sBcC!cmlQEf*QEx9~fOxOELlV_YXAR(f_~**#}>E?7vV3c$V>`%ozD0c#^PyIM^zh|0VSg%F&H(&0_>0LPy@V# zVOYPBX}<$%U=z^OfijPR4uplMwJt0Nb@=#MhnE*mw_F}MY1c@*@;5*BS{;zwnBJJd+iP}`?3>L8ko zO4d23hTlTX_!OpNK#b`i3-ui^1(h>fuor%ZdM_;2dbM`@c(T*XhcI z0(=Sg;CjqxV@7%vHItA8*Ga`hj6n}K;5)b$`?fVn`ztEcRoanVhz%{rV6@jew=EveS97p{qZpZizrh~IMlKN#-JH0!aZ9D|C*&d8y^+B!u2nL>K#@l!pPvKj*rHAWm#eS)- zvl;K=SlpOq&Vx!l?MaE6KpLu}OpL{ms1BE+&V{uYkB9MbEW=vb|8pKO+hHmG#f?>{ zwSKmjxn6{6)Hk4JdI6Q)f1^TPx3_sd3e{mJWY;+ZQ1>%Y?~Oz4ntarDEmB?k|2hRi z;e_^aoo6rtQ*jgO!{QR^EN|S`oC9M~N%u0g#;;IoS%#WvR6jG*A=rxg64Y}$QM=$* zd%tdf_P?^W7X=*E}2M ze?cu(m%%1t&)`Jr`%p^|J;XfU2~((#9^#pOxSk8z&&N?Syn_m9>nt;{QJ6q|F@|9= zD%8hN2bD9_oFB31r`jF6Vh-y3*oaz!Ls$uKqt2HH9tG{s8pBLdMWGt*iJHl5oPxVi z+beRoNwPRplJ!6}*b{Z8_d_jB7HYV%q)>gYq%HrtLG$Y<7rsD{2pKb}FIeC`NyJ=EG93%Q<*>+ukBKzYujktXDeQERyi zHIP#nf#*7HsJq~s9C16+ViaJ^sqe8wG6{$_AQ2&HM_@}+^j5Wzt6&1NYsH1%l zmVf_eQ;6Zh1k{XHpw8$+sI~eX6@h!G0fvqBZ+LC_7i<96W&9UWqy8gChqu;xDifeuE12Wn78D6YR$*Do0MCa_BeI zNm+TKNy=t8oceH7#CJ?&|0`7g<$^+Y857Z+WI9Yn$+V0M@m$z|df^yqA{q*JJuXGZoYYI{A8+4weUhR#%D zH4LWuFe>|FPyQ$(m_!PB-hfv$G6xrr{|2<~*dv(;uVlb+~Skw#? zPz`iOb^Hiwm;|C}?Cy zQ6V~uQFt9SgRp!P%4pQT-3Fq5mY+wh{geLR80vs9(A5s2LnZEr~PRG!%$xI0}^m38;Z3qZ*!v+BHw1+F6Qwa3gAfnf%qC wBm9T3200}Sa(q*tt1@t6&bS%5v!|Eve<61^sN8#6&ba*CoRU6)ZphUC1JYiclK=n! delta 7341 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 diff --git a/geonode/locale/it/LC_MESSAGES/django.po b/geonode/locale/it/LC_MESSAGES/django.po index f698c7205b5..8791b1cfec4 100644 --- a/geonode/locale/it/LC_MESSAGES/django.po +++ b/geonode/locale/it/LC_MESSAGES/django.po @@ -786,7 +786,7 @@ msgstr "" "controllo, zone postali e servizi, nomi di luoghi." msgid "Location" -msgstr "Posizione" +msgstr "Recapito" msgid "" "Features and characteristics of salt water bodies (excluding inland waters). Examples: " @@ -3775,7 +3775,7 @@ msgid "introduce yourself" msgstr "presentarsi" msgid "Position Name" -msgstr "Ruolo" +msgstr "Incarico" msgid "role or position of the responsible person" msgstr "ruolo o posizione della persona responsabile" diff --git a/geonode/people/templates/people/profile_detail.html b/geonode/people/templates/people/profile_detail.html index 0d6a48269e8..1a3c09ef7f1 100644 --- a/geonode/people/templates/people/profile_detail.html +++ b/geonode/people/templates/people/profile_detail.html @@ -73,14 +73,18 @@

    {{ profile.first_name|default:profile.name_long }}

    {% endif %} {% endif %} - - {% trans 'Position' %} - {{ profile.position | default:_('Not provided.') }} - {% trans 'Organization' %} {{ profile.organization | default:_('Not provided.') }} + + {% trans 'Description' %} + {{ profile.profile | default:_('Not provided.') }} + + + {% trans 'Position Name' %} + {{ profile.position | default:_('Not provided.') }} + {% trans 'Location' %} {{ profile.location | default:_('Not provided.') }} @@ -97,10 +101,6 @@

    {{ profile.first_name|default:profile.name_long }}

    {% trans 'Fax' %} {{ profile.fax | default:_('Not provided.') }} - - {% trans 'Description' %} - {{ profile.profile | default:_('Not provided.') }} - {% trans 'Keywords' %} From 84b27ae1b54b3f828b1e29fdcf7750120bcf3d75 Mon Sep 17 00:00:00 2001 From: Giovanni Allegri Date: Mon, 7 Aug 2023 17:58:17 +0200 Subject: [PATCH 087/111] use bracket notation inside the default feature template (#11343) --- geonode/layers/api/serializers.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/geonode/layers/api/serializers.py b/geonode/layers/api/serializers.py index 270c469aa82..3d39fdefe20 100644 --- a/geonode/layers/api/serializers.py +++ b/geonode/layers/api/serializers.py @@ -88,20 +88,20 @@ def get_attribute(self, instance): else: _attributes = instance.attributes.filter(visible=True).order_by("display_order") if _attributes.exists(): - _template = "
    " + _template = '
    ' for _field in _attributes: _label = _field.attribute_label or _field.attribute _template += '
    ' if _field.featureinfo_type == Attribute.TYPE_HREF: _template += ( '
    %s:
    \ -
    ' + ' % (_label, _field, _field) ) elif _field.featureinfo_type == Attribute.TYPE_IMAGE: _template += ( '
    \ - %s
    ' + %s
    ' % (_field.attribute, _field.attribute, _label, _label) ) elif _field.featureinfo_type in ( @@ -115,32 +115,32 @@ def get_attribute(self, instance): if "youtube" in _field.featureinfo_type: _template += ( '
    \ -
    ' +
    ' % (_field.attribute) ) else: _type = f"video/{_field.featureinfo_type[11:]}" _template += ( '
    \ -
    ' +
    ' % (_field.attribute, _type) ) elif _field.featureinfo_type == Attribute.TYPE_AUDIO: _template += ( '
    \ -
    ' +
    ' % (_field.attribute) ) elif _field.featureinfo_type == Attribute.TYPE_IFRAME: _template += ( '
    \ -
    ' +
    ' % (_field.attribute) ) elif _field.featureinfo_type == Attribute.TYPE_PROPERTY: _template += ( '
    %s:
    \ -
    ${properties.%s}
    ' +
    ${properties[\'%s\']}
    ' % (_label, _field.attribute) ) _template += "
    " From 1c8df230c1b44e8631dc8f6b0bec7e18e6d24685 Mon Sep 17 00:00:00 2001 From: Giovanni Allegri Date: Mon, 7 Aug 2023 18:00:27 +0200 Subject: [PATCH 088/111] remove Django admin widgets for bbox editable fields (#11339) --- geonode/documents/admin.py | 2 ++ geonode/geoapps/admin.py | 2 ++ geonode/layers/admin.py | 3 ++- geonode/maps/admin.py | 2 ++ geonode/services/admin.py | 1 + 5 files changed, 9 insertions(+), 1 deletion(-) diff --git a/geonode/documents/admin.py b/geonode/documents/admin.py index 36511c7d331..9af881f0e6d 100644 --- a/geonode/documents/admin.py +++ b/geonode/documents/admin.py @@ -36,6 +36,7 @@ class Meta(ResourceBaseAdminForm.Meta): class DocumentAdmin(TabbedTranslationAdmin): + exclude = ("ll_bbox_polygon", "bbox_polygon", "srid") list_display = ("id", "title", "date", "category", "group", "is_approved", "is_published", "metadata_completeness") list_display_links = ("id",) list_editable = ("title", "category", "group", "is_approved", "is_published") @@ -55,6 +56,7 @@ class DocumentAdmin(TabbedTranslationAdmin): "is_approved", "is_published", ) + readonly_fields = ("geographic_bounding_box",) date_hierarchy = "date" form = DocumentAdminForm actions = [metadata_batch_edit] diff --git a/geonode/geoapps/admin.py b/geonode/geoapps/admin.py index f0217db4cb2..33c8ff2f7c6 100644 --- a/geonode/geoapps/admin.py +++ b/geonode/geoapps/admin.py @@ -32,6 +32,7 @@ class Meta(ResourceBaseAdminForm.Meta): class GeoAppAdmin(TabbedTranslationAdmin): + exclude = ("ll_bbox_polygon", "bbox_polygon", "srid") list_display_links = ("title",) list_display = ( "id", @@ -65,6 +66,7 @@ class GeoAppAdmin(TabbedTranslationAdmin): "is_approved", "is_published", ) + readonly_fields = ("geographic_bounding_box",) form = GeoAppAdminForm def delete_queryset(self, request, queryset): diff --git a/geonode/layers/admin.py b/geonode/layers/admin.py index 379d361943b..7cb712206d1 100644 --- a/geonode/layers/admin.py +++ b/geonode/layers/admin.py @@ -55,6 +55,7 @@ class Meta(ResourceBaseAdminForm.Meta): class DatasetAdmin(TabbedTranslationAdmin): + exclude = ("ll_bbox_polygon", "bbox_polygon", "srid") list_display = ( "id", "alternate", @@ -86,7 +87,7 @@ class DatasetAdmin(TabbedTranslationAdmin): search_fields = ("alternate", "title", "abstract", "purpose", "is_approved", "is_published", "state") filter_horizontal = ("contacts",) date_hierarchy = "date" - readonly_fields = ("uuid", "alternate", "workspace") + readonly_fields = ("uuid", "alternate", "workspace", "geographic_bounding_box") inlines = [AttributeInline] form = DatasetAdminForm actions = [metadata_batch_edit] diff --git a/geonode/maps/admin.py b/geonode/maps/admin.py index 6be72dd9982..da6f8bf74a1 100644 --- a/geonode/maps/admin.py +++ b/geonode/maps/admin.py @@ -44,6 +44,7 @@ class MapAdmin(TabbedTranslationAdmin): inlines = [ MapLayerInline, ] + exclude = ("ll_bbox_polygon", "bbox_polygon", "srid") list_display_links = ("title",) list_display = ( "id", @@ -78,6 +79,7 @@ class MapAdmin(TabbedTranslationAdmin): "is_approved", "is_published", ) + readonly_fields = ("geographic_bounding_box",) form = MapAdminForm actions = [metadata_batch_edit] diff --git a/geonode/services/admin.py b/geonode/services/admin.py index d447c4145f0..2e61a21ce3e 100644 --- a/geonode/services/admin.py +++ b/geonode/services/admin.py @@ -31,6 +31,7 @@ class Meta(ResourceBaseAdminForm.Meta): class ServiceAdmin(admin.ModelAdmin): + exclude = ("ll_bbox_polygon", "bbox_polygon", "srid") list_display = ("id", "name", "base_url", "type", "method") list_display_links = ("id", "name") list_filter = ("id", "name", "type", "method") From 95eb82e8c79146dbe49ca2146fa405defc012732 Mon Sep 17 00:00:00 2001 From: mattiagiupponi <51856725+mattiagiupponi@users.noreply.github.com> Date: Mon, 7 Aug 2023 18:03:40 +0200 Subject: [PATCH 089/111] [Fixes #11332] Remove geoip task (#11336) * [Fixes #11332] Remove geoip task * [Fixes #11332] Rollback requirements.txt * [Fixes #11332] Rollback setuo.cfg --- entrypoint.sh | 1 - geonode/monitoring/models.py | 33 --------------------------------- pavement.py | 18 ------------------ tasks.py | 7 ------- 4 files changed, 59 deletions(-) diff --git a/entrypoint.sh b/entrypoint.sh index 4e394cdc703..620976448cc 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -54,7 +54,6 @@ else invoke prepare if [ ${FORCE_REINIT} = "true" ] || [ ${FORCE_REINIT} = "True" ] || [ ! -e "/mnt/volumes/statics/geonode_init.lock" ]; then - invoke updategeoip invoke fixtures invoke monitoringfixture invoke initialized diff --git a/geonode/monitoring/models.py b/geonode/monitoring/models.py index bbef043b228..18f302c73dc 100644 --- a/geonode/monitoring/models.py +++ b/geonode/monitoring/models.py @@ -701,39 +701,6 @@ def from_geonode(cls, service, request, response): sensitive_data = cls._get_user_data_gn(request) event_type = cls._get_event_type(request) - # ua = request.META.get('HTTP_USER_AGENT') or '' - # ua_family = cls._get_ua_family(ua) - # - # ip, is_routable = get_client_ip(request) - # lat = lon = None - # country = region = city = None - # if ip and is_routable: - # ip = ip.split(':')[0] - # if settings.TEST and ip == 'testserver': - # ip = '127.0.0.1' - # try: - # ip = gethostbyname(ip) - # except Exception as err: - # pass - # - # geoip = get_geoip() - # try: - # client_loc = geoip.city(ip) - # except Exception as err: - # log.warning("Cannot resolve %s: %s", ip, err) - # client_loc = None - # - # if client_loc: - # lat, lon = client_loc['latitude'], client_loc['longitude'], - # country = client_loc.get( - # 'country_code3') or client_loc['country_code'] - # if len(country) == 2: - # _c = pycountry.countries.get(alpha_2=country) - # country = _c.alpha_3 - # - # region = client_loc['region'] - # city = client_loc['city'] - data = { "received": received, "created": created, diff --git a/pavement.py b/pavement.py index f27d9d3c5de..5a494f124f4 100644 --- a/pavement.py +++ b/pavement.py @@ -261,10 +261,6 @@ def static(options): ) def setup(options): """Get dependencies and prepare a GeoNode development environment.""" - - if MONITORING_ENABLED: - updategeoip(options) - info( "GeoNode development environment successfully set up." "If you have not set up an administrative account," @@ -334,20 +330,6 @@ def upgradedb(options): print(f"Upgrades from version {version} are not yet supported.") -@task -@cmdopts([("settings=", "s", "Specify custom DJANGO_SETTINGS_MODULE")]) -def updategeoip(options): - """ - Update geoip db - """ - if MONITORING_ENABLED: - settings = options.get("settings", "") - if settings and "DJANGO_SETTINGS_MODULE" not in settings: - settings = f"DJANGO_SETTINGS_MODULE={settings}" - - sh(f"{settings} python -W ignore manage.py updategeoip -o") - - @task @cmdopts([("settings=", "s", "Specify custom DJANGO_SETTINGS_MODULE")]) def sync(options): diff --git a/tasks.py b/tasks.py index f3d72a0fff2..5711ab991b9 100755 --- a/tasks.py +++ b/tasks.py @@ -427,13 +427,6 @@ def monitoringfixture(ctx): logger.error(f"ERROR installing monitoring fixture: {str(e)}") -@task -def updategeoip(ctx): - print("**************************update geoip*******************************") - if ast.literal_eval(os.environ.get("MONITORING_ENABLED", "False")): - ctx.run(f"django-admin.py updategeoip --settings={_localsettings()}", pty=True) - - @task def updateadmin(ctx): print("***********************update admin details**************************") From e00861b85a024b98ae05d50c4fb99811888bd06a Mon Sep 17 00:00:00 2001 From: mattiagiupponi <51856725+mattiagiupponi@users.noreply.github.com> Date: Mon, 7 Aug 2023 18:06:44 +0200 Subject: [PATCH 090/111] [Fixes #11251] Extend MapLayer model with visualization properties (#11341) * [Fixes #11251] Extend MapLayer model with visualization properties * [Fixes #11251] Add test coverage --------- Co-authored-by: Giovanni Allegri --- geonode/maps/api/serializers.py | 10 +-- geonode/maps/api/tests.py | 73 +++++++++++++++++++ .../migrations/0043_auto_20230807_1234.py | 28 +++++++ geonode/maps/models.py | 5 ++ 4 files changed, 110 insertions(+), 6 deletions(-) create mode 100644 geonode/maps/migrations/0043_auto_20230807_1234.py diff --git a/geonode/maps/api/serializers.py b/geonode/maps/api/serializers.py index 2d9c88330e3..382bdd20d0b 100644 --- a/geonode/maps/api/serializers.py +++ b/geonode/maps/api/serializers.py @@ -138,6 +138,9 @@ class Meta: "current_style", "dataset", "name", + "order", + "visibility", + "opacity", ) @@ -145,12 +148,7 @@ class SimpleMapLayerSerializer(serializers.ModelSerializer): class Meta: model = MapLayer name = "maplayer" - fields = ( - "pk", - "name", - "extra_params", - "current_style", - ) + fields = ("pk", "name", "extra_params", "current_style", "order", "visibility", "opacity") class MapSerializer(ResourceBaseSerializer): diff --git a/geonode/maps/api/tests.py b/geonode/maps/api/tests.py index d5a15323a43..8d21f79bc2b 100644 --- a/geonode/maps/api/tests.py +++ b/geonode/maps/api/tests.py @@ -111,6 +111,10 @@ def test_maps(self): self.assertTrue(len(response.data["map"]["data"]["map"]["layers"]) == 7) self.assertEqual(response.data["map"]["maplayers"][0]["extra_params"], {"foo": "bar"}) self.assertIsNotNone(response.data["map"]["maplayers"][0]["dataset"]) + self.assertEqual(response.data["map"]["maplayers"][0]["extra_params"], {"foo": "bar"}) + self.assertEqual(response.data["map"]["maplayers"][0]["visibility"], 1) + self.assertEqual(response.data["map"]["maplayers"][0]["order"], 0) + self.assertEqual(response.data["map"]["maplayers"][0]["opacity"], 1.0) def test_extra_metadata_included_with_param(self): resource = Map.objects.first() @@ -150,6 +154,36 @@ def test_patch_map(self): self.assertEqual(response_maplayer["current_style"], "some-style-first-layer") self.assertIsNotNone(response_maplayer["dataset"]) + def test_patch_map_with_extra_maplayer_info(self): + """ + Patch to maps// + """ + # Get Layers List (backgrounds) + resource = Map.objects.first() + url = reverse("maps-detail", kwargs={"pk": resource.pk}) + + data = { + "title": f"{resource.title}-edited", + "abstract": resource.abstract, + "data": DUMMY_MAPDATA, + "id": resource.id, + "maplayers": DUMMY_MAPLAYERS_DATA_WITH_EXTRA_INFO, + } + self.client.login(username="admin", password="admin") + response = self.client.patch(f"{url}?include[]=data", data=data, format="json") + + self.assertEqual(response.status_code, 200) + self.assertTrue(len(response.data) > 0) + self.assertTrue("data" in response.data["map"]) + self.assertTrue(len(response.data["map"]["data"]["map"]["layers"]) == 7) + response_maplayer = response.data["map"]["maplayers"][0] + self.assertEqual(response_maplayer["extra_params"], {"msId": "Stamen.Watercolor__0"}) + self.assertEqual(response_maplayer["current_style"], "some-style-first-layer") + self.assertEqual(response_maplayer["visibility"], False) + self.assertEqual(response_maplayer["order"], 99) + self.assertEqual(response_maplayer["opacity"], 1.3) + self.assertIsNotNone(response_maplayer["dataset"]) + @patch("geonode.maps.api.views.resolve_object") def test_patch_map_raise_exception(self, mocked_obj): """ @@ -204,6 +238,34 @@ def test_create_map(self): self.assertIsNotNone(response_maplayer["dataset"]) self.assertIsNotNone(response.data["map"]["thumbnail_url"]) + def test_create_map_with_extra_maplayer_info(self): + """ + Post to maps/ + """ + # Get Layers List (backgrounds) + url = reverse("maps-list") + + data = { + "title": "Some created map", + "data": DUMMY_MAPDATA, + "maplayers": DUMMY_MAPLAYERS_DATA_WITH_EXTRA_INFO, + } + self.client.login(username="admin", password="admin") + response = self.client.post(f"{url}?include[]=data", data=data, format="json") + + self.assertEqual(response.status_code, 201) + self.assertTrue(len(response.data) > 0) + self.assertTrue("data" in response.data["map"]) + self.assertTrue(len(response.data["map"]["data"]["map"]["layers"]) == 7) + response_maplayer = response.data["map"]["maplayers"][0] + self.assertEqual(response_maplayer["extra_params"], {"msId": "Stamen.Watercolor__0"}) + self.assertEqual(response_maplayer["current_style"], "some-style-first-layer") + self.assertIsNotNone(response_maplayer["dataset"]) + self.assertEqual(response_maplayer["visibility"], False) + self.assertEqual(response_maplayer["order"], 99) + self.assertEqual(response_maplayer["opacity"], 1.3) + self.assertIsNotNone(response.data["map"]["thumbnail_url"]) + DUMMY_MAPDATA = { "map": { @@ -365,3 +427,14 @@ def test_create_map(self): "name": "geonode:CA", } ] + +DUMMY_MAPLAYERS_DATA_WITH_EXTRA_INFO = [ + { + "extra_params": {"msId": "Stamen.Watercolor__0"}, + "current_style": "some-style-first-layer", + "name": "geonode:CA", + "opacity": 1.3, + "visibility": False, + "order": 99, + } +] diff --git a/geonode/maps/migrations/0043_auto_20230807_1234.py b/geonode/maps/migrations/0043_auto_20230807_1234.py new file mode 100644 index 00000000000..894c1e4ea5c --- /dev/null +++ b/geonode/maps/migrations/0043_auto_20230807_1234.py @@ -0,0 +1,28 @@ +# Generated by Django 3.2.19 on 2023-08-07 12:34 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('maps', '0042_remove_maplayer_styles'), + ] + + operations = [ + migrations.AddField( + model_name='maplayer', + name='opacity', + field=models.FloatField(default=1.0), + ), + migrations.AddField( + model_name='maplayer', + name='order', + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name='maplayer', + name='visibility', + field=models.BooleanField(default=True), + ), + ] diff --git a/geonode/maps/models.py b/geonode/maps/models.py index 650417aebd8..90b18bc98ab 100644 --- a/geonode/maps/models.py +++ b/geonode/maps/models.py @@ -262,6 +262,11 @@ class MapLayer(models.Model): local = models.BooleanField(default=False, blank=True) # True if this layer is served by the local geoserver + # Extend MapLayer model with visualization properties #11251 + order = models.IntegerField(default=0) + visibility = models.BooleanField(default=True) + opacity = models.FloatField(default=1.0) + @property def dataset_title(self): """ From 48613757ca48d92f49191fce6ba4632d91c4949b Mon Sep 17 00:00:00 2001 From: Giovanni Allegri Date: Tue, 8 Aug 2023 12:09:08 +0200 Subject: [PATCH 091/111] fix feature template test (#11351) --- geonode/layers/api/tests.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/geonode/layers/api/tests.py b/geonode/layers/api/tests.py index dc6475bfdcc..7361e5c744a 100644 --- a/geonode/layers/api/tests.py +++ b/geonode/layers/api/tests.py @@ -93,8 +93,8 @@ def test_datasets(self): self.assertIsNotNone(response.data["dataset"].get("featureinfo_custom_template")) self.assertEqual( response.data["dataset"].get("featureinfo_custom_template"), - '
    Name:
    \ -
    ${properties.name}
    ', + '
    Name:
    \ +
    ${properties[\'name\']}
    ', ) _dataset.featureinfo_custom_template = "
    Foo Bar
    " @@ -104,8 +104,8 @@ def test_datasets(self): self.assertIsNotNone(response.data["dataset"].get("featureinfo_custom_template")) self.assertEqual( response.data["dataset"].get("featureinfo_custom_template"), - '
    Name:
    \ -
    ${properties.name}
    ', + '
    Name:
    \ +
    ${properties[\'name\']}
    ', ) _dataset.use_featureinfo_custom_template = True From c24458220aa1301c446fa2454b435e525cf8dcde Mon Sep 17 00:00:00 2001 From: Giovanni Allegri Date: Tue, 8 Aug 2023 23:43:26 +0200 Subject: [PATCH 092/111] Bump version (#11359) * Bump version * set python 3.10 as minimum version --- geonode/__init__.py | 2 +- setup.cfg | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/geonode/__init__.py b/geonode/__init__.py index e221b120dab..9b1e32fbb4d 100644 --- a/geonode/__init__.py +++ b/geonode/__init__.py @@ -19,7 +19,7 @@ import os -__version__ = (4, 1, 0, "dev", 0) +__version__ = (4, 2, 0, "dev", 0) default_app_config = "geonode.apps.AppConfig" diff --git a/setup.cfg b/setup.cfg index 7668acc79c2..04f081e7ac5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -14,12 +14,12 @@ classifiers = Intended Audience :: Developers Operating System :: OS Independent Topic :: Internet :: WWW/HTTP - Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.10 [options] zip_safe = False include_package_data = True -python_requires = >= 3.7 +python_requires = >= 3.10 packages = find: setup_requires = setuptools From 3fa9a8690f4aa27282cd815224014bb6cbc6fb00 Mon Sep 17 00:00:00 2001 From: Giovanni Allegri Date: Thu, 10 Aug 2023 18:19:45 +0200 Subject: [PATCH 093/111] [Fixes #11368] Fix spatial representation type parsing (#11369) * Fix spatial representation type parsing * improved code * fixed tests * fixed formatting --- geonode/base/fixtures/test_xml.xml | 2 +- geonode/catalogue/templates/catalogue/full_metadata.xml | 2 ++ geonode/layers/metadata.py | 4 +++- geonode/layers/tests.py | 4 ++-- geonode/resource/utils.py | 8 +------- 5 files changed, 9 insertions(+), 11 deletions(-) diff --git a/geonode/base/fixtures/test_xml.xml b/geonode/base/fixtures/test_xml.xml index 34a2af08443..1b990340631 100755 --- a/geonode/base/fixtures/test_xml.xml +++ b/geonode/base/fixtures/test_xml.xml @@ -406,7 +406,7 @@ - + vector eng diff --git a/geonode/catalogue/templates/catalogue/full_metadata.xml b/geonode/catalogue/templates/catalogue/full_metadata.xml index 39bffae1d3c..955f81c3e55 100644 --- a/geonode/catalogue/templates/catalogue/full_metadata.xml +++ b/geonode/catalogue/templates/catalogue/full_metadata.xml @@ -436,9 +436,11 @@ + {% if layer.spatial_representation_type %} {{layer.spatial_representation_type.identifier}} + {% endif %} {{layer.language}} diff --git a/geonode/layers/metadata.py b/geonode/layers/metadata.py index e61577ff5b5..74aab2ce8a9 100644 --- a/geonode/layers/metadata.py +++ b/geonode/layers/metadata.py @@ -79,7 +79,9 @@ def iso2dict(exml): mdata = MD_Metadata(exml) identifier = mdata.identifier vals["language"] = mdata.language or mdata.languagecode or "eng" - vals["spatial_representation_type"] = mdata.hierarchy + if mdata.identification[0].spatialrepresentationtype: + vals["spatial_representation_type"] = mdata.identification[0].spatialrepresentationtype[0] + vals["date"] = sniff_date(mdata.datestamp) if hasattr(mdata, "identification"): diff --git a/geonode/layers/tests.py b/geonode/layers/tests.py index fbe14c340ba..d7420fc2a84 100644 --- a/geonode/layers/tests.py +++ b/geonode/layers/tests.py @@ -1631,7 +1631,7 @@ def test_set_metadata_return_expected_values_from_xml(self): "date": datetime.datetime(2021, 4, 9, 9, 0, 46), "language": "eng", "purpose": None, - "spatial_representation_type": "dataset", + "spatial_representation_type": "vector", "supplemental_information": "No information provided", "temporal_extent_end": None, "temporal_extent_start": None, @@ -1691,7 +1691,7 @@ def setUp(self): "date": datetime.datetime(2021, 4, 9, 9, 0, 46), "language": "eng", "purpose": None, - "spatial_representation_type": "dataset", + "spatial_representation_type": "vector", "supplemental_information": "No information provided", "temporal_extent_end": None, "temporal_extent_start": None, diff --git a/geonode/resource/utils.py b/geonode/resource/utils.py index ca7175c799b..b8e70a362cb 100644 --- a/geonode/resource/utils.py +++ b/geonode/resource/utils.py @@ -167,13 +167,7 @@ def update_resource( if vals: for key, value in vals.items(): if key == "spatial_representation_type": - spatial_repr = SpatialRepresentationType.objects.filter(identifier=value) - if value is not None and spatial_repr.exists(): - value = SpatialRepresentationType(identifier=value) - # if the SpatialRepresentationType is not available in the DB, we just set it as None - elif value is not None and not spatial_repr.exists(): - value = None - defaults[key] = value + defaults[key] = SpatialRepresentationType.objects.filter(identifier=value).first() if value else None elif key == "topic_category": value, created = TopicCategory.objects.get_or_create( identifier=value, defaults={"description": "", "gn_description": value} From c84f55ef2143b962729bda3b85f749ce15c2afdf Mon Sep 17 00:00:00 2001 From: mattiagiupponi <51856725+mattiagiupponi@users.noreply.github.com> Date: Wed, 16 Aug 2023 11:32:36 +0200 Subject: [PATCH 094/111] =?UTF-8?q?[Fixes=20#11320]=20API=20V1=20delivers?= =?UTF-8?q?=20information=20on=20users=20that=20shouldn't=20be=E2=80=A6=20?= =?UTF-8?q?(#11321)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Fixes #11320] API V1 delivers information on users that shouldn't be visible * Fix black and flake8 * Fix black and flake8 * Fix black and flake8 * Fix black and flake8 * [Fixes #11320] API V1 delivers information on users that shouldn't be visible --------- Co-authored-by: Giovanni Allegri --- geonode/api/api.py | 10 ++++++++++ geonode/api/tests.py | 8 +++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/geonode/api/api.py b/geonode/api/api.py index 0663176dec9..4303c0caa4b 100644 --- a/geonode/api/api.py +++ b/geonode/api/api.py @@ -584,6 +584,16 @@ class OwnersResource(TypeFilteredResource): full_name = fields.CharField(null=True) + def apply_filters(self, request, applicable_filters): + """filter by group if applicable by group functionality""" + + semi_filtered = super().apply_filters(request, applicable_filters) + + if request.user and not request.user.is_superuser: + semi_filtered = get_available_users(request.user) + + return semi_filtered + def dehydrate_full_name(self, bundle): return bundle.obj.get_full_name() or bundle.obj.username diff --git a/geonode/api/tests.py b/geonode/api/tests.py index e8fb96626bb..39a428c21fb 100644 --- a/geonode/api/tests.py +++ b/geonode/api/tests.py @@ -321,7 +321,7 @@ def test_owners_lockdown(self): self.api_client.client.login(username="bobby", password="bob") resp = self.api_client.get(filter_url) self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 9) + self.assertEqual(len(self.deserialize(resp)["objects"]), 6) # Returns limitted info about other users bobby = get_user_model().objects.get(username="bobby") owners = self.deserialize(resp)["objects"] @@ -332,6 +332,12 @@ def test_owners_lockdown(self): self.assertIsNone(owner.get("email")) self.assertIsNone(owner.get("first_name")) + # now test with logged in admin + self.api_client.client.login(username="admin", password="admin") + resp = self.api_client.get(filter_url) + self.assertValidJSONResponse(resp) + self.assertEqual(len(self.deserialize(resp)["objects"]), 9) + @override_settings(API_LOCKDOWN=True) def test_groups_lockdown(self): groups_list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "groups"}) From c7d496462de7d67f2f3ee14db9e55bc392e1b9ce Mon Sep 17 00:00:00 2001 From: mattiagiupponi <51856725+mattiagiupponi@users.noreply.github.com> Date: Wed, 16 Aug 2023 11:35:52 +0200 Subject: [PATCH 095/111] Fixes #11296 Implement custom download URLs (#11298) * Fixes #11296 Implement custom download URLs * Fixes #11296 Fix tests * Fixes #11296 Let the download manager being overridable * Fixes #11296 rename dataset in resource * Fixes #11296 black and flake run * Fixes #11296 perform_last_step removed * Fixes #11296 file rename * Fixes #11296 file rename * Fixes #11296 file rename * Fixes #11296 fix codeql warning * Fixes #11296 fix flake8 and test * Fixes #11296 fix flake8 and test --------- Co-authored-by: Giovanni Allegri --- geonode/layers/tests.py | 32 +++---- geonode/layers/views.py | 76 ++------------- geonode/resource/download_handler.py | 133 +++++++++++++++++++++++++++ geonode/settings.py | 2 + geonode/tests/smoke.py | 21 +++++ 5 files changed, 177 insertions(+), 87 deletions(-) create mode 100644 geonode/resource/download_handler.py diff --git a/geonode/layers/tests.py b/geonode/layers/tests.py index d7420fc2a84..2deb3ca0a21 100644 --- a/geonode/layers/tests.py +++ b/geonode/layers/tests.py @@ -1210,7 +1210,7 @@ def test_dataset_download_invalid_wps_format(self): self.assertEqual(500, response.status_code) self.assertDictEqual({"error": "The format provided is not valid for the selected resource"}, response.json()) - @patch("geonode.layers.views.HttpClient.request") + @patch("geonode.resource.download_handler.HttpClient.request") def test_dataset_download_call_the_catalog_raise_error_for_no_200(self, mocked_catalog): _response = MagicMock(status_code=500, content="foo-bar") mocked_catalog.return_value = _response, "foo-bar" @@ -1220,12 +1220,9 @@ def test_dataset_download_call_the_catalog_raise_error_for_no_200(self, mocked_c url = reverse("dataset_download", args=[dataset.alternate]) response = self.client.get(url) self.assertEqual(500, response.status_code) - self.assertDictEqual( - {"error": "Download dataset exception: error during call with GeoServer: foo-bar"}, response.json() - ) + self.assertDictEqual({"error": "Download dataset exception: error during call with GeoServer"}, response.json()) - @patch("geonode.layers.views.HttpClient.request") - def test_dataset_download_call_the_catalog_raise_error_for_error_content(self, mocked_catalog): + def test_dataset_download_call_the_catalog_raise_error_for_error_content(self): content = """ @@ -1234,14 +1231,15 @@ def test_dataset_download_call_the_catalog_raise_error_for_error_content(self, m """ # noqa _response = MagicMock(status_code=200, text=content, headers={"Content-Type": "text/xml"}) - mocked_catalog.return_value = _response, content # if settings.USE_GEOSERVER is false, the URL must be redirected self.client.login(username="admin", password="admin") dataset = Dataset.objects.first() - url = reverse("dataset_download", args=[dataset.alternate]) - response = self.client.get(url) - self.assertEqual(500, response.status_code) - self.assertDictEqual({"error": "InvalidParameterValue: Foo Bar Exception"}, response.json()) + with patch("geonode.resource.download_handler.HttpClient.request") as mocked_catalog: + mocked_catalog.return_value = _response, content + url = reverse("dataset_download", args=[dataset.alternate]) + response = self.client.get(url) + self.assertEqual(500, response.status_code) + self.assertDictEqual({"error": "InvalidParameterValue: Foo Bar Exception"}, response.json()) def test_dataset_download_call_the_catalog_works(self): # if settings.USE_GEOSERVER is false, the URL must be redirected @@ -1249,7 +1247,7 @@ def test_dataset_download_call_the_catalog_works(self): self.client.login(username="admin", password="admin") dataset = Dataset.objects.first() layer = create_dataset(dataset.title, dataset.title, dataset.owner, "Point") - with patch("geonode.layers.views.HttpClient.request") as mocked_catalog: + with patch("geonode.resource.download_handler.HttpClient.request") as mocked_catalog: mocked_catalog.return_value = _response, "" url = reverse("dataset_download", args=[layer.alternate]) response = self.client.get(url) @@ -1268,21 +1266,21 @@ def test_dataset_download_call_the_catalog_work_anonymous(self): _response = MagicMock(status_code=200, text="", headers={"Content-Type": ""}) # noqa dataset = Dataset.objects.first() layer = create_dataset(dataset.title, dataset.title, dataset.owner, "Point") - with patch("geonode.layers.views.HttpClient.request") as mocked_catalog: + with patch("geonode.resource.download_handler.HttpClient.request") as mocked_catalog: mocked_catalog.return_value = _response, "" url = reverse("dataset_download", args=[layer.alternate]) response = self.client.get(url) self.assertTrue(response.status_code == 200) @override_settings(USE_GEOSERVER=True) - @patch("geonode.layers.views.get_template") + @patch("geonode.resource.download_handler.get_template") def test_dataset_download_call_the_catalog_work_for_raster(self, pathed_template): # if settings.USE_GEOSERVER is false, the URL must be redirected _response = MagicMock(status_code=200, text="", headers={"Content-Type": ""}) # noqa dataset = Dataset.objects.filter(subtype="raster").first() layer = create_dataset(dataset.title, dataset.title, dataset.owner, "Point") Dataset.objects.filter(alternate=layer.alternate).update(subtype="raster") - with patch("geonode.layers.views.HttpClient.request") as mocked_catalog: + with patch("geonode.resource.download_handler.HttpClient.request") as mocked_catalog: mocked_catalog.return_value = _response, "" url = reverse("dataset_download", args=[layer.alternate]) response = self.client.get(url) @@ -1295,13 +1293,13 @@ def test_dataset_download_call_the_catalog_work_for_raster(self, pathed_template ) @override_settings(USE_GEOSERVER=True) - @patch("geonode.layers.views.get_template") + @patch("geonode.resource.download_handler.get_template") def test_dataset_download_call_the_catalog_work_for_vector(self, pathed_template): # if settings.USE_GEOSERVER is false, the URL must be redirected _response = MagicMock(status_code=200, text="", headers={"Content-Type": ""}) # noqa dataset = Dataset.objects.filter(subtype="vector").first() layer = create_dataset(dataset.title, dataset.title, dataset.owner, "Point") - with patch("geonode.layers.views.HttpClient.request") as mocked_catalog: + with patch("geonode.resource.download_handler.HttpClient.request") as mocked_catalog: mocked_catalog.return_value = _response, "" url = reverse("dataset_download", args=[layer.alternate]) response = self.client.get(url) diff --git a/geonode/layers/views.py b/geonode/layers/views.py index 609a7f0c839..d9b08da7f97 100644 --- a/geonode/layers/views.py +++ b/geonode/layers/views.py @@ -23,21 +23,18 @@ import logging import warnings import traceback -from django.urls import reverse from owslib.wfs import WebFeatureService -import xml.etree.ElementTree as ET from django.conf import settings from django.db.models import F -from django.http import Http404, JsonResponse +from django.http import Http404 from django.contrib import messages from django.shortcuts import render from django.utils.html import escape from django.forms.utils import ErrorList from django.contrib.auth import get_user_model -from django.template.loader import get_template from django.utils.translation import ugettext as _ from django.core.exceptions import PermissionDenied from django.forms.models import inlineformset_factory @@ -50,9 +47,8 @@ from geonode import geoserver from geonode.layers.metadata import parse_metadata -from geonode.proxy.views import fetch_response_headers from geonode.resource.manager import resource_manager -from geonode.geoserver.helpers import set_dataset_style, wps_format_is_supported +from geonode.geoserver.helpers import set_dataset_style from geonode.resource.utils import update_resource from geonode.base.auth import get_or_create_token @@ -70,9 +66,10 @@ from geonode.groups.models import GroupProfile from geonode.security.utils import get_user_visible_groups, AdvancedSecurityWorkflowManager from geonode.people.forms import ProfileForm -from geonode.utils import HttpClient, check_ogc_backend, llbbox_to_mercator, resolve_object, mkdtemp +from geonode.utils import check_ogc_backend, llbbox_to_mercator, resolve_object, mkdtemp from geonode.geoserver.helpers import ogc_server_settings, select_relevant_files, write_uploaded_files_to_disk from geonode.geoserver.security import set_geowebcache_invalidate_cache +from django.utils.module_loading import import_string if check_ogc_backend(geoserver.BACKEND_PACKAGE): from geonode.geoserver.helpers import gs_catalog @@ -736,69 +733,8 @@ def dataset_metadata_advanced(request, layername): @csrf_exempt def dataset_download(request, layername): - try: - dataset = _resolve_dataset(request, layername, "base.download_resourcebase", _PERMISSION_MSG_GENERIC) - except Exception as e: - raise Http404(Exception(_("Not found"), e)) - - if not settings.USE_GEOSERVER: - # if GeoServer is not used, we redirect to the proxy download - return HttpResponseRedirect(reverse("download", args=[dataset.id])) - - download_format = request.GET.get("export_format") - - if download_format and not wps_format_is_supported(download_format, dataset.subtype): - logger.error("The format provided is not valid for the selected resource") - return JsonResponse({"error": "The format provided is not valid for the selected resource"}, status=500) - - _format = "application/zip" if dataset.is_vector() else "image/tiff" - # getting default payload - tpl = get_template("geoserver/dataset_download.xml") - ctx = {"alternate": dataset.alternate, "download_format": download_format or _format} - # applying context for the payload - payload = tpl.render(ctx) - - # init of Client - client = HttpClient() - - headers = {"Content-type": "application/xml", "Accept": "application/xml"} - - # defining the URL needed fr the download - url = f"{settings.OGC_SERVER['default']['LOCATION']}ows?service=WPS&version=1.0.0&REQUEST=Execute" - if not request.user.is_anonymous: - # define access token for the user - access_token = get_or_create_token(request.user) - url += f"&access_token={access_token}" - - # request to geoserver - response, content = client.request(url=url, data=payload, method="post", headers=headers) - - if response.status_code != 200: - logger.error(f"Download dataset exception: error during call with GeoServer: {response.content}") - return JsonResponse( - {"error": f"Download dataset exception: error during call with GeoServer: {response.content}"}, status=500 - ) - - # error handling - namespaces = {"ows": "http://www.opengis.net/ows/1.1", "wps": "http://www.opengis.net/wps/1.0.0"} - response_type = response.headers.get("Content-Type") - if response_type == "text/xml": - # parsing XML for get exception - content = ET.fromstring(response.text) - exc = content.find("*//ows:Exception", namespaces=namespaces) or content.find( - "ows:Exception", namespaces=namespaces - ) - if exc: - exc_text = exc.find("ows:ExceptionText", namespaces=namespaces) - logger.error(f"{exc.attrib.get('exceptionCode')} {exc_text.text}") - return JsonResponse({"error": f"{exc.attrib.get('exceptionCode')}: {exc_text.text}"}, status=500) - - return_response = fetch_response_headers( - HttpResponse(content=response.content, status=response.status_code, content_type=download_format), - response.headers, - ) - return_response.headers["Content-Type"] = download_format or _format - return return_response + DownloadHandler = import_string(settings.DATASET_DOWNLOAD_HANDLER) + return DownloadHandler(request, layername).get_download_response() @login_required diff --git a/geonode/resource/download_handler.py b/geonode/resource/download_handler.py new file mode 100644 index 00000000000..ae21e4e23a9 --- /dev/null +++ b/geonode/resource/download_handler.py @@ -0,0 +1,133 @@ +######################################################################### +# +# Copyright (C) 2023 OSGeo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### + +import logging +import xml.etree.ElementTree as ET + +from django.http import Http404, HttpResponse, HttpResponseRedirect, JsonResponse +from django.template.loader import get_template +from django.urls import reverse +from django.utils.translation import ugettext as _ +from django.conf import settings +from geonode.base.auth import get_or_create_token +from geonode.geoserver.helpers import wps_format_is_supported +from geonode.layers.views import _resolve_dataset +from geonode.proxy.views import fetch_response_headers +from geonode.utils import HttpClient + +logger = logging.getLogger("geonode.resource.download_handler") + + +class DownloadHandler: + def __str__(self): + return f"{self.__module__}.{self.__class__.__name__}" + + def __repr__(self): + return self.__str__() + + def __init__(self, request, resource_name) -> None: + self.request = request + self.resource_name = resource_name + + def get_download_response(self): + """ + Basic method. Should return the Response object + that allow the resource download + """ + resource = self.get_resource() + response = self.process_dowload(resource) + return response + + def get_resource(self): + """ + Returnt the object needed + """ + try: + return _resolve_dataset( + self.request, + self.resource_name, + "base.download_resourcebase", + _("You do not have permissions for this dataset."), + ) + except Exception as e: + raise Http404(Exception(_("Not found"), e)) + + def process_dowload(self, resource): + """ + Generate the response object + """ + if not settings.USE_GEOSERVER: + # if GeoServer is not used, we redirect to the proxy download + return HttpResponseRedirect(reverse("download", args=[resource.id])) + + download_format = self.request.GET.get("export_format") + + if download_format and not wps_format_is_supported(download_format, resource.subtype): + logger.error("The format provided is not valid for the selected resource") + return JsonResponse({"error": "The format provided is not valid for the selected resource"}, status=500) + + _format = "application/zip" if resource.is_vector() else "image/tiff" + # getting default payload + tpl = get_template("geoserver/dataset_download.xml") + ctx = {"alternate": resource.alternate, "download_format": download_format or _format} + # applying context for the payload + payload = tpl.render(ctx) + + # init of Client + client = HttpClient() + + headers = {"Content-type": "application/xml", "Accept": "application/xml"} + + # defining the URL needed fr the download + url = f"{settings.OGC_SERVER['default']['LOCATION']}ows?service=WPS&version=1.0.0&REQUEST=Execute" + if not self.request.user.is_anonymous: + # define access token for the user + access_token = get_or_create_token(self.request.user) + url += f"&access_token={access_token}" + + # request to geoserver + response, content = client.request(url=url, data=payload, method="post", headers=headers) + + if not response or response.status_code != 200: + logger.error(f"Download dataset exception: error during call with GeoServer: {content}") + return JsonResponse( + {"error": "Download dataset exception: error during call with GeoServer"}, + status=500, + ) + + # error handling + namespaces = {"ows": "http://www.opengis.net/ows/1.1", "wps": "http://www.opengis.net/wps/1.0.0"} + response_type = response.headers.get("Content-Type") + if response_type == "text/xml": + # parsing XML for get exception + content = ET.fromstring(response.text) + exc = content.find("*//ows:Exception", namespaces=namespaces) or content.find( + "ows:Exception", namespaces=namespaces + ) + if exc: + exc_text = exc.find("ows:ExceptionText", namespaces=namespaces) + logger.error(f"{exc.attrib.get('exceptionCode')} {exc_text.text}") + return JsonResponse({"error": f"{exc.attrib.get('exceptionCode')}: {exc_text.text}"}, status=500) + + return_response = fetch_response_headers( + HttpResponse(content=response.content, status=response.status_code, content_type=download_format), + response.headers, + ) + return_response.headers["Content-Type"] = download_format or _format + return return_response diff --git a/geonode/settings.py b/geonode/settings.py index 4a90f085e68..72f37a84b6f 100644 --- a/geonode/settings.py +++ b/geonode/settings.py @@ -2344,3 +2344,5 @@ def get_geonode_catalogue_service(): {"class": "geonode.facets.providers.users.OwnerFacetProvider", "config": {"order": 8, "type": "select"}}, {"class": "geonode.facets.providers.thesaurus.ThesaurusFacetProvider", "config": {"type": "select"}}, ] + +DATASET_DOWNLOAD_HANDLER = os.getenv("DATASET_DOWNLOAD_HANDLER", "geonode.resource.download_handler.DownloadHandler") diff --git a/geonode/tests/smoke.py b/geonode/tests/smoke.py index 7ae4a37c1b9..bda90ec0e16 100644 --- a/geonode/tests/smoke.py +++ b/geonode/tests/smoke.py @@ -18,7 +18,10 @@ ######################################################################### from unittest import TestCase + +from django.http import HttpResponse from geonode.base.populate_test_data import create_single_dataset +from geonode.resource.download_handler import DownloadHandler from geonode.resource.utils import metadata_storers from geonode.tests.base import GeoNodeBaseTestSupport @@ -333,3 +336,21 @@ def dummy_metadata_storer2(dataset, custom): if custom.get("second-stage", None): for key, value in custom["second-stage"].items(): setattr(dataset, key, value) + + +class DummyDownloadManager(DownloadHandler): + def get_download_response(self): + return HttpResponse(content=b"abcsfd2") + + +class TestDownloadManager(GeoNodeBaseTestSupport): + def setUp(self): + self.sut = DownloadHandler + + @override_settings(DATASET_DOWNLOAD_HANDLER="geonode.tests.smoke.DummyDownloadManager") + def test_download_handler(self): + dataset = create_single_dataset("test_dataset") + url = reverse("dataset_download", args=[dataset.alternate]) + response = self.client.get(url) + self.assertTrue(response.status_code == 200) + self.assertEqual(response.content, b"abcsfd2") From 05b1b08af665e200efe4f8c49c6b78f33b1b1d33 Mon Sep 17 00:00:00 2001 From: Giovanni Allegri Date: Thu, 17 Aug 2023 11:51:33 +0200 Subject: [PATCH 096/111] [Fixes #11386] Configure Django apps (#11387) * Configure Django apps * fix typos * fix * removed deprectaed default_app_config * missing conifg for goenode openid provider app * formatting --- geonode/api/apps.py | 24 + geonode/catalogue/apps.py | 24 + geonode/catalogue/metadataxsl/apps.py | 24 + geonode/documents/exif/apps.py | 24 + geonode/favorite/apps.py | 24 + geonode/geoserver/createlayer/apps.py | 24 + geonode/geoserver/processing/apps.py | 24 + geonode/local_settings.py.geoserver.sample | 442 +++++++++--------- .../providers/geonode_openid_connect/apps.py | 24 + geonode/proxy/__init__.py | 2 +- geonode/proxy/apps.py | 24 + geonode/security/__init__.py | 2 +- geonode/security/apps.py | 24 + geonode/tasks/apps.py | 24 + 14 files changed, 476 insertions(+), 234 deletions(-) create mode 100644 geonode/api/apps.py create mode 100644 geonode/catalogue/apps.py create mode 100644 geonode/catalogue/metadataxsl/apps.py create mode 100644 geonode/documents/exif/apps.py create mode 100644 geonode/favorite/apps.py create mode 100644 geonode/geoserver/createlayer/apps.py create mode 100644 geonode/geoserver/processing/apps.py create mode 100644 geonode/people/socialaccount/providers/geonode_openid_connect/apps.py create mode 100644 geonode/proxy/apps.py create mode 100644 geonode/security/apps.py create mode 100644 geonode/tasks/apps.py diff --git a/geonode/api/apps.py b/geonode/api/apps.py new file mode 100644 index 00000000000..bf3d85231a9 --- /dev/null +++ b/geonode/api/apps.py @@ -0,0 +1,24 @@ +######################################################################### +# +# Copyright (C) 2023 OSGeo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### +from django.apps import AppConfig + + +class GeoNodeApiAppConfig(AppConfig): + name = "geonode.api" + verbose_name = "GeoNode API v1" diff --git a/geonode/catalogue/apps.py b/geonode/catalogue/apps.py new file mode 100644 index 00000000000..961648a7945 --- /dev/null +++ b/geonode/catalogue/apps.py @@ -0,0 +1,24 @@ +######################################################################### +# +# Copyright (C) 2023 OSGeo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### +from django.apps import AppConfig + + +class GeoNodeCatalogueAppConfig(AppConfig): + name = "geonode.catalogue" + verbose_name = "GeoNode CSW Catalogue" diff --git a/geonode/catalogue/metadataxsl/apps.py b/geonode/catalogue/metadataxsl/apps.py new file mode 100644 index 00000000000..af8e4d6001f --- /dev/null +++ b/geonode/catalogue/metadataxsl/apps.py @@ -0,0 +1,24 @@ +######################################################################### +# +# Copyright (C) 2023 OSGeo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### +from django.apps import AppConfig + + +class GeoNodeCatalogueMetadataxslAppConfig(AppConfig): + name = "geonode.catalogue.metadataxsl" + verbose_name = "GeoNode Catalogue metadataxsl" diff --git a/geonode/documents/exif/apps.py b/geonode/documents/exif/apps.py new file mode 100644 index 00000000000..eaeb3e2a031 --- /dev/null +++ b/geonode/documents/exif/apps.py @@ -0,0 +1,24 @@ +######################################################################### +# +# Copyright (C) 2023 OSGeo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### +from django.apps import AppConfig + + +class GeoNodeDocumentsExifAppConfig(AppConfig): + name = "geonode.documents.exif" + verbose_name = "GeoNode Documents Exif" diff --git a/geonode/favorite/apps.py b/geonode/favorite/apps.py new file mode 100644 index 00000000000..0b27c6eb7e3 --- /dev/null +++ b/geonode/favorite/apps.py @@ -0,0 +1,24 @@ +######################################################################### +# +# Copyright (C) 2023 OSGeo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### +from django.apps import AppConfig + + +class GeoNodeFavoriteAppConfig(AppConfig): + name = "geonode.favorite" + verbose_name = "GeoNode Favorite" diff --git a/geonode/geoserver/createlayer/apps.py b/geonode/geoserver/createlayer/apps.py new file mode 100644 index 00000000000..a3b9434b8a4 --- /dev/null +++ b/geonode/geoserver/createlayer/apps.py @@ -0,0 +1,24 @@ +######################################################################### +# +# Copyright (C) 2023 OSGeo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### +from django.apps import AppConfig + + +class GeoNodeGeoserverCreatelayerAppConfig(AppConfig): + name = "geonode.geoserver.createlayer" + verbose_name = "GeoNode Geoserver Createlayer" diff --git a/geonode/geoserver/processing/apps.py b/geonode/geoserver/processing/apps.py new file mode 100644 index 00000000000..8b5d10c06cb --- /dev/null +++ b/geonode/geoserver/processing/apps.py @@ -0,0 +1,24 @@ +######################################################################### +# +# Copyright (C) 2023 OSGeo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### +from django.apps import AppConfig + + +class GeoNodeGeoserverProcessingAppConfig(AppConfig): + name = "geonode.geoserver.processing" + verbose_name = "GeoNode Geoserver Processing" diff --git a/geonode/local_settings.py.geoserver.sample b/geonode/local_settings.py.geoserver.sample index 733589d6d36..18aa492c76f 100644 --- a/geonode/local_settings.py.geoserver.sample +++ b/geonode/local_settings.py.geoserver.sample @@ -26,6 +26,7 @@ import ast import os + try: # python2 from urlparse import urlparse, urlunparse, urlsplit, urljoin except ImportError: @@ -35,159 +36,129 @@ from geonode.settings import * PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__)) -MEDIA_ROOT = os.getenv('MEDIA_ROOT', os.path.join(PROJECT_ROOT, "uploaded")) +MEDIA_ROOT = os.getenv("MEDIA_ROOT", os.path.join(PROJECT_ROOT, "uploaded")) -STATIC_ROOT = os.getenv('STATIC_ROOT', - os.path.join(PROJECT_ROOT, "static_root") - ) +STATIC_ROOT = os.getenv("STATIC_ROOT", os.path.join(PROJECT_ROOT, "static_root")) -TIME_ZONE = 'UTC' +TIME_ZONE = "UTC" # Login and logout urls override -LOGIN_URL = os.getenv('LOGIN_URL', '{}account/login/'.format(SITEURL)) -LOGOUT_URL = os.getenv('LOGOUT_URL', '{}account/logout/'.format(SITEURL)) +LOGIN_URL = os.getenv("LOGIN_URL", "{}account/login/".format(SITEURL)) +LOGOUT_URL = os.getenv("LOGOUT_URL", "{}account/logout/".format(SITEURL)) -ACCOUNT_LOGIN_REDIRECT_URL = os.getenv('LOGIN_REDIRECT_URL', SITEURL) -ACCOUNT_LOGOUT_REDIRECT_URL = os.getenv('LOGOUT_REDIRECT_URL', SITEURL) +ACCOUNT_LOGIN_REDIRECT_URL = os.getenv("LOGIN_REDIRECT_URL", SITEURL) +ACCOUNT_LOGOUT_REDIRECT_URL = os.getenv("LOGOUT_REDIRECT_URL", SITEURL) -AVATAR_GRAVATAR_SSL = ast.literal_eval(os.getenv('AVATAR_GRAVATAR_SSL', 'True')) +AVATAR_GRAVATAR_SSL = ast.literal_eval(os.getenv("AVATAR_GRAVATAR_SSL", "True")) # Backend DATABASES = { - 'default': { - 'ENGINE': 'django.contrib.gis.db.backends.postgis', - 'NAME': 'geonode', - 'USER': 'geonode', - 'PASSWORD': 'geonode', - 'HOST': 'localhost', - 'PORT': '5432', - 'CONN_MAX_AGE': 0, - 'CONN_TOUT': 5, - 'OPTIONS': { - 'connect_timeout': 5, - } + "default": { + "ENGINE": "django.contrib.gis.db.backends.postgis", + "NAME": "geonode", + "USER": "geonode", + "PASSWORD": "geonode", + "HOST": "localhost", + "PORT": "5432", + "CONN_MAX_AGE": 0, + "CONN_TOUT": 5, + "OPTIONS": { + "connect_timeout": 5, + }, }, # vector datastore for uploads - 'datastore': { - 'ENGINE': 'django.contrib.gis.db.backends.postgis', + "datastore": { + "ENGINE": "django.contrib.gis.db.backends.postgis", # 'ENGINE': '', # Empty ENGINE name disables - 'NAME': 'geonode_data', - 'USER': 'geonode', - 'PASSWORD': 'geonode', - 'HOST': 'localhost', - 'PORT': '5432', - 'CONN_MAX_AGE': 0, - 'CONN_TOUT': 5, - 'OPTIONS': { - 'connect_timeout': 5, - } - } + "NAME": "geonode_data", + "USER": "geonode", + "PASSWORD": "geonode", + "HOST": "localhost", + "PORT": "5432", + "CONN_MAX_AGE": 0, + "CONN_TOUT": 5, + "OPTIONS": { + "connect_timeout": 5, + }, + }, } -GEOSERVER_LOCATION = os.getenv( - 'GEOSERVER_LOCATION', 'http://localhost:8080/geoserver/' -) +GEOSERVER_LOCATION = os.getenv("GEOSERVER_LOCATION", "http://localhost:8080/geoserver/") -GEOSERVER_PUBLIC_HOST = os.getenv( - 'GEOSERVER_PUBLIC_HOST', SITE_HOST_NAME -) +GEOSERVER_PUBLIC_HOST = os.getenv("GEOSERVER_PUBLIC_HOST", SITE_HOST_NAME) -GEOSERVER_PUBLIC_PORT = os.getenv( - 'GEOSERVER_PUBLIC_PORT', 8080 -) +GEOSERVER_PUBLIC_PORT = os.getenv("GEOSERVER_PUBLIC_PORT", 8080) -_default_public_location = 'http://{}:{}/geoserver/'.format(GEOSERVER_PUBLIC_HOST, GEOSERVER_PUBLIC_PORT) if GEOSERVER_PUBLIC_PORT else 'http://{}/geoserver/'.format(GEOSERVER_PUBLIC_HOST) - -GEOSERVER_WEB_UI_LOCATION = os.getenv( - 'GEOSERVER_WEB_UI_LOCATION', GEOSERVER_LOCATION +_default_public_location = ( + "http://{}:{}/geoserver/".format(GEOSERVER_PUBLIC_HOST, GEOSERVER_PUBLIC_PORT) + if GEOSERVER_PUBLIC_PORT + else "http://{}/geoserver/".format(GEOSERVER_PUBLIC_HOST) ) -GEOSERVER_PUBLIC_LOCATION = os.getenv( - 'GEOSERVER_PUBLIC_LOCATION', _default_public_location -) +GEOSERVER_WEB_UI_LOCATION = os.getenv("GEOSERVER_WEB_UI_LOCATION", GEOSERVER_LOCATION) -OGC_SERVER_DEFAULT_USER = os.getenv( - 'GEOSERVER_ADMIN_USER', 'admin' -) +GEOSERVER_PUBLIC_LOCATION = os.getenv("GEOSERVER_PUBLIC_LOCATION", _default_public_location) -OGC_SERVER_DEFAULT_PASSWORD = os.getenv( - 'GEOSERVER_ADMIN_PASSWORD', 'geoserver' -) +OGC_SERVER_DEFAULT_USER = os.getenv("GEOSERVER_ADMIN_USER", "admin") + +OGC_SERVER_DEFAULT_PASSWORD = os.getenv("GEOSERVER_ADMIN_PASSWORD", "geoserver") # OGC (WMS/WFS/WCS) Server Settings OGC_SERVER = { - 'default': { - 'BACKEND': 'geonode.geoserver', - 'LOCATION': GEOSERVER_LOCATION, - 'WEB_UI_LOCATION': GEOSERVER_WEB_UI_LOCATION, - 'LOGIN_ENDPOINT': 'j_spring_oauth2_geonode_login', - 'LOGOUT_ENDPOINT': 'j_spring_oauth2_geonode_logout', + "default": { + "BACKEND": "geonode.geoserver", + "LOCATION": GEOSERVER_LOCATION, + "WEB_UI_LOCATION": GEOSERVER_WEB_UI_LOCATION, + "LOGIN_ENDPOINT": "j_spring_oauth2_geonode_login", + "LOGOUT_ENDPOINT": "j_spring_oauth2_geonode_logout", # PUBLIC_LOCATION needs to be kept like this because in dev mode # the proxy won't work and the integration tests will fail # the entire block has to be overridden in the local_settings - 'PUBLIC_LOCATION': GEOSERVER_PUBLIC_LOCATION, - 'USER': OGC_SERVER_DEFAULT_USER, - 'PASSWORD': OGC_SERVER_DEFAULT_PASSWORD, - 'MAPFISH_PRINT_ENABLED': True, - 'PRINT_NG_ENABLED': True, - 'GEONODE_SECURITY_ENABLED': True, - 'GEOFENCE_SECURITY_ENABLED': True, - 'GEOFENCE_TIMEOUT': int(os.getenv('GEOFENCE_TIMEOUT', os.getenv('OGC_REQUEST_TIMEOUT', '60'))), - 'WMST_ENABLED': False, - 'BACKEND_WRITE_ENABLED': True, - 'WPS_ENABLED': False, - 'LOG_FILE': '%s/geoserver/data/logs/geoserver.log' % os.path.abspath(os.path.join(PROJECT_ROOT, os.pardir)), + "PUBLIC_LOCATION": GEOSERVER_PUBLIC_LOCATION, + "USER": OGC_SERVER_DEFAULT_USER, + "PASSWORD": OGC_SERVER_DEFAULT_PASSWORD, + "MAPFISH_PRINT_ENABLED": True, + "PRINT_NG_ENABLED": True, + "GEONODE_SECURITY_ENABLED": True, + "GEOFENCE_SECURITY_ENABLED": True, + "GEOFENCE_TIMEOUT": int(os.getenv("GEOFENCE_TIMEOUT", os.getenv("OGC_REQUEST_TIMEOUT", "60"))), + "WMST_ENABLED": False, + "BACKEND_WRITE_ENABLED": True, + "WPS_ENABLED": False, + "LOG_FILE": "%s/geoserver/data/logs/geoserver.log" % os.path.abspath(os.path.join(PROJECT_ROOT, os.pardir)), # Set to dictionary identifier of database containing spatial data in DATABASES dictionary to enable - 'DATASTORE': 'datastore', - 'TIMEOUT': int(os.getenv('OGC_REQUEST_TIMEOUT', '60')), - 'MAX_RETRIES': int(os.getenv('OGC_REQUEST_MAX_RETRIES', '5')), - 'BACKOFF_FACTOR': float(os.getenv('OGC_REQUEST_BACKOFF_FACTOR', '0.3')), - 'POOL_MAXSIZE': int(os.getenv('OGC_REQUEST_POOL_MAXSIZE', '10')), - 'POOL_CONNECTIONS': int(os.getenv('OGC_REQUEST_POOL_CONNECTIONS', '10')), + "DATASTORE": "datastore", + "TIMEOUT": int(os.getenv("OGC_REQUEST_TIMEOUT", "60")), + "MAX_RETRIES": int(os.getenv("OGC_REQUEST_MAX_RETRIES", "5")), + "BACKOFF_FACTOR": float(os.getenv("OGC_REQUEST_BACKOFF_FACTOR", "0.3")), + "POOL_MAXSIZE": int(os.getenv("OGC_REQUEST_POOL_MAXSIZE", "10")), + "POOL_CONNECTIONS": int(os.getenv("OGC_REQUEST_POOL_CONNECTIONS", "10")), } } # If you want to enable Mosaics use the following configuration UPLOADER = { - 'BACKEND': 'geonode.importer', - 'OPTIONS': { - 'TIME_ENABLED': True, - 'MOSAIC_ENABLED': False, + "BACKEND": "geonode.importer", + "OPTIONS": { + "TIME_ENABLED": True, + "MOSAIC_ENABLED": False, }, - 'SUPPORTED_CRS': [ - 'EPSG:4326', - 'EPSG:3785', - 'EPSG:3857', - 'EPSG:32647', - 'EPSG:32736' - ], - 'SUPPORTED_EXT': [ - '.shp', - '.csv', - '.kml', - '.kmz', - '.json', - '.geojson', - '.tif', - '.tiff', - '.geotiff', - '.gml', - '.xml' - ] + "SUPPORTED_CRS": ["EPSG:4326", "EPSG:3785", "EPSG:3857", "EPSG:32647", "EPSG:32736"], + "SUPPORTED_EXT": [".shp", ".csv", ".kml", ".kmz", ".json", ".geojson", ".tif", ".tiff", ".geotiff", ".gml", ".xml"], } # CSW settings CATALOGUE = { - 'default': { + "default": { # The underlying CSW implementation # default is pycsw in local mode (tied directly to GeoNode Django DB) - 'ENGINE': 'geonode.catalogue.backends.pycsw_local', + "ENGINE": "geonode.catalogue.backends.pycsw_local", # pycsw in non-local mode # 'ENGINE': 'geonode.catalogue.backends.pycsw_http', # deegree and others # 'ENGINE': 'geonode.catalogue.backends.generic', # The FULLY QUALIFIED base url to the CSW instance for this GeoNode - 'URL': urljoin(SITEURL, '/catalogue/csw'), + "URL": urljoin(SITEURL, "/catalogue/csw"), # 'URL': 'http://localhost:8080/deegree-csw-demo-3.0.4/services', # 'ALTERNATES_ONLY': True, } @@ -196,69 +167,67 @@ CATALOGUE = { # pycsw settings PYCSW = { # pycsw configuration - 'CONFIGURATION': { + "CONFIGURATION": { # uncomment / adjust to override server config system defaults # 'server': { # 'maxrecords': '10', # 'pretty_print': 'true', # 'federatedcatalogues': 'http://catalog.data.gov/csw' # }, - 'server': { - 'home': '.', - 'url': CATALOGUE['default']['URL'], - 'encoding': 'UTF-8', - 'language': LANGUAGE_CODE, - 'maxrecords': '20', - 'pretty_print': 'true', + "server": { + "home": ".", + "url": CATALOGUE["default"]["URL"], + "encoding": "UTF-8", + "language": LANGUAGE_CODE, + "maxrecords": "20", + "pretty_print": "true", # 'domainquerytype': 'range', - 'domaincounts': 'true', - 'profiles': 'apiso,ebrim', + "domaincounts": "true", + "profiles": "apiso,ebrim", }, - 'manager': { + "manager": { # authentication/authorization is handled by Django - 'transactions': 'false', - 'allowed_ips': '*', + "transactions": "false", + "allowed_ips": "*", # 'csw_harvest_pagesize': '10', }, - 'metadata:main': { - 'identification_title': 'GeoNode Catalogue', - 'identification_abstract': 'GeoNode is an open source platform' \ - ' that facilitates the creation, sharing, and collaborative use' \ - ' of geospatial data', - 'identification_keywords': 'sdi, catalogue, discovery, metadata,' \ - ' GeoNode', - 'identification_keywords_type': 'theme', - 'identification_fees': 'None', - 'identification_accessconstraints': 'None', - 'provider_name': 'Organization Name', - 'provider_url': SITEURL, - 'contact_name': 'Lastname, Firstname', - 'contact_position': 'Position Title', - 'contact_address': 'Mailing Address', - 'contact_city': 'City', - 'contact_stateorprovince': 'Administrative Area', - 'contact_postalcode': 'Zip or Postal Code', - 'contact_country': 'Country', - 'contact_phone': '+xx-xxx-xxx-xxxx', - 'contact_fax': '+xx-xxx-xxx-xxxx', - 'contact_email': 'Email Address', - 'contact_url': 'Contact URL', - 'contact_hours': 'Hours of Service', - 'contact_instructions': 'During hours of service. Off on ' \ - 'weekends.', - 'contact_role': 'pointOfContact', + "metadata:main": { + "identification_title": "GeoNode Catalogue", + "identification_abstract": "GeoNode is an open source platform" + " that facilitates the creation, sharing, and collaborative use" + " of geospatial data", + "identification_keywords": "sdi, catalogue, discovery, metadata," " GeoNode", + "identification_keywords_type": "theme", + "identification_fees": "None", + "identification_accessconstraints": "None", + "provider_name": "Organization Name", + "provider_url": SITEURL, + "contact_name": "Lastname, Firstname", + "contact_position": "Position Title", + "contact_address": "Mailing Address", + "contact_city": "City", + "contact_stateorprovince": "Administrative Area", + "contact_postalcode": "Zip or Postal Code", + "contact_country": "Country", + "contact_phone": "+xx-xxx-xxx-xxxx", + "contact_fax": "+xx-xxx-xxx-xxxx", + "contact_email": "Email Address", + "contact_url": "Contact URL", + "contact_hours": "Hours of Service", + "contact_instructions": "During hours of service. Off on " "weekends.", + "contact_role": "pointOfContact", + }, + "metadata:inspire": { + "enabled": "true", + "languages_supported": "eng,gre", + "default_language": "eng", + "date": "YYYY-MM-DD", + "gemet_keywords": "Utility and governmental services", + "conformity_service": "notEvaluated", + "contact_name": "Organization Name", + "contact_email": "Email Address", + "temp_extent": "YYYY-MM-DD/YYYY-MM-DD", }, - 'metadata:inspire': { - 'enabled': 'true', - 'languages_supported': 'eng,gre', - 'default_language': 'eng', - 'date': 'YYYY-MM-DD', - 'gemet_keywords': 'Utility and governmental services', - 'conformity_service': 'notEvaluated', - 'contact_name': 'Organization Name', - 'contact_email': 'Email Address', - 'temp_extent': 'YYYY-MM-DD/YYYY-MM-DD', - } } } @@ -268,50 +237,51 @@ PYCSW = { # default map projection # Note: If set to EPSG:4326, then only EPSG:4326 basemaps will work. -DEFAULT_MAP_CRS = os.environ.get('DEFAULT_MAP_CRS', "EPSG:3857") +DEFAULT_MAP_CRS = os.environ.get("DEFAULT_MAP_CRS", "EPSG:3857") -DEFAULT_LAYER_FORMAT = os.environ.get('DEFAULT_LAYER_FORMAT', "image/png") +DEFAULT_LAYER_FORMAT = os.environ.get("DEFAULT_LAYER_FORMAT", "image/png") # Where should newly created maps be focused? -DEFAULT_MAP_CENTER = (os.environ.get('DEFAULT_MAP_CENTER_X', 0), os.environ.get('DEFAULT_MAP_CENTER_Y', 0)) +DEFAULT_MAP_CENTER = (os.environ.get("DEFAULT_MAP_CENTER_X", 0), os.environ.get("DEFAULT_MAP_CENTER_Y", 0)) # How tightly zoomed should newly created maps be? # 0 = entire world; # maximum zoom is between 12 and 15 (for Google Maps, coverage varies by area) -DEFAULT_MAP_ZOOM = int(os.environ.get('DEFAULT_MAP_ZOOM', 3)) +DEFAULT_MAP_ZOOM = int(os.environ.get("DEFAULT_MAP_ZOOM", 3)) -MAPBOX_ACCESS_TOKEN = os.environ.get('MAPBOX_ACCESS_TOKEN', None) -BING_API_KEY = os.environ.get('BING_API_KEY', None) -GOOGLE_API_KEY = os.environ.get('GOOGLE_API_KEY', None) +MAPBOX_ACCESS_TOKEN = os.environ.get("MAPBOX_ACCESS_TOKEN", None) +BING_API_KEY = os.environ.get("BING_API_KEY", None) +GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY", None) -GEONODE_CLIENT_LAYER_PREVIEW_LIBRARY = os.getenv('GEONODE_CLIENT_LAYER_PREVIEW_LIBRARY', 'mapstore') +GEONODE_CLIENT_LAYER_PREVIEW_LIBRARY = os.getenv("GEONODE_CLIENT_LAYER_PREVIEW_LIBRARY", "mapstore") MAP_BASELAYERS = [{}] """ MapStore2 REACT based Client parameters """ -if GEONODE_CLIENT_LAYER_PREVIEW_LIBRARY == 'mapstore': - GEONODE_CLIENT_HOOKSET = os.getenv('GEONODE_CLIENT_HOOKSET', 'geonode_mapstore_client.hooksets.MapStoreHookSet') +if GEONODE_CLIENT_LAYER_PREVIEW_LIBRARY == "mapstore": + GEONODE_CLIENT_HOOKSET = os.getenv("GEONODE_CLIENT_HOOKSET", "geonode_mapstore_client.hooksets.MapStoreHookSet") - if 'geonode_mapstore_client' not in INSTALLED_APPS: + if "geonode_mapstore_client" not in INSTALLED_APPS: INSTALLED_APPS += ( - 'mapstore2_adapter', - 'geonode_mapstore_client',) + "mapstore2_adapter", + "geonode_mapstore_client", + ) def get_geonode_catalogue_service(): if PYCSW: pycsw_config = PYCSW["CONFIGURATION"] if pycsw_config: - pycsw_catalogue = { - ("%s" % pycsw_config['metadata:main']['identification_title']): { - "url": CATALOGUE['default']['URL'], - "type": "csw", - "title": pycsw_config['metadata:main']['identification_title'], - "autoload": True - } + pycsw_catalogue = { + ("%s" % pycsw_config["metadata:main"]["identification_title"]): { + "url": CATALOGUE["default"]["URL"], + "type": "csw", + "title": pycsw_config["metadata:main"]["identification_title"], + "autoload": True, } - return pycsw_catalogue + } + return pycsw_catalogue return None GEONODE_CATALOGUE_SERVICE = get_geonode_catalogue_service() @@ -321,7 +291,9 @@ if GEONODE_CLIENT_LAYER_PREVIEW_LIBRARY == 'mapstore': MAPSTORE_CATALOGUE_SELECTED_SERVICE = "" if GEONODE_CATALOGUE_SERVICE: - MAPSTORE_CATALOGUE_SERVICES[list(GEONODE_CATALOGUE_SERVICE.keys())[0]] = GEONODE_CATALOGUE_SERVICE[list(GEONODE_CATALOGUE_SERVICE.keys())[0]] + MAPSTORE_CATALOGUE_SERVICES[list(GEONODE_CATALOGUE_SERVICE.keys())[0]] = GEONODE_CATALOGUE_SERVICE[ + list(GEONODE_CATALOGUE_SERVICE.keys())[0] + ] MAPSTORE_CATALOGUE_SELECTED_SERVICE = list(GEONODE_CATALOGUE_SERVICE.keys())[0] DEFAULT_MS2_BACKGROUNDS = [ @@ -333,7 +305,7 @@ if GEONODE_CLIENT_LAYER_PREVIEW_LIBRARY == 'mapstore': "source": "Stamen", "group": "background", "thumbURL": "https://stamen-tiles-c.a.ssl.fastly.net/watercolor/0/0/0.jpg", - "visibility": False + "visibility": False, }, { "type": "tileprovider", @@ -343,7 +315,7 @@ if GEONODE_CLIENT_LAYER_PREVIEW_LIBRARY == 'mapstore': "source": "Stamen", "group": "background", "thumbURL": "https://stamen-tiles-d.a.ssl.fastly.net/terrain/0/0/0.png", - "visibility": False + "visibility": False, }, { "type": "tileprovider", @@ -353,7 +325,7 @@ if GEONODE_CLIENT_LAYER_PREVIEW_LIBRARY == 'mapstore': "source": "Stamen", "group": "background", "thumbURL": "https://stamen-tiles-d.a.ssl.fastly.net/toner/0/0/0.png", - "visibility": False + "visibility": False, }, { "type": "osm", @@ -361,7 +333,7 @@ if GEONODE_CLIENT_LAYER_PREVIEW_LIBRARY == 'mapstore': "name": "mapnik", "source": "osm", "group": "background", - "visibility": True + "visibility": True, }, { "type": "tileprovider", @@ -370,7 +342,7 @@ if GEONODE_CLIENT_LAYER_PREVIEW_LIBRARY == 'mapstore': "name": "OpenTopoMap", "source": "OpenTopoMap", "group": "background", - "visibility": False + "visibility": False, }, { "type": "wms", @@ -384,14 +356,14 @@ if GEONODE_CLIENT_LAYER_PREVIEW_LIBRARY == 'mapstore': "https://maps3.geosolutionsgroup.com/geoserver/wms", "https://maps4.geosolutionsgroup.com/geoserver/wms", "https://maps5.geosolutionsgroup.com/geoserver/wms", - "https://maps6.geosolutionsgroup.com/geoserver/wms" + "https://maps6.geosolutionsgroup.com/geoserver/wms", ], "group": "background", "thumbURL": f"{SITEURL}static/mapstorestyle/img/s2cloudless-s2cloudless.png", "visibility": False, "credits": { - "title": "Sentinel-2 cloudless 2016 by EOX IT Services GmbH" - } + "title": 'Sentinel-2 cloudless 2016 by EOX IT Services GmbH' + }, }, { "source": "ol", @@ -401,8 +373,8 @@ if GEONODE_CLIENT_LAYER_PREVIEW_LIBRARY == 'mapstore': "title": "Empty Background", "type": "empty", "visibility": False, - "args": ["Empty Background", {"visibility": False}] - } + "args": ["Empty Background", {"visibility": False}], + }, ] if MAPBOX_ACCESS_TOKEN: @@ -413,11 +385,14 @@ if GEONODE_CLIENT_LAYER_PREVIEW_LIBRARY == 'mapstore': "name": "MapBox streets-v11", "accessToken": "%s" % MAPBOX_ACCESS_TOKEN, "source": "streets-v11", - "thumbURL": "https://api.mapbox.com/styles/v1/mapbox/streets-v11/tiles/256/6/33/23?access_token=%s" % MAPBOX_ACCESS_TOKEN, + "thumbURL": "https://api.mapbox.com/styles/v1/mapbox/streets-v11/tiles/256/6/33/23?access_token=%s" + % MAPBOX_ACCESS_TOKEN, "group": "background", - "visibility": True + "visibility": True, } - DEFAULT_MS2_BACKGROUNDS = [MAPBOX_BASEMAPS,] + DEFAULT_MS2_BACKGROUNDS + DEFAULT_MS2_BACKGROUNDS = [ + MAPBOX_BASEMAPS, + ] + DEFAULT_MS2_BACKGROUNDS if BING_API_KEY: BING_BASEMAPS = [ @@ -428,7 +403,7 @@ if GEONODE_CLIENT_LAYER_PREVIEW_LIBRARY == 'mapstore': "source": "bing", "group": "background", "apiKey": "{{apiKey}}", - "visibility": True + "visibility": True, }, { "type": "bing", @@ -438,7 +413,7 @@ if GEONODE_CLIENT_LAYER_PREVIEW_LIBRARY == 'mapstore': "group": "background", "apiKey": "{{apiKey}}", "thumbURL": "%sstatic/mapstorestyle/img/bing_road_on_demand.png" % SITEURL, - "visibility": False + "visibility": False, }, { "type": "bing", @@ -448,7 +423,7 @@ if GEONODE_CLIENT_LAYER_PREVIEW_LIBRARY == 'mapstore': "group": "background", "apiKey": "{{apiKey}}", "thumbURL": "%sstatic/mapstorestyle/img/bing_aerial_w_labels.png" % SITEURL, - "visibility": False + "visibility": False, }, { "type": "bing", @@ -458,64 +433,67 @@ if GEONODE_CLIENT_LAYER_PREVIEW_LIBRARY == 'mapstore': "group": "background", "apiKey": "{{apiKey}}", "thumbURL": "%sstatic/mapstorestyle/img/bing_canvas_dark.png" % SITEURL, - "visibility": False - } + "visibility": False, + }, ] - DEFAULT_MS2_BACKGROUNDS = [BING_BASEMAPS, ] + DEFAULT_MS2_BACKGROUNDS + DEFAULT_MS2_BACKGROUNDS = [ + BING_BASEMAPS, + ] + DEFAULT_MS2_BACKGROUNDS MAPSTORE_BASELAYERS = DEFAULT_MS2_BACKGROUNDS # MAPSTORE_BASELAYERS_SOURCES allow to configure tilematrix sets for wmts layers - MAPSTORE_BASELAYERS_SOURCES = os.environ.get('MAPSTORE_BASELAYERS_SOURCES', {}) + MAPSTORE_BASELAYERS_SOURCES = os.environ.get("MAPSTORE_BASELAYERS_SOURCES", {}) # -- END Client Hooksets Setup LOGGING = { - 'version': 1, - 'disable_existing_loggers': True, - 'formatters': { - 'verbose': { - 'format': '%(levelname)s %(asctime)s %(module)s %(process)d ' - '%(thread)d %(message)s' - }, - 'simple': { - 'format': '%(message)s', + "version": 1, + "disable_existing_loggers": True, + "formatters": { + "verbose": {"format": "%(levelname)s %(asctime)s %(module)s %(process)d " "%(thread)d %(message)s"}, + "simple": { + "format": "%(message)s", }, }, - 'filters': { - 'require_debug_false': { - '()': 'django.utils.log.RequireDebugFalse' - } - }, - 'handlers': { - 'console': { - 'level': 'INFO', - 'class': 'logging.StreamHandler', - 'formatter': 'simple' + "filters": {"require_debug_false": {"()": "django.utils.log.RequireDebugFalse"}}, + "handlers": { + "console": {"level": "INFO", "class": "logging.StreamHandler", "formatter": "simple"}, + "mail_admins": { + "level": "ERROR", + "filters": ["require_debug_false"], + "class": "django.utils.log.AdminEmailHandler", }, - 'mail_admins': { - 'level': 'ERROR', - 'filters': ['require_debug_false'], - 'class': 'django.utils.log.AdminEmailHandler', - } }, "loggers": { "django": { - "handlers": ["console"], "level": "ERROR", }, + "handlers": ["console"], + "level": "ERROR", + }, "geonode": { - "handlers": ["console"], "level": "INFO", }, + "handlers": ["console"], + "level": "INFO", + }, "geoserver-restconfig.catalog": { - "handlers": ["console"], "level": "ERROR", }, + "handlers": ["console"], + "level": "ERROR", + }, "owslib": { - "handlers": ["console"], "level": "ERROR", }, + "handlers": ["console"], + "level": "ERROR", + }, "pycsw": { - "handlers": ["console"], "level": "INFO", }, + "handlers": ["console"], + "level": "INFO", + }, "celery": { - 'handlers': ["console"], 'level': 'ERROR', }, + "handlers": ["console"], + "level": "ERROR", + }, }, } # Additional settings -X_FRAME_OPTIONS = 'ALLOW-FROM %s' % SITEURL +X_FRAME_OPTIONS = "ALLOW-FROM %s" % SITEURL CORS_ALLOW_ALL_ORIGINS = True GEOIP_PATH = "/usr/local/share/GeoIP" diff --git a/geonode/people/socialaccount/providers/geonode_openid_connect/apps.py b/geonode/people/socialaccount/providers/geonode_openid_connect/apps.py new file mode 100644 index 00000000000..ce9d3da1ad3 --- /dev/null +++ b/geonode/people/socialaccount/providers/geonode_openid_connect/apps.py @@ -0,0 +1,24 @@ +######################################################################### +# +# Copyright (C) 2023 OSGeo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### +from django.apps import AppConfig + + +class GeoNodeOpenIdConnectAppConfig(AppConfig): + name = "geonode.people.socialaccount.providers.geonode_openid_connect" + verbose_name = "GeoNode OpenId Connect" diff --git a/geonode/proxy/__init__.py b/geonode/proxy/__init__.py index 79177e00bdd..da86ef5219a 100644 --- a/geonode/proxy/__init__.py +++ b/geonode/proxy/__init__.py @@ -1,6 +1,6 @@ ######################################################################### # -# Copyright (C) 2016 OSGeo +# Copyright (C) 2023 OSGeo # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/geonode/proxy/apps.py b/geonode/proxy/apps.py new file mode 100644 index 00000000000..9901d333798 --- /dev/null +++ b/geonode/proxy/apps.py @@ -0,0 +1,24 @@ +######################################################################### +# +# Copyright (C) 2023 OSGeo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### +from django.apps import AppConfig + + +class GeoNodeProxyAppConfig(AppConfig): + name = "geonode.proxy" + verbose_name = "GeoNode Proxy" diff --git a/geonode/security/__init__.py b/geonode/security/__init__.py index 79177e00bdd..da86ef5219a 100644 --- a/geonode/security/__init__.py +++ b/geonode/security/__init__.py @@ -1,6 +1,6 @@ ######################################################################### # -# Copyright (C) 2016 OSGeo +# Copyright (C) 2023 OSGeo # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/geonode/security/apps.py b/geonode/security/apps.py new file mode 100644 index 00000000000..3475f4d05f1 --- /dev/null +++ b/geonode/security/apps.py @@ -0,0 +1,24 @@ +######################################################################### +# +# Copyright (C) 2023 OSGeo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### +from django.apps import AppConfig + + +class GeoNodeSecurityAppConfig(AppConfig): + name = "geonode.security" + verbose_name = "GeoNode Security" diff --git a/geonode/tasks/apps.py b/geonode/tasks/apps.py new file mode 100644 index 00000000000..9b49856a418 --- /dev/null +++ b/geonode/tasks/apps.py @@ -0,0 +1,24 @@ +######################################################################### +# +# Copyright (C) 2023 OSGeo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### +from django.apps import AppConfig + + +class GeoNodeTasksAppConfig(AppConfig): + name = "geonode.tasks" + verbose_name = "GeoNode Tasks" From d72cd4f07de4a5912517f72a27a09074432c44d8 Mon Sep 17 00:00:00 2001 From: Giovanni Allegri Date: Thu, 17 Aug 2023 12:33:25 +0200 Subject: [PATCH 097/111] [Fixes #11383] Remove explicit dependency from wandb (#11382) * Remove explicit dependency from wandb * removed wandb from setup.cfg --- requirements.txt | 1 - setup.cfg | 1 - 2 files changed, 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 10805de234a..4c40148f408 100644 --- a/requirements.txt +++ b/requirements.txt @@ -171,7 +171,6 @@ webdriver_manager==4.0.0 # Security and audit mistune==3.0.1 -wandb==0.15.7 protobuf==3.20.3 mako==1.2.4 certifi>=2022.12.7 # not directly required, pinned by Snyk to avoid a vulnerability diff --git a/setup.cfg b/setup.cfg index 04f081e7ac5..516ea95be42 100644 --- a/setup.cfg +++ b/setup.cfg @@ -196,7 +196,6 @@ install_requires = # Security and audit mistune==3.0.1 - wandb==0.15.7 protobuf==3.20.3 mako==1.2.4 certifi>=2022.12.7 # not directly required, pinned by Snyk to avoid a vulnerability From 0f874707946b0a0dfe6df47a272b8cffc593f602 Mon Sep 17 00:00:00 2001 From: Giovanni Allegri Date: Thu, 17 Aug 2023 13:06:02 +0200 Subject: [PATCH 098/111] Enable IFC format (#11395) --- geonode/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/geonode/settings.py b/geonode/settings.py index 72f37a84b6f..73e6fea14da 100644 --- a/geonode/settings.py +++ b/geonode/settings.py @@ -652,6 +652,7 @@ "glb", "pcd", "gltf", + "ifc", ] if os.getenv("ALLOWED_DOCUMENT_TYPES") is None else re.split(r" *[,|:;] *", os.getenv("ALLOWED_DOCUMENT_TYPES")) From d95db9f511849c7369fc19f88d0af4cc488be6df Mon Sep 17 00:00:00 2001 From: Giovanni Allegri Date: Fri, 18 Aug 2023 12:12:32 +0200 Subject: [PATCH 099/111] [Fixes #11347] Import resources page displays service name instead of title (#11385) * Resolve issue 11347 by showing more informative and readable service title instead of name * fix: #11347 by giving better description of service * small improvements --------- Co-authored-by: sahilsekr42 Co-authored-by: Alessio Fabiani --- geonode/services/templates/services/service_detail.html | 2 +- geonode/services/templates/services/service_remove.html | 4 ++-- .../templates/services/service_resources_harvest.html | 4 +--- geonode/services/views.py | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/geonode/services/templates/services/service_detail.html b/geonode/services/templates/services/service_detail.html index beb8ac84b12..08c1ef70042 100644 --- a/geonode/services/templates/services/service_detail.html +++ b/geonode/services/templates/services/service_detail.html @@ -4,7 +4,7 @@ {% block body %}
    -

    {{service.title|default:service.name}}

    +

    {{service.title|default:service.name}}

    {% trans "Type" %}: {{service.service_type}}

    {% trans "URL" %}: {{service.base_url}}

    {% trans "Abstract" %}: {{service.abstract}}

    diff --git a/geonode/services/templates/services/service_remove.html b/geonode/services/templates/services/service_remove.html index 90a2f8fa9e9..3ce551b58b6 100644 --- a/geonode/services/templates/services/service_remove.html +++ b/geonode/services/templates/services/service_remove.html @@ -1,7 +1,7 @@ {% extends "services/services_base.html" %} {% load i18n %} -{% block title %} {{ service.name }} - {{ block.super }} {% endblock %} +{% block title %} {{service.title|default:service.name}} - {{ block.super }} {% endblock %} {% block body %}
    -

    {% trans "Are you sure you want to remove" %} {{ service.name }}?

    +

    {% trans "Are you sure you want to remove" %} {{service.title|default:service.name}}?

    {% csrf_token %} diff --git a/geonode/services/templates/services/service_resources_harvest.html b/geonode/services/templates/services/service_resources_harvest.html index 4541223354e..8de1f454f43 100644 --- a/geonode/services/templates/services/service_resources_harvest.html +++ b/geonode/services/templates/services/service_resources_harvest.html @@ -8,9 +8,7 @@ {% block body %}
    {% if resources %} diff --git a/geonode/services/views.py b/geonode/services/views.py index 12edf78e71a..dc67c212c67 100644 --- a/geonode/services/views.py +++ b/geonode/services/views.py @@ -352,5 +352,5 @@ def remove_service(request, service_id): elif request.method == "POST": service.dataset_set.all().delete() service.delete() - messages.add_message(request, messages.INFO, _(f"Service {service.name} has been deleted")) + messages.add_message(request, messages.INFO, _(f"Service {service.title} has been deleted")) return HttpResponseRedirect(reverse("services")) From dc6268c11643d713884d4f43132061b97bd68d0c Mon Sep 17 00:00:00 2001 From: Alessio Fabiani Date: Tue, 22 Aug 2023 19:07:26 +0200 Subject: [PATCH 100/111] Align to GeoNode master (4.2.0) and improve/fix docker initialization (#11308) * - Externalizing "UID" filed from the "settings.py" * - Pep8 issues * Align to GeoNode master (4.2.0) and improve/fix docker initialization * - Update test docker-docker-compose-test * Update .env_test test file * Bump to 4.2.0dev0 * Update .circleci config.yml file * Increase the Django healthcheck timeout time * Typo on GEOSERVER_JAVA_OPTS * Update docker-compose-test django healthcheck with retries * Update docker-compose-test django healthcheck with retries * Fix test cases * - Removing the fixed "IS_MANAGER" field in favor of a pluggable generic "OpenIDGroupRoleMapper" class * - Fix docker compose structure and .env.sample labels * Configure Django apps * fix typos * fix * - Update docker-compose-test.yml image names * - Black formatting issues * removed Azure ID env vars * dropped PUBLIC_PORT * gitignore .env file * disable abbreviated params * renamed OGC credential vars and introduced OGC_SERVER_FACTORY_PASSWORD * removed useless OGC_ADMIN_* vars * renamved to GEOSERVER_FACTORY_PASSWORD * use django-admin instead of django-admin.py * fixed monitoring fixtures * fixes blowfish deprecation warning * fix to update method * break loop if succeeding * fix formmatting * - Alingning docker-compose-dev to docker-compose * set nginx image version * remove wrong folder pushed by error * set nginx image version in all compose files * Improvements to create-envfile documentation * move location of Geoserver LB host variables * Remove geoserver_ui variable from .env.sample --------- Co-authored-by: afabiani Co-authored-by: Giovanni Allegri --- .circleci/config.yml | 24 +- .env => .env.sample | 93 ++++---- .env_dev | 49 ++-- .env_local | 47 ++-- .env_test | 53 +++-- .gitignore | 2 + Dockerfile | 9 +- README.md | 39 +++- create-envfile.py | 185 +++++++++++++++ docker-compose-dev.yml | 62 +++--- docker-compose-test.yml | 60 +++-- docker-compose.yml | 38 ++-- entrypoint.sh | 4 +- geonode/api/__init__.py | 1 + geonode/base/api/tests.py | 8 +- geonode/catalogue/__init__.py | 1 + geonode/catalogue/metadataxsl/__init__.py | 1 + geonode/celery_app.py | 4 +- geonode/documents/exif/__init__.py | 1 + geonode/favorite/__init__.py | 1 + geonode/geoserver/createlayer/__init__.py | 1 + geonode/geoserver/processing/__init__.py | 1 + geonode/local_settings.py.geoserver.sample | 8 +- geonode/people/adapters.py | 33 ++- geonode/people/profileextractors.py | 20 +- .../geonode_openid_connect/provider.py | 6 +- .../providers/geonode_openid_connect/tests.py | 1 - geonode/proxy/__init__.py | 1 + geonode/security/__init__.py | 1 + geonode/settings.py | 35 +-- geonode/tasks/__init__.py | 1 + geonode/upload/tests/test_settings.py | 10 +- geonode/utils.py | 4 +- package/support/geonode.local_settings | 12 +- pavement.py | 4 +- requirements.txt | 1 + scripts/docker/nginx/Dockerfile | 5 +- scripts/docker/nginx/docker-entrypoint.sh | 23 +- scripts/docker/nginx/geonode.conf.envsubst | 90 +++----- scripts/docker/nginx/nginx.conf.envsubst | 2 +- tasks.py | 210 ++++++++---------- uwsgi.ini | 14 +- 42 files changed, 716 insertions(+), 449 deletions(-) rename .env => .env.sample (73%) create mode 100644 create-envfile.py diff --git a/.circleci/config.yml b/.circleci/config.yml index 60cfd1c921e..9babc1a6978 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -23,12 +23,12 @@ jobs: - run: name: Build the stack - command: docker-compose -f docker-compose-test.yml build --no-cache + command: docker-compose --env-file .env_test -f docker-compose-test.yml build --no-cache working_directory: ./ - run: name: Start the stack - command: docker-compose -f docker-compose-test.yml up -d + command: docker-compose --env-file .env_test -f docker-compose-test.yml up -d working_directory: ./ - run: @@ -78,21 +78,21 @@ jobs: - run: name: Run test suite command: | - docker-compose -f docker-compose-test.yml exec db psql -U postgres -c 'SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE pid <> pg_backend_pid();' - docker-compose -f docker-compose-test.yml exec db createdb -U postgres -T postgres test_postgres - docker-compose -f docker-compose-test.yml exec db createdb -U postgres -T postgres test_geonode - docker-compose -f docker-compose-test.yml exec db createdb -U postgres -T postgres test_geonode_data - docker-compose -f docker-compose-test.yml exec db psql -U postgres -d test_geonode -c 'CREATE EXTENSION IF NOT EXISTS postgis;' - docker-compose -f docker-compose-test.yml exec db psql -U postgres -d test_geonode_data -c 'CREATE EXTENSION IF NOT EXISTS postgis;' - docker-compose -f docker-compose-test.yml exec django bash -c '<>' + docker-compose --env-file .env_test -f docker-compose-test.yml exec db psql -U postgres -c 'SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE pid <> pg_backend_pid();' + docker-compose --env-file .env_test -f docker-compose-test.yml exec db createdb -U postgres -T postgres test_postgres + docker-compose --env-file .env_test -f docker-compose-test.yml exec db createdb -U postgres -T postgres test_geonode + docker-compose --env-file .env_test -f docker-compose-test.yml exec db createdb -U postgres -T postgres test_geonode_data + docker-compose --env-file .env_test -f docker-compose-test.yml exec db psql -U postgres -d test_geonode -c 'CREATE EXTENSION IF NOT EXISTS postgis;' + docker-compose --env-file .env_test -f docker-compose-test.yml exec db psql -U postgres -d test_geonode_data -c 'CREATE EXTENSION IF NOT EXISTS postgis;' + docker-compose --env-file .env_test -f docker-compose-test.yml exec django bash -c '<>' working_directory: ./ no_output_timeout: 10m - run: name: Run code quality checks command: | - docker-compose -f docker-compose-test.yml exec django bash -c 'black --check geonode' - docker-compose -f docker-compose-test.yml exec django bash -c 'flake8 geonode' - docker-compose -f docker-compose-test.yml exec django bash -c 'codecov; bash <(curl -s https://codecov.io/bash) -t 2c0e7780-1640-45f0-93a3-e103b057d8c8' + docker-compose --env-file .env_test -f docker-compose-test.yml exec django bash -c 'black --check geonode' + docker-compose --env-file .env_test -f docker-compose-test.yml exec django bash -c 'flake8 geonode' + docker-compose --env-file .env_test -f docker-compose-test.yml exec django bash -c 'codecov; bash <(curl -s https://codecov.io/bash) -t 2c0e7780-1640-45f0-93a3-e103b057d8c8' working_directory: ./ workflows: diff --git a/.env b/.env.sample similarity index 73% rename from .env rename to .env.sample index 492278acb55..b74e48d371c 100644 --- a/.env +++ b/.env.sample @@ -1,6 +1,4 @@ COMPOSE_PROJECT_NAME=geonode -DOCKERHOST= -DOCKER_HOST_IP= # See https://github.com/containers/podman/issues/13889 # DOCKER_BUILDKIT=0 DOCKER_ENV=production @@ -13,33 +11,29 @@ C_FORCE_ROOT=1 FORCE_REINIT=false INVOKE_LOG_STDOUT=true -# LANGUAGE_CODE=pt +# LANGUAGE_CODE=it-it # LANGUAGES=(('en-us','English'),('it-it','Italiano')) DJANGO_SETTINGS_MODULE=geonode.settings GEONODE_INSTANCE_NAME=geonode -GEONODE_LB_HOST_IP= -GEONODE_LB_PORT= -PUBLIC_PORT=80 -NGINX_BASE_URL= # ################# # backend # ################# POSTGRES_USER=postgres -POSTGRES_PASSWORD=postgres +POSTGRES_PASSWORD={pgpwd} GEONODE_DATABASE=geonode GEONODE_DATABASE_USER=geonode -GEONODE_DATABASE_PASSWORD=geonode +GEONODE_DATABASE_PASSWORD={dbpwd} GEONODE_GEODATABASE=geonode_data GEONODE_GEODATABASE_USER=geonode_data -GEONODE_GEODATABASE_PASSWORD=geonode_data +GEONODE_GEODATABASE_PASSWORD={geodbpwd} GEONODE_DATABASE_SCHEMA=public GEONODE_GEODATABASE_SCHEMA=public DATABASE_HOST=db DATABASE_PORT=5432 -DATABASE_URL=postgis://geonode:geonode@db:5432/geonode -GEODATABASE_URL=postgis://geonode_data:geonode_data@db:5432/geonode_data +DATABASE_URL=postgis://geonode:{dbpwd}@db:5432/geonode +GEODATABASE_URL=postgis://geonode_data:{geodbpwd}@db:5432/geonode_data GEONODE_DB_CONN_MAX_AGE=0 GEONODE_DB_CONN_TOUT=5 DEFAULT_BACKEND_DATASTORE=datastore @@ -47,9 +41,9 @@ BROKER_URL=amqp://guest:guest@rabbitmq:5672/ CELERY_BEAT_SCHEDULER=celery.beat:PersistentScheduler ASYNC_SIGNALS=True -SITEURL=https://localhost/ +SITEURL={siteurl}/ -ALLOWED_HOSTS=['django', '*'] +ALLOWED_HOSTS="['django', '{hostname}']" # Data Uploader DEFAULT_BACKEND_UPLOADER=geonode.importer @@ -64,13 +58,14 @@ HAYSTACK_SEARCH_RESULTS_PER_PAGE=200 # nginx # HTTPD Server # ################# -GEONODE_LB_HOST_IP=localhost -GEONODE_LB_PORT=80 +GEONODE_LB_HOST_IP=django +GEONODE_LB_PORT=8000 +NGINX_BASE_URL={siteurl} # IP or domain name and port where the server can be reached on HTTPS (leave HOST empty if you want to use HTTP only) # port where the server can be reached on HTTPS -HTTP_HOST= -HTTPS_HOST=localhost +HTTP_HOST={http_host} +HTTPS_HOST={https_host} HTTP_PORT=80 HTTPS_PORT=443 @@ -80,8 +75,8 @@ HTTPS_PORT=443 # disabled : we do not get a certificate at all (a placeholder certificate will be used) # staging : we get staging certificates (are invalid, but allow to test the process completely and have much higher limit rates) # production : we get a normal certificate (default) -# LETSENCRYPT_MODE=disabled -LETSENCRYPT_MODE=staging +LETSENCRYPT_MODE={letsencrypt_mode} +# LETSENCRYPT_MODE=staging # LETSENCRYPT_MODE=production RESOLVER=127.0.0.11 @@ -89,11 +84,13 @@ RESOLVER=127.0.0.11 # ################# # geoserver # ################# -GEOSERVER_WEB_UI_LOCATION=https://localhost/geoserver/ -GEOSERVER_PUBLIC_LOCATION=https://localhost/geoserver/ -GEOSERVER_LOCATION=http://geoserver:8080/geoserver/ +GEOSERVER_LB_HOST_IP=geoserver +GEOSERVER_LB_PORT=8080 +GEOSERVER_WEB_UI_LOCATION={siteurl}/geoserver/ +GEOSERVER_PUBLIC_LOCATION={siteurl}/geoserver/ +GEOSERVER_LOCATION=http://${GEOSERVER_LB_HOST_IP}:${GEOSERVER_LB_PORT}/geoserver/ GEOSERVER_ADMIN_USER=admin -GEOSERVER_ADMIN_PASSWORD=geoserver +GEOSERVER_ADMIN_PASSWORD={geoserverpwd} OGC_REQUEST_TIMEOUT=30 OGC_REQUEST_MAX_RETRIES=1 @@ -104,7 +101,7 @@ OGC_REQUEST_POOL_CONNECTIONS=10 # Java Options & Memory ENABLE_JSONP=true outFormat=text/javascript -GEOSERVER_JAVA_OPTS="-Djava.awt.headless=true -Xms2G -Xmx4G -Dgwc.context.suffix=gwc -XX:+UnlockDiagnosticVMOptions -XX:+LogVMOutput -XX:LogFile=/var/log/jvm.log -XX:PerfDataSamplingInterval=500 -XX:SoftRefLRUPolicyMSPerMB=36000 -XX:-UseGCOverheadLimit -XX:+UseConcMarkSweepGC -XX:ParallelGCThreads=4 -Dfile.encoding=UTF8 -Djavax.servlet.request.encoding=UTF-8 -Djavax.servlet.response.encoding=UTF-8 -Duser.timezone=GMT -Dorg.geotools.shapefile.datetime=false -DGS-SHAPEFILE-CHARSET=UTF-8 -DGEOSERVER_CSRF_DISABLED=true -DPRINT_BASE_URL=http://geoserver:8080/geoserver/pdf -DALLOW_ENV_PARAMETRIZATION=true -Xbootclasspath/a:/usr/local/tomcat/webapps/geoserver/WEB-INF/lib/marlin-0.9.3-Unsafe.jar -Dsun.java2d.renderer=org.marlin.pisces.MarlinRenderingEngine" +GEOSERVER_JAVA_OPTS='-Djava.awt.headless=true -Xms4G -Xmx4G -Dgwc.context.suffix=gwc -XX:+UnlockDiagnosticVMOptions -XX:+LogVMOutput -XX:LogFile=/var/log/jvm.log -XX:PerfDataSamplingInterval=500 -XX:SoftRefLRUPolicyMSPerMB=36000 -XX:-UseGCOverheadLimit -XX:ParallelGCThreads=4 -Dfile.encoding=UTF8 -Djavax.servlet.request.encoding=UTF-8 -Djavax.servlet.response.encoding=UTF-8 -Duser.timezone=GMT -Dorg.geotools.shapefile.datetime=false -DGS-SHAPEFILE-CHARSET=UTF-8 -DGEOSERVER_CSRF_DISABLED=true -DPRINT_BASE_URL={geoserver_ui}/geoserver/pdf -DALLOW_ENV_PARAMETRIZATION=true -Xbootclasspath/a:/usr/local/tomcat/webapps/geoserver/WEB-INF/lib/marlin-0.9.3-Unsafe.jar -Dsun.java2d.renderer=org.marlin.pisces.MarlinRenderingEngine' # ################# # Security @@ -117,8 +114,8 @@ GEOSERVER_JAVA_OPTS="-Djava.awt.headless=true -Xms2G -Xmx4G -Dgwc.context.suffix # in DB will honored. ADMIN_USERNAME=admin -ADMIN_PASSWORD=admin -ADMIN_EMAIL=admin@localhost +ADMIN_PASSWORD={geonodepwd} +ADMIN_EMAIL={email} # EMAIL Notifications EMAIL_ENABLE=False @@ -129,29 +126,36 @@ DJANGO_EMAIL_HOST_USER= DJANGO_EMAIL_HOST_PASSWORD= DJANGO_EMAIL_USE_TLS=False DJANGO_EMAIL_USE_SSL=False -DEFAULT_FROM_EMAIL='GeoNode ' +DEFAULT_FROM_EMAIL='{email}' # eg Company # Session/Access Control LOCKDOWN_GEONODE=False -CORS_ALLOW_ALL_ORIGINS=True X_FRAME_OPTIONS="SAMEORIGIN" SESSION_EXPIRED_CONTROL_ENABLED=True DEFAULT_ANONYMOUS_VIEW_PERMISSION=True DEFAULT_ANONYMOUS_DOWNLOAD_PERMISSION=True +CORS_ALLOW_ALL_ORIGINS=True +GEOSERVER_CORS_ENABLED=True +GEOSERVER_CORS_ALLOWED_ORIGINS=* +GEOSERVER_CORS_ALLOWED_METHODS=GET,POST,PUT,DELETE,HEAD,OPTIONS +GEOSERVER_CORS_ALLOWED_HEADERS=* + # Users Registration ACCOUNT_OPEN_SIGNUP=True ACCOUNT_EMAIL_REQUIRED=True ACCOUNT_APPROVAL_REQUIRED=False ACCOUNT_CONFIRM_EMAIL_ON_GET=False ACCOUNT_EMAIL_VERIFICATION=none +ACCOUNT_EMAIL_CONFIRMATION_EMAIL=False +ACCOUNT_EMAIL_CONFIRMATION_REQUIRED=False ACCOUNT_AUTHENTICATION_METHOD=username_email AUTO_ASSIGN_REGISTERED_MEMBERS_TO_REGISTERED_MEMBERS_GROUP_NAME=True # OAuth2 OAUTH2_API_KEY= -OAUTH2_CLIENT_ID=Jrchz2oPY3akmzndmgUTYrs9gczlgoV20YPSvqaV -OAUTH2_CLIENT_SECRET=rCnp5txobUo83EpQEblM8fVj3QT5zb5qRfxNsuPzCqZaiRyIoxM4jdgMiZKFfePBHYXCLd7B8NlkfDBY9HKeIQPcy5Cp08KQNpRHQbjpLItDHv12GvkSeXp6OxaUETv3 +OAUTH2_CLIENT_ID={clientid} +OAUTH2_CLIENT_SECRET={clientsecret} # GeoNode APIs API_LOCKDOWN=False @@ -161,9 +165,9 @@ TASTYPIE_APIKEY= # Production and # Monitoring # ################# -DEBUG=False +DEBUG={debug} -SECRET_KEY='myv-y4#7j-d*p-__@j#*3z@!y24fz8%^z2v6atuy4bo9vqr1_a' +SECRET_KEY='{secret_key}' STATIC_ROOT=/mnt/volumes/statics/static/ MEDIA_ROOT=/mnt/volumes/statics/uploaded/ @@ -177,7 +181,7 @@ MEMCACHED_LOCATION=127.0.0.1:11211 MEMCACHED_LOCK_EXPIRE=3600 MEMCACHED_LOCK_TIMEOUT=10 -MAX_DOCUMENT_SIZE=2 +MAX_DOCUMENT_SIZE=200 CLIENT_RESULTS_LIMIT=5 API_LIMIT_PER_PAGE=1000 @@ -207,13 +211,6 @@ FAVORITE_ENABLED=True RESOURCE_PUBLISHING=False ADMIN_MODERATE_UPLOADS=False -# PostgreSQL -POSTGRESQL_MAX_CONNECTIONS=200 - -# Upload Size Limiting -DEFAULT_MAX_UPLOAD_SIZE=5368709120 -DEFAULT_MAX_PARALLEL_UPLOADS_PER_USER=100 - # LDAP LDAP_ENABLED=False LDAP_SERVER_URL=ldap:// @@ -232,10 +229,22 @@ LDAP_GROUP_PROFILE_MEMBER_ATTR=uniqueMember # ## # Note right autoscale value must coincide with worker concurrency value # CELERY__AUTOSCALE_VALUES="15,10" -# CELERY__WORKER_CONCURRENCY="4" +# CELERY__WORKER_CONCURRENCY="10" # ## # CELERY__OPTS="--without-gossip --without-mingle -Ofair -B -E" # CELERY__BEAT_SCHEDULE="/mnt/volumes/statics/celerybeat-schedule" # CELERY__LOG_LEVEL="INFO" # CELERY__LOG_FILE="/var/log/celery.log" # CELERY__WORKER_NAME="worker1@%h" + +# PostgreSQL +POSTGRESQL_MAX_CONNECTIONS=200 + +# Common containers restart policy +RESTART_POLICY_CONDITION="on-failure" +RESTART_POLICY_DELAY="5s" +RESTART_POLICY_MAX_ATTEMPTS="3" +RESTART_POLICY_WINDOW=120s + +DEFAULT_MAX_UPLOAD_SIZE=5368709120 +DEFAULT_MAX_PARALLEL_UPLOADS_PER_USER=5 diff --git a/.env_dev b/.env_dev index 88a2b273a4f..6119f68ebd6 100644 --- a/.env_dev +++ b/.env_dev @@ -1,6 +1,4 @@ COMPOSE_PROJECT_NAME=geonode -DOCKERHOST= -DOCKER_HOST_IP= # See https://github.com/containers/podman/issues/13889 # DOCKER_BUILDKIT=0 DOCKER_ENV=production @@ -13,15 +11,11 @@ C_FORCE_ROOT=1 FORCE_REINIT=false INVOKE_LOG_STDOUT=true -# LANGUAGE_CODE=pt -# LANGUAGES=(('en','English'),('pt','Portuguese')) +# LANGUAGE_CODE=it-it +# LANGUAGES=(('en-us','English'),('it-it','Italiano')) DJANGO_SETTINGS_MODULE=geonode.settings GEONODE_INSTANCE_NAME=geonode -GEONODE_LB_HOST_IP= -GEONODE_LB_PORT= -PUBLIC_PORT=80 -NGINX_BASE_URL= # ################# # backend @@ -64,8 +58,11 @@ HAYSTACK_SEARCH_RESULTS_PER_PAGE=200 # nginx # HTTPD Server # ################# -GEONODE_LB_HOST_IP=localhost -GEONODE_LB_PORT=80 +GEONODE_LB_HOST_IP=django +GEONODE_LB_PORT=8000 +GEOSERVER_LB_HOST_IP=geoserver +GEOSERVER_LB_PORT=8080 +NGINX_BASE_URL=http://localhost # IP or domain name and port where the server can be reached on HTTPS (leave HOST empty if you want to use HTTP only) # port where the server can be reached on HTTPS @@ -95,8 +92,8 @@ GEOSERVER_LOCATION=http://localhost:8080/geoserver/ GEOSERVER_ADMIN_USER=admin GEOSERVER_ADMIN_PASSWORD=geoserver -OGC_REQUEST_TIMEOUT=60 -OGC_REQUEST_MAX_RETRIES=0 +OGC_REQUEST_TIMEOUT=30 +OGC_REQUEST_MAX_RETRIES=1 OGC_REQUEST_BACKOFF_FACTOR=0.3 OGC_REQUEST_POOL_MAXSIZE=10 OGC_REQUEST_POOL_CONNECTIONS=10 @@ -104,12 +101,18 @@ OGC_REQUEST_POOL_CONNECTIONS=10 # Java Options & Memory ENABLE_JSONP=true outFormat=text/javascript -GEOSERVER_JAVA_OPTS="-Djava.awt.headless=true -Xms2G -Xmx4G -Dgwc.context.suffix=gwc -XX:+UnlockDiagnosticVMOptions -XX:+LogVMOutput -XX:LogFile=/var/log/jvm.log -XX:PerfDataSamplingInterval=500 -XX:SoftRefLRUPolicyMSPerMB=36000 -XX:-UseGCOverheadLimit -XX:+UseConcMarkSweepGC -XX:ParallelGCThreads=4 -Dfile.encoding=UTF8 -Djavax.servlet.request.encoding=UTF-8 -Djavax.servlet.response.encoding=UTF-8 -Duser.timezone=GMT -Dorg.geotools.shapefile.datetime=false -DGS-SHAPEFILE-CHARSET=UTF-8 -DGEOSERVER_CSRF_DISABLED=true -DPRINT_BASE_URL=http://localhost:8080/geoserver/pdf -DALLOW_ENV_PARAMETRIZATION=true -Xbootclasspath/a:/usr/local/tomcat/webapps/geoserver/WEB-INF/lib/marlin-0.9.3-Unsafe.jar -Dsun.java2d.renderer=org.marlin.pisces.MarlinRenderingEngine" +GEOSERVER_JAVA_OPTS='-Djava.awt.headless=true -Xms4G -Xmx4G -Dgwc.context.suffix=gwc -XX:+UnlockDiagnosticVMOptions -XX:+LogVMOutput -XX:LogFile=/var/log/jvm.log -XX:PerfDataSamplingInterval=500 -XX:SoftRefLRUPolicyMSPerMB=36000 -XX:-UseGCOverheadLimit -XX:ParallelGCThreads=4 -Dfile.encoding=UTF8 -Djavax.servlet.request.encoding=UTF-8 -Djavax.servlet.response.encoding=UTF-8 -Duser.timezone=GMT -Dorg.geotools.shapefile.datetime=false -DGS-SHAPEFILE-CHARSET=UTF-8 -DGEOSERVER_CSRF_DISABLED=true -DPRINT_BASE_URL=http://localhost:8080/geoserver/pdf -DALLOW_ENV_PARAMETRIZATION=true -Xbootclasspath/a:/usr/local/tomcat/webapps/geoserver/WEB-INF/lib/marlin-0.9.3-Unsafe.jar -Dsun.java2d.renderer=org.marlin.pisces.MarlinRenderingEngine' # ################# # Security # ################# # Admin Settings +# +# ADMIN_PASSWORD is used to overwrite the GeoNode admin password **ONLY** the first time +# GeoNode is run. If you need to overwrite it again, you need to set the env var FORCE_REINIT, +# otherwise the invoke updateadmin task will be skipped and the current password already stored +# in DB will honored. + ADMIN_USERNAME=admin ADMIN_PASSWORD=admin ADMIN_EMAIL=admin@localhost @@ -127,18 +130,25 @@ DEFAULT_FROM_EMAIL='GeoNode ' # Session/Access Control LOCKDOWN_GEONODE=False -CORS_ALLOW_ALL_ORIGINS=True X_FRAME_OPTIONS="SAMEORIGIN" SESSION_EXPIRED_CONTROL_ENABLED=True DEFAULT_ANONYMOUS_VIEW_PERMISSION=True DEFAULT_ANONYMOUS_DOWNLOAD_PERMISSION=True +CORS_ALLOW_ALL_ORIGINS=True +GEOSERVER_CORS_ENABLED=True +GEOSERVER_CORS_ALLOWED_ORIGINS=* +GEOSERVER_CORS_ALLOWED_METHODS=GET,POST,PUT,DELETE,HEAD,OPTIONS +GEOSERVER_CORS_ALLOWED_HEADERS=* + # Users Registration ACCOUNT_OPEN_SIGNUP=True ACCOUNT_EMAIL_REQUIRED=True ACCOUNT_APPROVAL_REQUIRED=False ACCOUNT_CONFIRM_EMAIL_ON_GET=False ACCOUNT_EMAIL_VERIFICATION=none +ACCOUNT_EMAIL_CONFIRMATION_EMAIL=False +ACCOUNT_EMAIL_CONFIRMATION_REQUIRED=False ACCOUNT_AUTHENTICATION_METHOD=username_email AUTO_ASSIGN_REGISTERED_MEMBERS_TO_REGISTERED_MEMBERS_GROUP_NAME=True @@ -171,7 +181,7 @@ MEMCACHED_LOCATION=127.0.0.1:11211 MEMCACHED_LOCK_EXPIRE=3600 MEMCACHED_LOCK_TIMEOUT=10 -MAX_DOCUMENT_SIZE=2 +MAX_DOCUMENT_SIZE=200 CLIENT_RESULTS_LIMIT=5 API_LIMIT_PER_PAGE=1000 @@ -204,6 +214,11 @@ ADMIN_MODERATE_UPLOADS=False # PostgreSQL POSTGRESQL_MAX_CONNECTIONS=200 -# Upload Size Limiting +# Common containers restart policy +RESTART_POLICY_CONDITION="on-failure" +RESTART_POLICY_DELAY="5s" +RESTART_POLICY_MAX_ATTEMPTS="3" +RESTART_POLICY_WINDOW=120s + DEFAULT_MAX_UPLOAD_SIZE=5368709120 -DEFAULT_MAX_PARALLEL_UPLOADS_PER_USER=100 \ No newline at end of file +DEFAULT_MAX_PARALLEL_UPLOADS_PER_USER=5 diff --git a/.env_local b/.env_local index a19213926f2..48cba183d81 100644 --- a/.env_local +++ b/.env_local @@ -1,6 +1,4 @@ COMPOSE_PROJECT_NAME=geonode -DOCKERHOST= -DOCKER_HOST_IP= # See https://github.com/containers/podman/issues/13889 # DOCKER_BUILDKIT=0 DOCKER_ENV=production @@ -13,15 +11,11 @@ C_FORCE_ROOT=1 FORCE_REINIT=false INVOKE_LOG_STDOUT=true -# LANGUAGE_CODE=pt -# LANGUAGES=(('en','English'),('pt','Portuguese')) +# LANGUAGE_CODE=it-it +# LANGUAGES=(('en-us','English'),('it-it','Italiano')) DJANGO_SETTINGS_MODULE=geonode.settings GEONODE_INSTANCE_NAME=geonode -GEONODE_LB_HOST_IP= -GEONODE_LB_PORT= -PUBLIC_PORT=80 -NGINX_BASE_URL= # ################# # backend @@ -64,8 +58,11 @@ HAYSTACK_SEARCH_RESULTS_PER_PAGE=200 # nginx # HTTPD Server # ################# -GEONODE_LB_HOST_IP=localhost -GEONODE_LB_PORT=80 +GEONODE_LB_HOST_IP=django +GEONODE_LB_PORT=8000 +GEOSERVER_LB_HOST_IP=geoserver +GEOSERVER_LB_PORT=8080 +NGINX_BASE_URL=https://localhost # IP or domain name and port where the server can be reached on HTTPS (leave HOST empty if you want to use HTTP only) # port where the server can be reached on HTTPS @@ -104,12 +101,18 @@ OGC_REQUEST_POOL_CONNECTIONS=10 # Java Options & Memory ENABLE_JSONP=true outFormat=text/javascript -GEOSERVER_JAVA_OPTS="-Djava.awt.headless=true -Xms2G -Xmx4G -Dgwc.context.suffix=gwc -XX:+UnlockDiagnosticVMOptions -XX:+LogVMOutput -XX:LogFile=/var/log/jvm.log -XX:PerfDataSamplingInterval=500 -XX:SoftRefLRUPolicyMSPerMB=36000 -XX:-UseGCOverheadLimit -XX:+UseConcMarkSweepGC -XX:ParallelGCThreads=4 -Dfile.encoding=UTF8 -Djavax.servlet.request.encoding=UTF-8 -Djavax.servlet.response.encoding=UTF-8 -Duser.timezone=GMT -Dorg.geotools.shapefile.datetime=false -DGS-SHAPEFILE-CHARSET=UTF-8 -DGEOSERVER_CSRF_DISABLED=true -DPRINT_BASE_URL=http://localhost:8080/geoserver/pdf -DALLOW_ENV_PARAMETRIZATION=true -Xbootclasspath/a:/usr/local/tomcat/webapps/geoserver/WEB-INF/lib/marlin-0.9.3-Unsafe.jar -Dsun.java2d.renderer=org.marlin.pisces.MarlinRenderingEngine" +GEOSERVER_JAVA_OPTS='-Djava.awt.headless=true -Xms4G -Xmx4G -Dgwc.context.suffix=gwc -XX:+UnlockDiagnosticVMOptions -XX:+LogVMOutput -XX:LogFile=/var/log/jvm.log -XX:PerfDataSamplingInterval=500 -XX:SoftRefLRUPolicyMSPerMB=36000 -XX:-UseGCOverheadLimit -XX:ParallelGCThreads=4 -Dfile.encoding=UTF8 -Djavax.servlet.request.encoding=UTF-8 -Djavax.servlet.response.encoding=UTF-8 -Duser.timezone=GMT -Dorg.geotools.shapefile.datetime=false -DGS-SHAPEFILE-CHARSET=UTF-8 -DGEOSERVER_CSRF_DISABLED=true -DPRINT_BASE_URL=http://localhost:8080/geoserver/pdf -DALLOW_ENV_PARAMETRIZATION=true -Xbootclasspath/a:/usr/local/tomcat/webapps/geoserver/WEB-INF/lib/marlin-0.9.3-Unsafe.jar -Dsun.java2d.renderer=org.marlin.pisces.MarlinRenderingEngine' # ################# # Security # ################# # Admin Settings +# +# ADMIN_PASSWORD is used to overwrite the GeoNode admin password **ONLY** the first time +# GeoNode is run. If you need to overwrite it again, you need to set the env var FORCE_REINIT, +# otherwise the invoke updateadmin task will be skipped and the current password already stored +# in DB will honored. + ADMIN_USERNAME=admin ADMIN_PASSWORD=admin ADMIN_EMAIL=admin@localhost @@ -127,18 +130,25 @@ DEFAULT_FROM_EMAIL='GeoNode ' # Session/Access Control LOCKDOWN_GEONODE=False -CORS_ALLOW_ALL_ORIGINS=True X_FRAME_OPTIONS="SAMEORIGIN" SESSION_EXPIRED_CONTROL_ENABLED=True DEFAULT_ANONYMOUS_VIEW_PERMISSION=True DEFAULT_ANONYMOUS_DOWNLOAD_PERMISSION=True +CORS_ALLOW_ALL_ORIGINS=True +GEOSERVER_CORS_ENABLED=True +GEOSERVER_CORS_ALLOWED_ORIGINS=* +GEOSERVER_CORS_ALLOWED_METHODS=GET,POST,PUT,DELETE,HEAD,OPTIONS +GEOSERVER_CORS_ALLOWED_HEADERS=* + # Users Registration ACCOUNT_OPEN_SIGNUP=True ACCOUNT_EMAIL_REQUIRED=True ACCOUNT_APPROVAL_REQUIRED=False ACCOUNT_CONFIRM_EMAIL_ON_GET=False ACCOUNT_EMAIL_VERIFICATION=none +ACCOUNT_EMAIL_CONFIRMATION_EMAIL=False +ACCOUNT_EMAIL_CONFIRMATION_REQUIRED=False ACCOUNT_AUTHENTICATION_METHOD=username_email AUTO_ASSIGN_REGISTERED_MEMBERS_TO_REGISTERED_MEMBERS_GROUP_NAME=True @@ -171,7 +181,7 @@ MEMCACHED_LOCATION=127.0.0.1:11211 MEMCACHED_LOCK_EXPIRE=3600 MEMCACHED_LOCK_TIMEOUT=10 -MAX_DOCUMENT_SIZE=2 +MAX_DOCUMENT_SIZE=200 CLIENT_RESULTS_LIMIT=5 API_LIMIT_PER_PAGE=1000 @@ -182,7 +192,7 @@ BING_API_KEY= GOOGLE_API_KEY= # Monitoring -MONITORING_ENABLED=True +MONITORING_ENABLED=False MONITORING_DATA_TTL=365 USER_ANALYTICS_ENABLED=True USER_ANALYTICS_GZIP=True @@ -204,6 +214,11 @@ ADMIN_MODERATE_UPLOADS=False # PostgreSQL POSTGRESQL_MAX_CONNECTIONS=200 -# Upload Size Limiting +# Common containers restart policy +RESTART_POLICY_CONDITION="on-failure" +RESTART_POLICY_DELAY="5s" +RESTART_POLICY_MAX_ATTEMPTS="3" +RESTART_POLICY_WINDOW=120s + DEFAULT_MAX_UPLOAD_SIZE=5368709120 -DEFAULT_MAX_PARALLEL_UPLOADS_PER_USER=100 \ No newline at end of file +DEFAULT_MAX_PARALLEL_UPLOADS_PER_USER=5 diff --git a/.env_test b/.env_test index 9eed802d452..fb1910d57b1 100644 --- a/.env_test +++ b/.env_test @@ -1,6 +1,4 @@ COMPOSE_PROJECT_NAME=geonode -DOCKERHOST= -DOCKER_HOST_IP= # See https://github.com/containers/podman/issues/13889 # DOCKER_BUILDKIT=0 DOCKER_ENV=production @@ -13,15 +11,11 @@ C_FORCE_ROOT=1 FORCE_REINIT=false INVOKE_LOG_STDOUT=true -# LANGUAGE_CODE=pt -# LANGUAGES=(('en','English'),('pt','Portuguese')) +# LANGUAGE_CODE=it-it +# LANGUAGES=(('en-us','English'),('it-it','Italiano')) DJANGO_SETTINGS_MODULE=geonode.settings GEONODE_INSTANCE_NAME=geonode -GEONODE_LB_HOST_IP= -GEONODE_LB_PORT= -PUBLIC_PORT=80 -NGINX_BASE_URL= # ################# # backend @@ -47,7 +41,7 @@ BROKER_URL=amqp://guest:guest@rabbitmq:5672/ CELERY_BEAT_SCHEDULER=celery.beat:PersistentScheduler ASYNC_SIGNALS=True -SITEURL=http://localhost:8001/ +SITEURL=http://localhost:8000/ ALLOWED_HOSTS="['django', '*']" @@ -64,15 +58,18 @@ HAYSTACK_SEARCH_RESULTS_PER_PAGE=200 # nginx # HTTPD Server # ################# -GEONODE_LB_HOST_IP=localhost -GEONODE_LB_PORT=80 +GEONODE_LB_HOST_IP=django +GEONODE_LB_PORT=8000 +GEOSERVER_LB_HOST_IP=geoserver +GEOSERVER_LB_PORT=8080 +NGINX_BASE_URL=http://localhost # IP or domain name and port where the server can be reached on HTTPS (leave HOST empty if you want to use HTTP only) # port where the server can be reached on HTTPS HTTP_HOST=localhost HTTPS_HOST= -HTTP_PORT=8001 +HTTP_PORT=8000 HTTPS_PORT=443 # Let's Encrypt certificates for https encryption. You must have a domain name as HTTPS_HOST (doesn't work @@ -89,7 +86,7 @@ RESOLVER=127.0.0.11 # ################# # geoserver # ################# -GEOSERVER_WEB_UI_LOCATION=http://localhost:8001/geoserver/ +GEOSERVER_WEB_UI_LOCATION=http://localhost:8000/geoserver/ GEOSERVER_PUBLIC_LOCATION=http://localhost/geoserver/ GEOSERVER_LOCATION=http://geoserver:8080/geoserver/ GEOSERVER_ADMIN_USER=admin @@ -105,12 +102,12 @@ OGC_REQUEST_POOL_CONNECTIONS=10 # catalogue # ################# CATALOGUE_ENGINE=geonode.catalogue.backends.pycsw_local -CATALOGUE_URL=http://localhost:8001/catalogue/csw +CATALOGUE_URL=http://localhost:8000/catalogue/csw # Java Options & Memory ENABLE_JSONP=true outFormat=text/javascript -GEOSERVER_JAVA_OPTS="-Djava.awt.headless=true -Xms2G -Xmx4G -Dgwc.context.suffix=gwc -XX:+UnlockDiagnosticVMOptions -XX:+LogVMOutput -XX:LogFile=/var/log/jvm.log -XX:PerfDataSamplingInterval=500 -XX:SoftRefLRUPolicyMSPerMB=36000 -XX:-UseGCOverheadLimit -XX:+UseConcMarkSweepGC -XX:ParallelGCThreads=4 -Dfile.encoding=UTF8 -Djavax.servlet.request.encoding=UTF-8 -Djavax.servlet.response.encoding=UTF-8 -Duser.timezone=GMT -Dorg.geotools.shapefile.datetime=false -DGS-SHAPEFILE-CHARSET=UTF-8 -DGEOSERVER_CSRF_DISABLED=true -DPRINT_BASE_URL=http://localhost:8001/geoserver/pdf -DALLOW_ENV_PARAMETRIZATION=true -Xbootclasspath/a:/usr/local/tomcat/webapps/geoserver/WEB-INF/lib/marlin-0.9.3-Unsafe.jar -Dsun.java2d.renderer=org.marlin.pisces.MarlinRenderingEngine" +GEOSERVER_JAVA_OPTS='-Djava.awt.headless=true -Xms4G -Xmx4G -Dgwc.context.suffix=gwc -XX:+UnlockDiagnosticVMOptions -XX:+LogVMOutput -XX:LogFile=/var/log/jvm.log -XX:PerfDataSamplingInterval=500 -XX:SoftRefLRUPolicyMSPerMB=36000 -XX:-UseGCOverheadLimit -XX:ParallelGCThreads=4 -Dfile.encoding=UTF8 -Djavax.servlet.request.encoding=UTF-8 -Djavax.servlet.response.encoding=UTF-8 -Duser.timezone=GMT -Dorg.geotools.shapefile.datetime=false -DGS-SHAPEFILE-CHARSET=UTF-8 -DGEOSERVER_CSRF_DISABLED=true -DPRINT_BASE_URL=http://localhost/geoserver/pdf -DALLOW_ENV_PARAMETRIZATION=true -Xbootclasspath/a:/usr/local/tomcat/webapps/geoserver/WEB-INF/lib/marlin-0.9.3-Unsafe.jar -Dsun.java2d.renderer=org.marlin.pisces.MarlinRenderingEngine' # ################# # Security @@ -139,18 +136,25 @@ DEFAULT_FROM_EMAIL='GeoNode ' # Session/Access Control LOCKDOWN_GEONODE=False -CORS_ALLOW_ALL_ORIGINS=True X_FRAME_OPTIONS="SAMEORIGIN" SESSION_EXPIRED_CONTROL_ENABLED=True DEFAULT_ANONYMOUS_VIEW_PERMISSION=True DEFAULT_ANONYMOUS_DOWNLOAD_PERMISSION=True +CORS_ALLOW_ALL_ORIGINS=True +GEOSERVER_CORS_ENABLED=True +GEOSERVER_CORS_ALLOWED_ORIGINS=* +GEOSERVER_CORS_ALLOWED_METHODS=GET,POST,PUT,DELETE,HEAD,OPTIONS +GEOSERVER_CORS_ALLOWED_HEADERS=* + # Users Registration ACCOUNT_OPEN_SIGNUP=True ACCOUNT_EMAIL_REQUIRED=True ACCOUNT_APPROVAL_REQUIRED=False ACCOUNT_CONFIRM_EMAIL_ON_GET=False ACCOUNT_EMAIL_VERIFICATION=none +ACCOUNT_EMAIL_CONFIRMATION_EMAIL=False +ACCOUNT_EMAIL_CONFIRMATION_REQUIRED=False ACCOUNT_AUTHENTICATION_METHOD=username_email AUTO_ASSIGN_REGISTERED_MEMBERS_TO_REGISTERED_MEMBERS_GROUP_NAME=True @@ -186,7 +190,7 @@ MEMCACHED_LOCATION=127.0.0.1:11211 MEMCACHED_LOCK_EXPIRE=3600 MEMCACHED_LOCK_TIMEOUT=10 -MAX_DOCUMENT_SIZE=2 +MAX_DOCUMENT_SIZE=200 CLIENT_RESULTS_LIMIT=5 API_LIMIT_PER_PAGE=1000 @@ -219,6 +223,17 @@ ADMIN_MODERATE_UPLOADS=False # PostgreSQL POSTGRESQL_MAX_CONNECTIONS=200 -# Upload Size Limiting +# Common containers restart policy +RESTART_POLICY_CONDITION="on-failure" +RESTART_POLICY_DELAY="5s" +RESTART_POLICY_MAX_ATTEMPTS="3" +RESTART_POLICY_WINDOW=120s + DEFAULT_MAX_UPLOAD_SIZE=5368709120 -DEFAULT_MAX_PARALLEL_UPLOADS_PER_USER=100 \ No newline at end of file +DEFAULT_MAX_PARALLEL_UPLOADS_PER_USER=100 + +# Azure AD +MICROSOFT_TENANT_ID= +AZURE_CLIENT_ID= +AZURE_SECRET_KEY= +AZURE_KEY= diff --git a/.gitignore b/.gitignore index bfe33426c44..7a455624878 100644 --- a/.gitignore +++ b/.gitignore @@ -95,3 +95,5 @@ scripts/spcgeonode/_volume_* # Docker Hub hooks !hooks/* +.env + diff --git a/Dockerfile b/Dockerfile index c09545fcf3b..0805c6494d6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,8 +28,13 @@ RUN chmod +x /usr/bin/celery-cmd # RUN cd /usr/src/geonode-contribs/geonode-logstash; pip install --upgrade -e . \ # cd /usr/src/geonode-contribs/ldap; pip install --upgrade -e . -RUN pip install --upgrade --no-cache-dir --src /usr/src -r requirements.txt -RUN pip install --upgrade -e . +RUN yes w | pip install --src /usr/src -Ur requirements.txt +RUN yes w | pip install --upgrade -e . + +# Cleanup apt update lists +RUN apt-get autoremove --purge +RUN apt-get clean +RUN rm -rf /var/lib/apt/lists/* # Export ports EXPOSE 8000 diff --git a/README.md b/README.md index db5ca65a4b3..8ec744e587e 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ Table of Contents - [Table of Contents](#table-of-contents) - [What is GeoNode?](#what-is-geonode) - [Try out GeoNode](#try-out-geonode) + - [Quick Docker Start](#quick-docker-start) - [Install](#install) - [Learn GeoNode](#learn-geonode) - [Development](#development) @@ -47,17 +48,51 @@ maps, editing metadata, styles and much more. To get an overview what GeoNode can do we recommend to have a look at the [Users Workshop](https://docs.geonode.org/en/master/usage/index.html). +Quick Docker Start +------------------ + + ```bash + python3.10 -m venv ~/.venvs/geonode + source ~/.venvs/geonode/bin/activate + + pip install Django==3.2.* + ``` + ```bash + python create-envfile.py + ``` +`create-envfile.py` accepts the following arguments: + +- `--https`: Enable SSL. It's disabled by default +- `--env_type`: + - When set to `prod` `DEBUG` is disabled and the creation of a valid `SSL` is requested to Letsencrypt's ACME server + - When set to `test` `DEBUG` is disabled and a test `SSL` certificate is generated for local testing + - When set to `dev` `DEBUG` is enabled and no `SSL` certificate is generated +- `--hostname`: The URL that whill serve GeoNode (`localhost` by default) +- `--email`: The administrator's email. Notice that a real email and a valid SMPT configurations are required if `--env_type` is seto to `prod`. Letsencrypt uses to email for issuing the SSL certificate +- `--geonodepwd`: GeoNode's administrator password. A random value is set if left empty +- `--geoserverpwd`: GeoNode's administrator password. A random value is set if left empty +- `--pgpwd`: PostgreSQL's administrator password. A random value is set if left empty +- `--dbpwd`: GeoNode DB user role's password. A random value is set if left empty +- `--geodbpwd`: GeoNode data DB user role's password. A random value is set if left empty +- `--clientid`: Client id of Geoserver's GeoNode Oauth2 client. A random value is set if left empty +- `--clientsecret`: Client secret of Geoserver's GeoNode Oauth2 client. A random value is set if left empty + +```bash + docker compose build + docker compose up -d +``` + Install ------- - The latest official release is 4.0.2! + The latest official release is 4.1.0! GeoNode can be setup in different ways, flavors and plattforms. If you´re planning to do development or install for production please visit the offical GeoNode installation documentation: - [Docker](https://docs.geonode.org/en/master/install/advanced/core/index.html#docker) -- [Ubuntu 20.04 LTS](https://docs.geonode.org/en/master/install/advanced/core/index.html#ubuntu-20-04lts) +- [Ubuntu 22.04](https://docs.geonode.org/en/master/install/advanced/core/index.html#ubuntu-22-04) Learn GeoNode ------------- diff --git a/create-envfile.py b/create-envfile.py new file mode 100644 index 00000000000..c910e912618 --- /dev/null +++ b/create-envfile.py @@ -0,0 +1,185 @@ +# -*- coding: utf-8 -*- +######################################################################### +# +# Copyright (C) 2022 OSGeo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### +import argparse +import json +import logging +import os +import random +import re +import string +import sys +import ast + +dir_path = os.path.dirname(os.path.realpath(__file__)) + +logger = logging.getLogger() +handler = logging.StreamHandler(sys.stdout) +logger.setLevel(logging.INFO) +formatter = logging.Formatter("%(levelname)s - %(message)s") +handler.setFormatter(formatter) +logger.addHandler(handler) + + +def shuffle(chars): + chars_as_list = list(chars) + random.shuffle(chars_as_list) + return "".join(chars_as_list) + + +_simple_chars = shuffle(string.ascii_letters + string.digits) +_strong_chars = shuffle( + string.ascii_letters + string.digits + string.punctuation.replace('"', "").replace("'", "").replace("`", "") +) + + +def generate_env_file(args): + # validity checks + if not os.path.exists(args.sample_file): + logger.error(f"File does not exists {args.sample_file}") + raise FileNotFoundError + + if args.file and not os.path.isfile(args.file): + logger.error(f"File does not exists: {args.file}") + raise FileNotFoundError + + if args.https and not args.email: + raise Exception("With HTTPS enabled, the email parameter is required") + + _sample_file = None + with open(args.sample_file, "r+") as sample_file: + _sample_file = sample_file.read() + + if not _sample_file: + raise Exception("Sample file is empty!") + + def _get_vals_to_replace(args): + _config = ["sample_file", "file", "env_type", "https", "email"] + _jsfile = {} + if args.file: + with open(args.file) as _json_file: + _jsfile = json.load(_json_file) + + _vals_to_replace = {key: _jsfile.get(key, val) for key, val in vars(args).items() if key not in _config} + tcp = "https" if ast.literal_eval(f"{_jsfile.get('https', args.https)}".capitalize()) else "http" + + _vals_to_replace["public_port"] = ( + "443" if ast.literal_eval(f"{_jsfile.get('https', args.https)}".capitalize()) else "80" + ) + _vals_to_replace["http_host"] = _jsfile.get("hostname", args.hostname) if tcp == "http" else "" + _vals_to_replace["https_host"] = _jsfile.get("hostname", args.hostname) if tcp == "https" else "" + + _vals_to_replace["siteurl"] = f"{tcp}://{_jsfile.get('hostname', args.hostname)}" + _vals_to_replace["secret_key"] = _jsfile.get("secret_key", args.secret_key) or "".join( + random.choice(_strong_chars) for _ in range(50) + ) + _vals_to_replace["letsencrypt_mode"] = ( + "disabled" + if not _vals_to_replace.get("https_host") + else "staging" + if _jsfile.get("env_type", args.env_type) in ["test"] + else "production" + ) + _vals_to_replace["debug"] = False if _jsfile.get("env_type", args.env_type) in ["prod", "test"] else True + _vals_to_replace["email"] = _jsfile.get("email", args.email) + + if tcp == "https" and not _vals_to_replace["email"]: + raise Exception("With HTTPS enabled, the email parameter is required") + + return {**_jsfile, **_vals_to_replace} + + for key, val in _get_vals_to_replace(args).items(): + _val = val or "".join(random.choice(_simple_chars) for _ in range(15)) + if isinstance(val, bool) or key in ["email", "http_host", "https_host"]: + _val = str(val) + _sample_file = re.sub( + "{" + key + "}", + lambda _: _val, + _sample_file, + ) + + with open(f"{dir_path}/.env", "w+") as output_env: + output_env.write(_sample_file) + logger.info(f".env file created: {dir_path}/.env") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + prog="ENV file builder", + description="Tool for generate environment file automatically. The information can be passed or via CLI or via JSON file ( --file /path/env.json)", + usage="python create-envfile.py localhost -f /path/to/json/file.json", + allow_abbrev=False + ) + parser.add_argument( + "--noinput", + "--no-input", + action="store_false", + dest="confirmation", + help=("skips prompting for confirmation."), + ) + parser.add_argument( + "-hn", + "--hostname", + help=f"Host name, default localhost", + default="localhost", + ) + + # expected path as a value + parser.add_argument( + "-sf", + "--sample_file", + help=f"Path of the sample file to use as a template. Default is: {dir_path}/.env.sample", + default=f"{dir_path}/.env.sample", + ) + parser.add_argument( + "-f", + "--file", + help="absolute path of the file with the configuration. Note: we expect that the keys of the dictionary have the same name as the CLI params", + ) + # booleans + parser.add_argument("--https", action="store_true", default=False, help="If provided, https is used") + # strings + parser.add_argument("--email", help="Admin email, this field is required if https is enabled") + + parser.add_argument("--geonodepwd", help="GeoNode admin password") + parser.add_argument("--geoserverpwd", help="Geoserver admin password") + parser.add_argument("--pgpwd", help="PostgreSQL password") + parser.add_argument("--dbpwd", help="GeoNode DB user password") + parser.add_argument("--geodbpwd", help="Geodatabase user password") + parser.add_argument("--clientid", help="Oauth2 client id") + parser.add_argument("--clientsecret", help="Oauth2 client secret") + parser.add_argument("--secret_key", help="Django Secret Key") + + parser.add_argument( + "--env_type", + help="Development/production or test", + choices=["prod", "test", "dev"], + default="prod", + ) + + args = parser.parse_args() + + if not args.confirmation: + generate_env_file(args) + else: + overwrite_env = input("This action will overwrite any existing .env file. Do you wish to continue? (y/n)") + if overwrite_env not in ["y", "n"]: + logger.error("Please enter a valid response") + if overwrite_env == "y": + generate_env_file(args) diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index 1ba2fec619f..0ac801a9621 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -3,12 +3,12 @@ version: '3.9' # Common Django template for GeoNode and Celery services below x-common-django: &default-common-django - image: geonode:local - restart: on-failure + image: geonode/geonode:local + restart: unless-stopped env_file: - .env volumes: - # - '.:/usr/src/geonode' + - '.:/usr/src/geonode' - statics:/mnt/volumes/statics - geoserver-data-dir:/geoserver_data/data - backup-restore:/backup_restore @@ -17,8 +17,6 @@ x-common-django: depends_on: db: condition: service_healthy - geoserver: - condition: service_healthy services: @@ -30,11 +28,11 @@ services: dockerfile: Dockerfile container_name: django4${COMPOSE_PROJECT_NAME} healthcheck: - test: "curl --fail --silent --write-out 'HTTP CODE : %{http_code}\n' --output /dev/null http://127.0.0.1:8001/" + test: "curl -m 10 --fail --silent --write-out 'HTTP CODE : %{http_code}\n' --output /dev/null http://django:8000/" start_period: 60s interval: 60s timeout: 10s - retries: 10 + retries: 2 environment: - IS_CELERY=False entrypoint: ["/usr/src/geonode/entrypoint.sh"] @@ -43,10 +41,10 @@ services: # Celery worker that executes celery tasks created by Django. celery: << : *default-common-django - image: geonode:local container_name: celery4${COMPOSE_PROJECT_NAME} depends_on: - - django + django: + condition: service_healthy environment: - IS_CELERY=True entrypoint: ["/usr/src/geonode/entrypoint.sh"] @@ -54,15 +52,12 @@ services: # Nginx is serving django static and media files and proxies to django and geonode geonode: - image: geonode/nginx:4.0 + image: geonode/nginx:1.25.1 build: ./scripts/docker/nginx/ container_name: nginx4${COMPOSE_PROJECT_NAME} + env_file: + - .env environment: - - HTTPS_HOST=${HTTPS_HOST} - - HTTP_HOST=${HTTP_HOST} - - HTTPS_PORT=${HTTPS_PORT} - - HTTP_PORT=${HTTP_PORT} - - LETSENCRYPT_MODE=${LETSENCRYPT_MODE} - RESOLVER=127.0.0.11 ports: - "${HTTP_PORT}:80" @@ -71,46 +66,45 @@ services: - nginx-confd:/etc/nginx - nginx-certificates:/geonode-certificates - statics:/mnt/volumes/statics - restart: on-failure + restart: unless-stopped # Gets and installs letsencrypt certificates letsencrypt: - image: geonode/letsencrypt:4.0 + image: geonode/letsencrypt:latest build: ./scripts/docker/letsencrypt/ container_name: letsencrypt4${COMPOSE_PROJECT_NAME} - environment: - - HTTPS_HOST=${HTTPS_HOST} - - HTTP_HOST=${HTTP_HOST} - - ADMIN_EMAIL=${ADMIN_EMAIL} - - LETSENCRYPT_MODE=${LETSENCRYPT_MODE} + env_file: + - .env volumes: - nginx-certificates:/geonode-certificates - restart: on-failure + restart: unless-stopped # Geoserver backend geoserver: image: geonode/geoserver:2.23.0 container_name: geoserver4${COMPOSE_PROJECT_NAME} healthcheck: - test: "curl --fail --silent --write-out 'HTTP CODE : %{http_code}\n' --output /dev/null http://127.0.0.1:8080/geoserver/ows" + test: "curl -m 10 --fail --silent --write-out 'HTTP CODE : %{http_code}\n' --output /dev/null http://geoserver:8080/geoserver/ows" start_period: 60s interval: 60s timeout: 10s - retries: 10 + retries: 2 env_file: - .env + ports: + - "8080:8080" volumes: - statics:/mnt/volumes/statics - geoserver-data-dir:/geoserver_data/data - backup-restore:/backup_restore - data:/data - tmp:/tmp - restart: on-failure + restart: unless-stopped depends_on: - db: - condition: service_healthy data-dir-conf: condition: service_healthy + django: + condition: service_healthy data-dir-conf: image: geonode/geoserver_data:2.23.0 @@ -118,14 +112,14 @@ services: entrypoint: sleep infinity volumes: - geoserver-data-dir:/geoserver_data/data - restart: on-failure + restart: unless-stopped healthcheck: test: "ls -A '/geoserver_data/data' | wc -l" # PostGIS database. db: - # use geonode official postgis 13 image - image: geonode/postgis:13 + # use geonode official postgis 15 image + image: geonode/postgis:15 command: postgres -c "max_connections=${POSTGRESQL_MAX_CONNECTIONS}" container_name: db4${COMPOSE_PROJECT_NAME} env_file: @@ -133,7 +127,7 @@ services: volumes: - dbdata:/var/lib/postgresql/data - dbbackups:/pg_backups - restart: on-failure + restart: unless-stopped healthcheck: test: "pg_isready -d postgres -U postgres" # uncomment to enable remote connections to postgres @@ -142,11 +136,11 @@ services: # Vanilla RabbitMQ service. This is needed by celery rabbitmq: - image: rabbitmq:3.7-alpine + image: rabbitmq:3-alpine container_name: rabbitmq4${COMPOSE_PROJECT_NAME} volumes: - rabbitmq:/var/lib/rabbitmq - restart: on-failure + restart: unless-stopped volumes: statics: diff --git a/docker-compose-test.yml b/docker-compose-test.yml index de398f099a7..762183a5eac 100644 --- a/docker-compose-test.yml +++ b/docker-compose-test.yml @@ -3,8 +3,8 @@ version: '3.9' # Common Django template for GeoNode and Celery services below x-common-django: &default-common-django - image: geonode/geonode:4.0 - restart: on-failure + image: geonode/geonode:latest-ubuntu-22.10 + restart: unless-stopped env_file: - .env_test volumes: @@ -17,8 +17,6 @@ x-common-django: depends_on: db: condition: service_healthy - geoserver: - condition: service_healthy services: @@ -30,11 +28,11 @@ services: dockerfile: Dockerfile container_name: django4${COMPOSE_PROJECT_NAME} healthcheck: - test: "curl --fail --silent --write-out 'HTTP CODE : %{http_code}\n' --output /dev/null http://127.0.0.1:8001/" + test: "curl -m 10 --fail --silent --write-out 'HTTP CODE : %{http_code}\n' --output /dev/null http://django:8000/" start_period: 60s interval: 60s timeout: 10s - retries: 10 + retries: 2 environment: - IS_CELERY=False entrypoint: ["/usr/src/geonode/entrypoint.sh"] @@ -43,10 +41,10 @@ services: # Celery worker that executes celery tasks created by Django. celery: << : *default-common-django - image: geonode/geonode:4.0 container_name: celery4${COMPOSE_PROJECT_NAME} depends_on: - - django + django: + condition: service_healthy environment: - IS_CELERY=True entrypoint: ["/usr/src/geonode/entrypoint.sh"] @@ -54,15 +52,12 @@ services: # Nginx is serving django static and media files and proxies to django and geonode geonode: - image: geonode/nginx:4.0 + image: geonode/nginx:1.25.1 build: ./scripts/docker/nginx/ container_name: nginx4${COMPOSE_PROJECT_NAME} + env_file: + - .env_test environment: - - HTTPS_HOST=${HTTPS_HOST} - - HTTP_HOST=${HTTP_HOST} - - HTTPS_PORT=${HTTPS_PORT} - - HTTP_PORT=${HTTP_PORT} - - LETSENCRYPT_MODE=${LETSENCRYPT_MODE} - RESOLVER=127.0.0.11 ports: - "${HTTP_PORT}:80" @@ -71,46 +66,45 @@ services: - nginx-confd:/etc/nginx - nginx-certificates:/geonode-certificates - statics:/mnt/volumes/statics - restart: on-failure + restart: unless-stopped # Gets and installs letsencrypt certificates letsencrypt: - image: geonode/letsencrypt:4.0 + image: geonode/letsencrypt:latest build: ./scripts/docker/letsencrypt/ container_name: letsencrypt4${COMPOSE_PROJECT_NAME} - environment: - - HTTPS_HOST=${HTTPS_HOST} - - HTTP_HOST=${HTTP_HOST} - - ADMIN_EMAIL=${ADMIN_EMAIL} - - LETSENCRYPT_MODE=${LETSENCRYPT_MODE} + env_file: + - .env_test volumes: - nginx-certificates:/geonode-certificates - restart: on-failure + restart: unless-stopped # Geoserver backend geoserver: image: geonode/geoserver:2.23.0 container_name: geoserver4${COMPOSE_PROJECT_NAME} healthcheck: - test: "curl --fail --silent --write-out 'HTTP CODE : %{http_code}\n' --output /dev/null http://127.0.0.1:8080/geoserver/ows" + test: "curl -m 10 --fail --silent --write-out 'HTTP CODE : %{http_code}\n' --output /dev/null http://geoserver:8080/geoserver/ows" start_period: 60s interval: 60s timeout: 10s - retries: 10 + retries: 2 env_file: - .env_test + ports: + - "8080:8080" volumes: - statics:/mnt/volumes/statics - geoserver-data-dir:/geoserver_data/data - backup-restore:/backup_restore - data:/data - tmp:/tmp - restart: on-failure + restart: unless-stopped depends_on: - db: - condition: service_healthy data-dir-conf: condition: service_healthy + django: + condition: service_healthy data-dir-conf: image: geonode/geoserver_data:2.23.0 @@ -118,14 +112,14 @@ services: entrypoint: sleep infinity volumes: - geoserver-data-dir:/geoserver_data/data - restart: on-failure + restart: unless-stopped healthcheck: test: "ls -A '/geoserver_data/data' | wc -l" # PostGIS database. db: - # use geonode official postgis 13 image - image: geonode/postgis:13 + # use geonode official postgis 15 image + image: geonode/postgis:15 command: postgres -c "max_connections=${POSTGRESQL_MAX_CONNECTIONS}" container_name: db4${COMPOSE_PROJECT_NAME} env_file: @@ -133,7 +127,7 @@ services: volumes: - dbdata:/var/lib/postgresql/data - dbbackups:/pg_backups - restart: on-failure + restart: unless-stopped healthcheck: test: "pg_isready -d postgres -U postgres" # uncomment to enable remote connections to postgres @@ -142,11 +136,11 @@ services: # Vanilla RabbitMQ service. This is needed by celery rabbitmq: - image: rabbitmq:3.7-alpine + image: rabbitmq:3-alpine container_name: rabbitmq4${COMPOSE_PROJECT_NAME} volumes: - rabbitmq:/var/lib/rabbitmq - restart: on-failure + restart: unless-stopped volumes: statics: diff --git a/docker-compose.yml b/docker-compose.yml index d87342e2ad4..853231d824a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,7 +8,6 @@ x-common-django: env_file: - .env volumes: - # - '.:/usr/src/geonode' - statics:/mnt/volumes/statics - geoserver-data-dir:/geoserver_data/data - backup-restore:/backup_restore @@ -17,8 +16,6 @@ x-common-django: depends_on: db: condition: service_healthy - geoserver: - condition: service_healthy services: @@ -27,11 +24,11 @@ services: << : *default-common-django container_name: django4${COMPOSE_PROJECT_NAME} healthcheck: - test: "curl --fail --silent --write-out 'HTTP CODE : %{http_code}\n' --output /dev/null http://127.0.0.1:8001/" + test: "curl -m 10 --fail --silent --write-out 'HTTP CODE : %{http_code}\n' --output /dev/null http://django:8000/" start_period: 60s interval: 60s timeout: 10s - retries: 10 + retries: 2 environment: - IS_CELERY=False entrypoint: ["/usr/src/geonode/entrypoint.sh"] @@ -42,7 +39,8 @@ services: << : *default-common-django container_name: celery4${COMPOSE_PROJECT_NAME} depends_on: - - django + django: + condition: service_healthy environment: - IS_CELERY=True entrypoint: ["/usr/src/geonode/entrypoint.sh"] @@ -50,15 +48,12 @@ services: # Nginx is serving django static and media files and proxies to django and geonode geonode: - image: geonode/nginx:4.1.0 + image: geonode/nginx:1.25.1 build: ./scripts/docker/nginx/ container_name: nginx4${COMPOSE_PROJECT_NAME} + env_file: + - .env environment: - - HTTPS_HOST=${HTTPS_HOST} - - HTTP_HOST=${HTTP_HOST} - - HTTPS_PORT=${HTTPS_PORT} - - HTTP_PORT=${HTTP_PORT} - - LETSENCRYPT_MODE=${LETSENCRYPT_MODE} - RESOLVER=127.0.0.11 ports: - "${HTTP_PORT}:80" @@ -71,14 +66,11 @@ services: # Gets and installs letsencrypt certificates letsencrypt: - image: geonode/letsencrypt:4.1.0 + image: geonode/letsencrypt:latest build: ./scripts/docker/letsencrypt/ container_name: letsencrypt4${COMPOSE_PROJECT_NAME} - environment: - - HTTPS_HOST=${HTTPS_HOST} - - HTTP_HOST=${HTTP_HOST} - - ADMIN_EMAIL=${ADMIN_EMAIL} - - LETSENCRYPT_MODE=${LETSENCRYPT_MODE} + env_file: + - .env volumes: - nginx-certificates:/geonode-certificates restart: unless-stopped @@ -88,13 +80,15 @@ services: image: geonode/geoserver:2.23.0 container_name: geoserver4${COMPOSE_PROJECT_NAME} healthcheck: - test: "curl --fail --silent --write-out 'HTTP CODE : %{http_code}\n' --output /dev/null http://127.0.0.1:8080/geoserver/ows" + test: "curl -m 10 --fail --silent --write-out 'HTTP CODE : %{http_code}\n' --output /dev/null http://geoserver:8080/geoserver/ows" start_period: 60s interval: 60s timeout: 10s - retries: 10 + retries: 2 env_file: - .env + ports: + - "8080:8080" volumes: - statics:/mnt/volumes/statics - geoserver-data-dir:/geoserver_data/data @@ -103,10 +97,10 @@ services: - tmp:/tmp restart: unless-stopped depends_on: - db: - condition: service_healthy data-dir-conf: condition: service_healthy + django: + condition: service_healthy data-dir-conf: image: geonode/geoserver_data:2.23.0 diff --git a/entrypoint.sh b/entrypoint.sh index 620976448cc..6bb062b910e 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -41,7 +41,7 @@ echo MONITORING_HOST_NAME=$MONITORING_HOST_NAME echo MONITORING_SERVICE_NAME=$MONITORING_SERVICE_NAME echo MONITORING_DATA_TTL=$MONITORING_DATA_TTL -invoke waitfordbs +# invoke waitfordbs cmd="$@" @@ -61,8 +61,6 @@ else fi invoke statics - invoke waitforgeoserver - invoke geoserverfixture echo "Executing UWSGI server $cmd for Production" fi diff --git a/geonode/api/__init__.py b/geonode/api/__init__.py index 79177e00bdd..55b0b0cb9fd 100644 --- a/geonode/api/__init__.py +++ b/geonode/api/__init__.py @@ -16,3 +16,4 @@ # along with this program. If not, see . # ######################################################################### +default_app_config = "geonode.api.apps.GeoNodeApiAppConfig" diff --git a/geonode/base/api/tests.py b/geonode/base/api/tests.py index 5a278f432bd..88df7c3f7b2 100644 --- a/geonode/base/api/tests.py +++ b/geonode/base/api/tests.py @@ -622,15 +622,15 @@ def test_base_resources(self): }, { "name": "", - "slug": "http-localhost-8001-thesaurus-no-about-thesauro-38", - "uri": "http://localhost:8001//thesaurus/no-about-thesauro#38", + "slug": "http-localhost-8000-thesaurus-no-about-thesauro-38", + "uri": "http://localhost:8000//thesaurus/no-about-thesauro#38", "thesaurus": {"name": "Thesauro without the about", "slug": "no-about-thesauro", "uri": ""}, "i18n": {}, }, { "name": "bar_keyword", - "slug": "http-localhost-8001-thesaurus-no-about-thesauro-bar-keyword", - "uri": "http://localhost:8001//thesaurus/no-about-thesauro#bar_keyword", + "slug": "http-localhost-8000-thesaurus-no-about-thesauro-bar-keyword", + "uri": "http://localhost:8000//thesaurus/no-about-thesauro#bar_keyword", "thesaurus": {"name": "Thesauro without the about", "slug": "no-about-thesauro", "uri": ""}, "i18n": {}, }, diff --git a/geonode/catalogue/__init__.py b/geonode/catalogue/__init__.py index 3b577289766..d1b9c72dbff 100644 --- a/geonode/catalogue/__init__.py +++ b/geonode/catalogue/__init__.py @@ -25,6 +25,7 @@ from django.core.exceptions import ImproperlyConfigured from importlib import import_module +default_app_config = "geonode.catalogue.apps.GeoNodeCatalogueAppConfig" DEFAULT_CATALOGUE_ALIAS = "default" # GeoNode uses this if the CATALOGUE setting is empty (None). diff --git a/geonode/catalogue/metadataxsl/__init__.py b/geonode/catalogue/metadataxsl/__init__.py index 79177e00bdd..0143f08e23a 100644 --- a/geonode/catalogue/metadataxsl/__init__.py +++ b/geonode/catalogue/metadataxsl/__init__.py @@ -16,3 +16,4 @@ # along with this program. If not, see . # ######################################################################### +default_app_config = "geonode.catalogue.metadataxsl.apps.GeoNodeCatalogueMetadataxslAppConfig" diff --git a/geonode/celery_app.py b/geonode/celery_app.py index 0e3c411c2ad..5f9b5e95624 100644 --- a/geonode/celery_app.py +++ b/geonode/celery_app.py @@ -49,14 +49,14 @@ def setup_periodic_tasks(sender, **kwargs): @app.task( bind=True, - name='{{project_name}}.test', + name='geonode.test', queue='default') def test(arg): _log(arg) @app.task( bind=True, - name='{{project_name}}.debug_task', + name='geonode.debug_task', queue='default') def debug_task(self): _log(f"Request: {self.request}") diff --git a/geonode/documents/exif/__init__.py b/geonode/documents/exif/__init__.py index 79177e00bdd..bc045338385 100644 --- a/geonode/documents/exif/__init__.py +++ b/geonode/documents/exif/__init__.py @@ -16,3 +16,4 @@ # along with this program. If not, see . # ######################################################################### +default_app_config = "geonode.documents.exif.apps.GeoNodeDocumentsExifAppConfig" diff --git a/geonode/favorite/__init__.py b/geonode/favorite/__init__.py index 79177e00bdd..83005e67c7f 100644 --- a/geonode/favorite/__init__.py +++ b/geonode/favorite/__init__.py @@ -16,3 +16,4 @@ # along with this program. If not, see . # ######################################################################### +default_app_config = "geonode.favorite.apps.GeoNodeFavoriteAppConfig" diff --git a/geonode/geoserver/createlayer/__init__.py b/geonode/geoserver/createlayer/__init__.py index 3d54b02dfef..a304ef09a5b 100644 --- a/geonode/geoserver/createlayer/__init__.py +++ b/geonode/geoserver/createlayer/__init__.py @@ -16,3 +16,4 @@ # along with this program. If not, see . # ######################################################################### +default_app_config = "geonode.geoserver.createlayer.apps.GeoNodeGeoserverCreatelayerAppConfig" diff --git a/geonode/geoserver/processing/__init__.py b/geonode/geoserver/processing/__init__.py index 6b4db6084e8..333409c58a5 100644 --- a/geonode/geoserver/processing/__init__.py +++ b/geonode/geoserver/processing/__init__.py @@ -16,3 +16,4 @@ # along with this program. If not, see . # ######################################################################### +default_app_config = "geonode.geoserver.processing.apps.GeoNodeGeoserverProcessingAppConfig" diff --git a/geonode/local_settings.py.geoserver.sample b/geonode/local_settings.py.geoserver.sample index 18aa492c76f..59ac574511b 100644 --- a/geonode/local_settings.py.geoserver.sample +++ b/geonode/local_settings.py.geoserver.sample @@ -99,9 +99,9 @@ GEOSERVER_WEB_UI_LOCATION = os.getenv("GEOSERVER_WEB_UI_LOCATION", GEOSERVER_LOC GEOSERVER_PUBLIC_LOCATION = os.getenv("GEOSERVER_PUBLIC_LOCATION", _default_public_location) -OGC_SERVER_DEFAULT_USER = os.getenv("GEOSERVER_ADMIN_USER", "admin") +GEOSERVER_ADMIN_USER = os.getenv("GEOSERVER_ADMIN_USER", "admin") -OGC_SERVER_DEFAULT_PASSWORD = os.getenv("GEOSERVER_ADMIN_PASSWORD", "geoserver") +GEOSERVER_ADMIN_PASSWORD = os.getenv("GEOSERVER_ADMIN_PASSWORD", "geoserver") # OGC (WMS/WFS/WCS) Server Settings OGC_SERVER = { @@ -115,8 +115,8 @@ OGC_SERVER = { # the proxy won't work and the integration tests will fail # the entire block has to be overridden in the local_settings "PUBLIC_LOCATION": GEOSERVER_PUBLIC_LOCATION, - "USER": OGC_SERVER_DEFAULT_USER, - "PASSWORD": OGC_SERVER_DEFAULT_PASSWORD, + "USER": GEOSERVER_ADMIN_USER, + "PASSWORD": GEOSERVER_ADMIN_PASSWORD, "MAPFISH_PRINT_ENABLED": True, "PRINT_NG_ENABLED": True, "GEONODE_SECURITY_ENABLED": True, diff --git a/geonode/people/adapters.py b/geonode/people/adapters.py index a322562d19a..63ac7e36659 100644 --- a/geonode/people/adapters.py +++ b/geonode/people/adapters.py @@ -44,6 +44,7 @@ from django.core.exceptions import ValidationError from django.utils.module_loading import import_string +from geonode.utils import import_class_module from geonode.groups.models import GroupProfile logger = logging.getLogger(__name__) @@ -67,6 +68,18 @@ def get_data_extractor(provider_id): return extractor +def get_group_role_mapper(provider_id): + group_role_mapper_class = import_class_module( + getattr(settings, "SOCIALACCOUNT_PROVIDERS", {}) + .get(PROVIDER_ID, {}) + .get( + "GROUP_ROLE_MAPPER_CLASS", + "geonode.people.profileextractors.OpenIDGroupRoleMapper", + ) + ) + return group_role_mapper_class() + + def update_profile(sociallogin): """Update a people.models.Profile object with info from the sociallogin""" user = sociallogin.user @@ -248,11 +261,14 @@ class GenericOpenIDConnectAdapter(OAuth2Adapter, SocialAccountAdapter): def complete_login(self, request, app, token, response, **kwargs): extra_data = {} if self.profile_url: - headers = {"Authorization": "Bearer {0}".format(token.token)} - resp = requests.get(self.profile_url, headers=headers) - profile_data = resp.json() - extra_data.update(profile_data) - elif "id_token" in response: + try: + headers = {"Authorization": f"Bearer {token.token}"} + resp = requests.get(self.profile_url, headers=headers) + profile_data = resp.json() + extra_data.update(profile_data) + except Exception: + logger.exception(OAuth2Error("Invalid profile_url, falling back to id_token checks...")) + if not extra_data and "id_token" in response: try: extra_data = jwt.decode( response["id_token"], @@ -279,20 +295,21 @@ def complete_login(self, request, app, token, response, **kwargs): def save_user(self, request, sociallogin, form=None): user = super(SocialAccountAdapter, self).save_user(request, sociallogin, form=form) extractor = get_data_extractor(sociallogin.account.provider) + group_role_mapper = get_group_role_mapper(sociallogin.account.provider) try: groups = extractor.extract_groups(sociallogin.account.extra_data) or extractor.extract_roles( sociallogin.account.extra_data ) - is_manager = extractor.extract_is_manager(sociallogin.account.extra_data) # check here if user is member already of other groups and remove it form the ones that are not declared here... for groupprofile in user.group_list_all(): groupprofile.leave(user) - for group_name in groups: + for group_role_name in groups: + group_name, role_name = group_role_mapper.parse_group_and_role(group_role_name) groupprofile = GroupProfile.objects.filter(slug=group_name).first() if groupprofile: groupprofile.join(user) - if is_manager: + if group_role_mapper.is_manager(role_name): groupprofile.promote() except (AttributeError, NotImplementedError): pass # extractor doesn't define a method for extracting field diff --git a/geonode/people/profileextractors.py b/geonode/people/profileextractors.py index 2bf7fa12e51..7b9cdebefec 100644 --- a/geonode/people/profileextractors.py +++ b/geonode/people/profileextractors.py @@ -129,10 +129,6 @@ def _extract_field(self, name, data): PROVIDER_ID = getattr(settings, "SOCIALACCOUNT_OIDC_PROVIDER", "geonode_openid_connect") -IS_MANAGER_FIELD = ( - getattr(settings, "SOCIALACCOUNT_PROVIDERS", {}).get(PROVIDER_ID, {}).get("IS_MANAGER_FIELD", "is_manager") -) - class OpenIDExtractor(BaseExtractor): def extract_email(self, data): @@ -198,10 +194,20 @@ def extract_groups(self, data): def extract_roles(self, data): return data.get("roles", "") - def extract_is_manager(self, data): - return data.get(IS_MANAGER_FIELD, "") - def _get_latest_position(data): all_positions = data.get("positions", {"values": []})["values"] return all_positions[0] if any(all_positions) else None + + +class OpenIDGroupRoleMapper: + """GeoNode will look for names like: ["GroupProfile1.Role", "GroupProfile2.Role", ..., "GroupProfileN.Role"]""" + + def parse_group_and_role(self, group_role_name): + _group_role_name = group_role_name if "." in group_role_name else f"{group_role_name}.None" + group_name, role_name = _group_role_name.rsplit(".", 1) + return (group_name, role_name) + + def is_manager(role_name): + _role_name = role_name or "" + return "manager" in _role_name.lower() diff --git a/geonode/people/socialaccount/providers/geonode_openid_connect/provider.py b/geonode/people/socialaccount/providers/geonode_openid_connect/provider.py index 329ba3c4bf3..196be1e17a2 100644 --- a/geonode/people/socialaccount/providers/geonode_openid_connect/provider.py +++ b/geonode/people/socialaccount/providers/geonode_openid_connect/provider.py @@ -68,7 +68,11 @@ def get_auth_params(self, request, action): return ret def extract_uid(self, data): - return data.get("sub", data.get("id")) + _uid_field = getattr(settings, "SOCIALACCOUNT_PROVIDERS", {}).get(PROVIDER_ID, {}).get("UID_FIELD", None) + if _uid_field: + return data.get(_uid_field) + else: + return data.get("uid", data.get("sub", data.get("id"))) def extract_common_fields(self, data): _common_fields = getattr(settings, "SOCIALACCOUNT_PROVIDERS", {}).get(PROVIDER_ID, {}).get("COMMON_FIELDS", {}) diff --git a/geonode/people/socialaccount/providers/geonode_openid_connect/tests.py b/geonode/people/socialaccount/providers/geonode_openid_connect/tests.py index 29373c2fd93..04be234c6c3 100644 --- a/geonode/people/socialaccount/providers/geonode_openid_connect/tests.py +++ b/geonode/people/socialaccount/providers/geonode_openid_connect/tests.py @@ -213,7 +213,6 @@ def test_social_email_verification_optional(self): "prompt": "select_account consent", }, "COMMON_FIELDS": {"email": "email", "last_name": "family_name", "first_name": "given_name"}, - "IS_MANAGER_FIELD": "is_manager", "ACCOUNT_CLASS": "allauth.socialaccount.providers.google.provider.GoogleAccount", "ACCESS_TOKEN_URL": "https://oauth2.googleapis.com/token", "AUTHORIZE_URL": "https://accounts.google.com/o/oauth2/v2/auth", diff --git a/geonode/proxy/__init__.py b/geonode/proxy/__init__.py index da86ef5219a..7b2209ac902 100644 --- a/geonode/proxy/__init__.py +++ b/geonode/proxy/__init__.py @@ -16,3 +16,4 @@ # along with this program. If not, see . # ######################################################################### +default_app_config = "geonode.proxy.apps.GeoNodeProxyAppConfig" diff --git a/geonode/security/__init__.py b/geonode/security/__init__.py index da86ef5219a..0edec538e72 100644 --- a/geonode/security/__init__.py +++ b/geonode/security/__init__.py @@ -16,3 +16,4 @@ # along with this program. If not, see . # ######################################################################### +default_app_config = "geonode.security.apps.GeoNodeSecurityAppConfig" diff --git a/geonode/settings.py b/geonode/settings.py index 73e6fea14da..9b746b9d63e 100644 --- a/geonode/settings.py +++ b/geonode/settings.py @@ -1004,9 +1004,12 @@ GEOSERVER_WEB_UI_LOCATION = os.getenv("GEOSERVER_WEB_UI_LOCATION", GEOSERVER_PUBLIC_LOCATION) -OGC_SERVER_DEFAULT_USER = os.getenv("GEOSERVER_ADMIN_USER", "admin") +GEOSERVER_ADMIN_USER = os.getenv("GEOSERVER_ADMIN_USER", "admin") -OGC_SERVER_DEFAULT_PASSWORD = os.getenv("GEOSERVER_ADMIN_PASSWORD", "geoserver") +GEOSERVER_ADMIN_PASSWORD = os.getenv("GEOSERVER_ADMIN_PASSWORD", "geoserver") + +# This is the password from Geoserver factory data-dir. It's only used at install time to perform the very first configurfation of GEOSERVER_ADMIN_PASSWORD +GEOSERVER_FACTORY_PASSWORD = os.getenv("GEOSERVER_FACTORY_PASSWORD", "geoserver") GEOFENCE_SECURITY_ENABLED = ( False if TEST and not INTEGRATION else ast.literal_eval(os.getenv("GEOFENCE_SECURITY_ENABLED", "True")) @@ -1025,8 +1028,8 @@ # the proxy won't work and the integration tests will fail # the entire block has to be overridden in the local_settings "PUBLIC_LOCATION": GEOSERVER_PUBLIC_LOCATION, - "USER": OGC_SERVER_DEFAULT_USER, - "PASSWORD": OGC_SERVER_DEFAULT_PASSWORD, + "USER": GEOSERVER_ADMIN_USER, + "PASSWORD": GEOSERVER_ADMIN_PASSWORD, "MAPFISH_PRINT_ENABLED": ast.literal_eval(os.getenv("MAPFISH_PRINT_ENABLED", "True")), "PRINT_NG_ENABLED": ast.literal_eval(os.getenv("PRINT_NG_ENABLED", "True")), "GEONODE_SECURITY_ENABLED": ast.literal_eval(os.getenv("GEONODE_SECURITY_ENABLED", "True")), @@ -1969,6 +1972,17 @@ def get_geonode_catalogue_service(): SOCIALACCOUNT_OIDC_PROVIDER_ENABLED = ast.literal_eval(os.environ.get("SOCIALACCOUNT_OIDC_PROVIDER_ENABLED", "False")) SOCIALACCOUNT_ADAPTER = os.environ.get("SOCIALACCOUNT_ADAPTER", "geonode.people.adapters.GenericOpenIDConnectAdapter") +_SOCIALACCOUNT_PROFILE_EXTRACTOR = os.environ.get( + "SOCIALACCOUNT_PROFILE_EXTRACTOR", "geonode.people.profileextractors.OpenIDExtractor" +) +SOCIALACCOUNT_PROFILE_EXTRACTORS = { + SOCIALACCOUNT_OIDC_PROVIDER: _SOCIALACCOUNT_PROFILE_EXTRACTOR, +} + +SOCIALACCOUNT_GROUP_ROLE_MAPPER = os.environ.get( + "SOCIALACCOUNT_GROUP_ROLE_MAPPER", "geonode.people.profileextractors.OpenIDGroupRoleMapper" +) + # Enable this in order to enable the OIDC SocialAccount Provider if SOCIALACCOUNT_OIDC_PROVIDER_ENABLED: INSTALLED_APPS += ("geonode.people.socialaccount.providers.geonode_openid_connect",) @@ -1985,7 +1999,8 @@ def get_geonode_catalogue_service(): "prompt": "select_account", }, "COMMON_FIELDS": {"email": "mail", "last_name": "surname", "first_name": "givenName"}, - "IS_MANAGER_FIELD": "is_manager", + "UID_FIELD": "unique_name", + "GROUP_ROLE_MAPPER_CLASS": SOCIALACCOUNT_GROUP_ROLE_MAPPER, "ACCOUNT_CLASS": "allauth.socialaccount.providers.azure.provider.AzureAccount", "ACCESS_TOKEN_URL": f"https://login.microsoftonline.com/{_AZURE_TENANT_ID}/oauth2/v2.0/token", "AUTHORIZE_URL": f"https://login.microsoftonline.com/{_AZURE_TENANT_ID}/oauth2/v2.0/authorize", @@ -2003,7 +2018,7 @@ def get_geonode_catalogue_service(): "prompt": "select_account consent", }, "COMMON_FIELDS": {"email": "email", "last_name": "family_name", "first_name": "given_name"}, - "IS_MANAGER_FIELD": "is_manager", + "GROUP_ROLE_MAPPER_CLASS": SOCIALACCOUNT_GROUP_ROLE_MAPPER, "ACCOUNT_CLASS": "allauth.socialaccount.providers.google.provider.GoogleAccount", "ACCESS_TOKEN_URL": "https://oauth2.googleapis.com/token", "AUTHORIZE_URL": "https://accounts.google.com/o/oauth2/v2/auth", @@ -2018,13 +2033,7 @@ def get_geonode_catalogue_service(): SOCIALACCOUNT_OIDC_PROVIDER: SOCIALACCOUNT_PROVIDERS_DEFS.get(_SOCIALACCOUNT_PROVIDER), } -_SOCIALACCOUNT_PROFILE_EXTRACTOR = os.environ.get( - "SOCIALACCOUNT_PROFILE_EXTRACTOR", "geonode.people.profileextractors.OpenIDExtractor" -) -SOCIALACCOUNT_PROFILE_EXTRACTORS = { - SOCIALACCOUNT_OIDC_PROVIDER: _SOCIALACCOUNT_PROFILE_EXTRACTOR, -} - +# Invitation Adapter INVITATIONS_ADAPTER = ACCOUNT_ADAPTER INVITATIONS_CONFIRMATION_URL_NAME = "geonode.invitations:accept-invite" diff --git a/geonode/tasks/__init__.py b/geonode/tasks/__init__.py index f3b257d5e12..41405557613 100644 --- a/geonode/tasks/__init__.py +++ b/geonode/tasks/__init__.py @@ -16,3 +16,4 @@ # along with this program. If not, see . # ######################################################################### +default_app_config = "geonode.tasks.apps.GeoNodeTasksAppConfig" diff --git a/geonode/upload/tests/test_settings.py b/geonode/upload/tests/test_settings.py index 9e25f52c835..3ff3368c88b 100644 --- a/geonode/upload/tests/test_settings.py +++ b/geonode/upload/tests/test_settings.py @@ -100,9 +100,11 @@ GEOSERVER_PUBLIC_LOCATION = os.getenv("GEOSERVER_PUBLIC_LOCATION", _default_public_location) -OGC_SERVER_DEFAULT_USER = os.getenv("GEOSERVER_ADMIN_USER", "admin") +GEOSERVER_ADMIN_USER = os.getenv("GEOSERVER_ADMIN_USER", "admin") -OGC_SERVER_DEFAULT_PASSWORD = os.getenv("GEOSERVER_ADMIN_PASSWORD", "geoserver") +GEOSERVER_ADMIN_PASSWORD = os.getenv("GEOSERVER_ADMIN_PASSWORD", "geoserver") + +GEOSERVER_FACTORY_PASSWORD = os.getenv("GEOSERVER_FACTORY_PASSWORD", "geoserver") # OGC (WMS/WFS/WCS) Server Settings OGC_SERVER = { @@ -116,8 +118,8 @@ # the proxy won't work and the integration tests will fail # the entire block has to be overridden in the local_settings "PUBLIC_LOCATION": GEOSERVER_PUBLIC_LOCATION, - "USER": OGC_SERVER_DEFAULT_USER, - "PASSWORD": OGC_SERVER_DEFAULT_PASSWORD, + "USER": GEOSERVER_ADMIN_USER, + "PASSWORD": GEOSERVER_ADMIN_PASSWORD, "MAPFISH_PRINT_ENABLED": True, "PRINT_NG_ENABLED": True, "GEONODE_SECURITY_ENABLED": True, diff --git a/geonode/utils.py b/geonode/utils.py index 6d541926ba1..5abab4b2411 100755 --- a/geonode/utils.py +++ b/geonode/utils.py @@ -2012,8 +2012,8 @@ def import_class_module(full_class_string): """ Dynamically load a class from a string - >>> klass = load_class("module.submodule.ClassName") - >>> klass2 = load_class("myfile.Class2") + >>> klass = import_class_module("module.submodule.ClassName") + >>> klass2 = import_class_module("myfile.Class2") """ try: module_path, class_name = full_class_string.rsplit(".", 1) diff --git a/package/support/geonode.local_settings b/package/support/geonode.local_settings index 5525ee0aa20..3e485a5d021 100644 --- a/package/support/geonode.local_settings +++ b/package/support/geonode.local_settings @@ -98,14 +98,18 @@ GEOSERVER_PUBLIC_LOCATION = os.getenv( 'GEOSERVER_PUBLIC_LOCATION', '{}gs/'.format(SITEURL) ) -OGC_SERVER_DEFAULT_USER = os.getenv( +GEOSERVER_ADMIN_USER = os.getenv( 'GEOSERVER_ADMIN_USER', 'admin' ) -OGC_SERVER_DEFAULT_PASSWORD = os.getenv( +GEOSERVER_ADMIN_PASSWORD = os.getenv( 'GEOSERVER_ADMIN_PASSWORD', 'geoserver' ) +GEOSERVER_FACTORY_PASSWORD = os.getenv( + 'GEOSERVER_FACTORY_PASSWORD', 'geoserver' +) + # OGC (WMS/WFS/WCS) Server Settings OGC_SERVER = { 'default': { @@ -117,8 +121,8 @@ OGC_SERVER = { # the proxy won't work and the integration tests will fail # the entire block has to be overridden in the local_settings 'PUBLIC_LOCATION': GEOSERVER_PUBLIC_LOCATION, - 'USER': OGC_SERVER_DEFAULT_USER, - 'PASSWORD': OGC_SERVER_DEFAULT_PASSWORD, + 'USER': GEOSERVER_ADMIN_USER, + 'PASSWORD': GEOSERVER_ADMIN_PASSWORD, 'MAPFISH_PRINT_ENABLED': True, 'PRINT_NG_ENABLED': True, 'GEONODE_SECURITY_ENABLED': True, diff --git a/pavement.py b/pavement.py index 5a494f124f4..3c59b99a4eb 100644 --- a/pavement.py +++ b/pavement.py @@ -765,9 +765,7 @@ def test_integration(options): bind = options.get("bind", "0.0.0.0:8000") foreground = "" if options.get("foreground", False) else "&" sh(f"DJANGO_SETTINGS_MODULE={settings} python -W ignore manage.py runmessaging {foreground}") - sh( - f"DJANGO_SETTINGS_MODULE={settings} python -W ignore manage.py runserver {bind} {foreground}" - ) + sh(f"DJANGO_SETTINGS_MODULE={settings} python -W ignore manage.py runserver {bind} {foreground}") sh("sleep 30") settings = f"REUSE_DB=1 DJANGO_SETTINGS_MODULE={settings}" else: diff --git a/requirements.txt b/requirements.txt index 4c40148f408..8e5702f81fa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -173,6 +173,7 @@ webdriver_manager==4.0.0 mistune==3.0.1 protobuf==3.20.3 mako==1.2.4 +paramiko==3.3.1 # not directly required, fixes Blowfish deprecation warning certifi>=2022.12.7 # not directly required, pinned by Snyk to avoid a vulnerability jwcrypto>=1.4 # not directly required, pinned by Snyk to avoid a vulnerability cryptography>=41.0.0 # not directly required, pinned by Snyk to avoid a vulnerability diff --git a/scripts/docker/nginx/Dockerfile b/scripts/docker/nginx/Dockerfile index 0a497ce0384..2e3a8fea7cd 100644 --- a/scripts/docker/nginx/Dockerfile +++ b/scripts/docker/nginx/Dockerfile @@ -1,9 +1,12 @@ FROM nginx:1.25.1-alpine -RUN apk add --no-cache openssl inotify-tools +RUN apk add --no-cache openssl inotify-tools vim WORKDIR /etc/nginx/ +RUN mkdir -p /etc/nginx/html +RUN touch /etc/nginx/html/index.html + ADD nginx.conf.envsubst nginx.https.available.conf.envsubst ./ ADD geonode.conf.envsubst ./sites-enabled/ diff --git a/scripts/docker/nginx/docker-entrypoint.sh b/scripts/docker/nginx/docker-entrypoint.sh index 9afb7cffedb..e6bec7a1db2 100644 --- a/scripts/docker/nginx/docker-entrypoint.sh +++ b/scripts/docker/nginx/docker-entrypoint.sh @@ -15,7 +15,7 @@ echo "Creating autoissued certificates for HTTP host" if [ ! -f "/geonode-certificates/autoissued/privkey.pem" ] || [[ $(find /geonode-certificates/autoissued/privkey.pem -mtime +365 -print) ]]; then echo "Autoissued certificate does not exist or is too old, we generate one" mkdir -p "/geonode-certificates/autoissued/" - openssl req -x509 -nodes -days 1825 -newkey rsa:2048 -keyout "/geonode-certificates/autoissued/privkey.pem" -out "/geonode-certificates/autoissued/fullchain.pem" -subj "/CN=${HTTP_HOST:-null}" + openssl req -x509 -nodes -days 1825 -newkey rsa:2048 -keyout "/geonode-certificates/autoissued/privkey.pem" -out "/geonode-certificates/autoissued/fullchain.pem" -subj "/CN=${HTTP_HOST:-HTTPS_HOST}" else echo "Autoissued certificate already exists" fi @@ -32,18 +32,25 @@ else ln -sf "/geonode-certificates/autoissued" /certificate_symlink fi -echo "Sanity checks on http/s ports configuration" -if [ -z "${JENKINS_HTTP_PORT}" ]; then - JENKINS_HTTP_PORT=9080 +if [ -z "${HTTPS_HOST}" ]; then + HTTP_SCHEME="http" +else + HTTP_SCHEME="https" fi +export HTTP_SCHEME=${HTTP_SCHEME:-http} +export GEONODE_LB_HOST_IP=${GEONODE_LB_HOST_IP:-django} +export GEONODE_LB_PORT=${GEONODE_LB_PORT:-8000} +export GEOSERVER_LB_HOST_IP=${GEOSERVER_LB_HOST_IP:-geoserver} +export GEOSERVER_LB_PORT=${GEOSERVER_LB_PORT:-8080} + echo "Replacing environement variables" -envsubst '\$HTTP_HOST \$HTTPS_HOST \$RESOLVER' < /etc/nginx/nginx.conf.envsubst > /etc/nginx/nginx.conf -envsubst '\$HTTP_HOST \$HTTPS_HOST \$RESOLVER' < /etc/nginx/nginx.https.available.conf.envsubst > /etc/nginx/nginx.https.available.conf -envsubst '\$HTTP_HOST \$HTTPS_HOST \$JENKINS_HTTP_PORT' < /etc/nginx/sites-enabled/geonode.conf.envsubst > /etc/nginx/sites-enabled/geonode.conf +envsubst '\$HTTP_HOST \$HTTPS_HOST \$HTTP_SCHEME \$GEONODE_LB_HOST_IP \$GEONODE_LB_PORT \$GEOSERVER_LB_HOST_IP \$GEOSERVER_LB_PORT \$RESOLVER' < /etc/nginx/nginx.conf.envsubst > /etc/nginx/nginx.conf +envsubst '\$HTTP_HOST \$HTTPS_HOST \$HTTP_SCHEME \$GEONODE_LB_HOST_IP \$GEONODE_LB_PORT \$GEOSERVER_LB_HOST_IP \$GEOSERVER_LB_PORT \$RESOLVER' < /etc/nginx/nginx.https.available.conf.envsubst > /etc/nginx/nginx.https.available.conf +envsubst '\$HTTP_HOST \$HTTPS_HOST \$HTTP_SCHEME \$GEONODE_LB_HOST_IP \$GEONODE_LB_PORT \$GEOSERVER_LB_HOST_IP \$GEOSERVER_LB_PORT' < /etc/nginx/sites-enabled/geonode.conf.envsubst > /etc/nginx/sites-enabled/geonode.conf echo "Enabling or not https configuration" -if [ -z "${HTTPS_HOST}" ]; then +if [ -z "${HTTPS_HOST}" ]; then echo "" > /etc/nginx/nginx.https.enabled.conf else ln -sf /etc/nginx/nginx.https.available.conf /etc/nginx/nginx.https.enabled.conf diff --git a/scripts/docker/nginx/geonode.conf.envsubst b/scripts/docker/nginx/geonode.conf.envsubst index 9291ded95bc..1176ce2cc2b 100644 --- a/scripts/docker/nginx/geonode.conf.envsubst +++ b/scripts/docker/nginx/geonode.conf.envsubst @@ -6,8 +6,14 @@ charset utf-8; # max upload size client_max_body_size 100G; client_body_buffer_size 256K; +client_body_timeout 600s; large_client_header_buffers 4 64k; -proxy_read_timeout 600s; + +proxy_connect_timeout 600; +proxy_send_timeout 600; +proxy_read_timeout 600; +uwsgi_read_timeout 600; +send_timeout 600; fastcgi_hide_header Set-Cookie; @@ -35,33 +41,23 @@ gzip_types # GeoServer location /geoserver { - - # Using a variable is a trick to let Nginx start even if upstream host is not up yet - # (see https://sandro-keil.de/blog/2017/07/24/let-nginx-start-if-upstream-host-is-unavailable-or-down/) - set $upstream geoserver:8080; - - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - - proxy_pass http://$upstream; -} - -# Jenkins -location /jenkins { - - # Using a variable is a trick to let Nginx start even if upstream host is not up yet - # (see https://sandro-keil.de/blog/2017/07/24/let-nginx-start-if-upstream-host-is-unavailable-or-down/) - set $upstream jenkins:$JENKINS_HTTP_PORT; - # set $upstream $HTTP_HOST$HTTPS_HOST:$JENKINS_HTTP_PORT; - - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - - proxy_pass http://$upstream; + # Using a variable is a trick to let Nginx start even if upstream host is not up yet + # (see https://sandro-keil.de/blog/2017/07/24/let-nginx-start-if-upstream-host-is-unavailable-or-down/) + set $upstream $GEOSERVER_LB_HOST_IP:$GEOSERVER_LB_PORT; + + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Server $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Proto $HTTP_SCHEME; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_hide_header X-Frame-Options; + proxy_pass http://$upstream; + proxy_http_version 1.1; + proxy_redirect http://$upstream $HTTP_SCHEME://$HTTP_HOST; + proxy_request_buffering off; + client_max_body_size 0; } # GeoNode @@ -89,31 +85,10 @@ location /uploaded/ { } } -location ~ ^/celery-monitor/? { - - # Using a variable is a trick to let Nginx start even if upstream host is not up yet - # (see https://sandro-keil.de/blog/2017/07/24/let-nginx-start-if-upstream-host-is-unavailable-or-down/) - set $upstream celerymonitor:5555; - - rewrite ^/celery-monitor/?(.*)$ /$1 break; - - sub_filter '="/' '="/celery-monitor/'; - sub_filter_last_modified on; - sub_filter_once off; - - # proxy_pass http://unix:/tmp/flower.sock:/; - proxy_pass http://$upstream; - proxy_redirect off; - proxy_set_header Host $host; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_http_version 1.1; -} - location / { # Using a variable is a trick to let Nginx start even if upstream host is not up yet # (see https://sandro-keil.de/blog/2017/07/24/let-nginx-start-if-upstream-host-is-unavailable-or-down/) - set $upstream django:8000; + set $upstream $GEONODE_LB_HOST_IP:$GEONODE_LB_PORT; if ($request_method = OPTIONS) { add_header Access-Control-Allow-Methods "GET, POST, PUT, PATCH, OPTIONS"; @@ -128,27 +103,26 @@ location / { add_header Access-Control-Allow-Credentials false; add_header Access-Control-Allow-Headers "Content-Type, Accept, Authorization, Origin, User-Agent"; add_header Access-Control-Allow-Methods "GET, POST, PUT, PATCH, OPTIONS"; - - proxy_connect_timeout 600; - proxy_send_timeout 600; - proxy_read_timeout 600; - send_timeout 600; + proxy_redirect off; proxy_set_header Host $host; + proxy_set_header Origin $HTTP_SCHEME://$host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Host $server_name; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto $HTTP_SCHEME; proxy_hide_header X-Frame-Options; + proxy_request_buffering off; # uwsgi_params include /etc/nginx/uwsgi_params; - # proxy_pass http://$upstream; - uwsgi_pass $upstream; + proxy_pass http://$upstream; + # uwsgi_pass $upstream; # when a client closes the connection then keep the channel to uwsgi open. Otherwise uwsgi throws an IOError uwsgi_ignore_client_abort on; + uwsgi_request_buffering off; location ~* \.(?:js|jpg|jpeg|gif|png|tgz|gz|rar|bz2|doc|pdf|ppt|tar|wav|bmp|ttf|rtf|swf|ico|flv|woff|woff2|svg|xml)$ { gzip_static always; diff --git a/scripts/docker/nginx/nginx.conf.envsubst b/scripts/docker/nginx/nginx.conf.envsubst index 4a486226a36..b6065209d51 100644 --- a/scripts/docker/nginx/nginx.conf.envsubst +++ b/scripts/docker/nginx/nginx.conf.envsubst @@ -23,7 +23,7 @@ http { # TODO : do not use unencrypted connection even on LAN, but is it possible to have browser not complaining about unknown authority ? server { listen 80; - server_name $HTTP_HOST 127.0.0.1 geonode; + server_name $HTTP_HOST 127.0.0.1; include sites-enabled/*.conf; } diff --git a/tasks.py b/tasks.py index 5711ab991b9..967c08af386 100755 --- a/tasks.py +++ b/tasks.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- ######################################################################### # # Copyright (C) 2016 OSGeo @@ -23,10 +24,11 @@ import time import docker import socket +import ipaddress import logging import datetime -from urllib.parse import urlparse +from urllib.parse import urlparse, urlunparse from invoke import task BOOTSTRAP_IMAGE_CHEIP = "codenvy/che-ip:nightly" @@ -41,20 +43,12 @@ def waitfordbs(ctx): ctx.run(f"/usr/bin/wait-for-databases {db_host}", pty=True) -@task -def waitforgeoserver(ctx): - print("****************************geoserver********************************") - while not _gs_service_availability(f"{os.environ['GEOSERVER_LOCATION']}ows"): - print("Wait for GeoServer API availability...") - print("GeoServer is available for HTTP calls!") - - @task def update(ctx): print("***************************setting env*********************************") ctx.run("env", pty=True) - pub_ip = _geonode_public_host_ip() - print(f"Public Hostname or IP is {pub_ip}") + pub_host = _geonode_public_host() + print(f"Public Hostname is {pub_host}") pub_port = _geonode_public_port() print(f"Public PORT is {pub_port}") pub_protocol = "https" if pub_port == "443" else "http" @@ -62,13 +56,14 @@ def update(ctx): pub_port = None db_url = _update_db_connstring() geodb_url = _update_geodb_connstring() - service_ready = False - while not service_ready: + geonode_docker_host = None + for _cnt in range(1, 29): try: - socket.gethostbyname("geonode") - service_ready = True + geonode_docker_host = str(socket.gethostbyname("geonode")) + break except Exception: - time.sleep(10) + print(f"...waiting for NGINX to pop-up...{_cnt}") + time.sleep(1) override_env = "$HOME/.override_env" if os.path.exists(override_env): @@ -77,24 +72,24 @@ def update(ctx): print(f"Can not delete the {override_env} file as it doesn't exists") if pub_port: - siteurl = f"{pub_protocol}://{pub_ip}:{pub_port}/" - gs_pub_loc = f"http://{pub_ip}:{pub_port}/geoserver/" + siteurl = f"{pub_protocol}://{pub_host}:{pub_port}/" + gs_pub_loc = f"http://{pub_host}:{pub_port}/geoserver/" else: - siteurl = f"{pub_protocol}://{pub_ip}/" - gs_pub_loc = f"http://{pub_ip}/geoserver/" + siteurl = f"{pub_protocol}://{pub_host}/" + gs_pub_loc = f"http://{pub_host}/geoserver/" envs = { "local_settings": str(_localsettings()), "siteurl": os.environ.get("SITEURL", siteurl), - "geonode_docker_host": str(socket.gethostbyname("geonode")), + "geonode_docker_host": geonode_docker_host, "public_protocol": pub_protocol, - "public_fqdn": str(pub_ip) + str(f":{pub_port}" if pub_port else ""), - "public_host": str(pub_ip), + "public_fqdn": str(pub_host) + str(f":{pub_port}" if pub_port else ""), + "public_host": str(pub_host), "dburl": os.environ.get("DATABASE_URL", db_url), "geodburl": os.environ.get("GEODATABASE_URL", geodb_url), "static_root": os.environ.get("STATIC_ROOT", "/mnt/volumes/statics/static/"), "media_root": os.environ.get("MEDIA_ROOT", "/mnt/volumes/statics/uploaded/"), "geoip_path": os.environ.get("GEOIP_PATH", "/mnt/volumes/statics/geoip.db"), - "monitoring": os.environ.get("MONITORING_ENABLED", True), + "monitoring": os.environ.get("MONITORING_ENABLED", False), "monitoring_host_name": os.environ.get("MONITORING_HOST_NAME", "geonode"), "monitoring_service_name": os.environ.get("MONITORING_SERVICE_NAME", "local-geonode"), "monitoring_data_ttl": os.environ.get("MONITORING_DATA_TTL", 7), @@ -118,7 +113,7 @@ def update(ctx): ) except ValueError: current_allowed = [] - current_allowed.extend([str(pub_ip), f"{pub_ip}:{pub_port}"]) + current_allowed.extend([str(pub_host), f"{pub_host}:{pub_port}"]) allowed_hosts = [str(c) for c in current_allowed] + ['"geonode"', '"django"'] ctx.run( @@ -326,9 +321,15 @@ def update(ctx): def migrations(ctx): print("**************************migrations*******************************") ctx.run(f"python manage.py migrate --noinput --settings={_localsettings()}", pty=True) - ctx.run(f"python manage.py migrate --noinput --settings={_localsettings()} --database=datastore", pty=True) + ctx.run( + f"python manage.py migrate --noinput --settings={_localsettings()} --database=datastore", + pty=True, + ) try: - ctx.run(f"python manage.py rebuild_index --noinput --settings={_localsettings()}", pty=True) + ctx.run( + f"python manage.py rebuild_index --noinput --settings={_localsettings()}", + pty=True, + ) except Exception: pass @@ -338,7 +339,10 @@ def statics(ctx): print("**************************statics*******************************") try: ctx.run("mkdir -p /mnt/volumes/statics/{static,uploads}") - ctx.run(f"python manage.py collectstatic --noinput --settings={_localsettings()}", pty=True) + ctx.run( + f"python manage.py collectstatic --noinput --settings={_localsettings()}", + pty=True, + ) except Exception: import traceback @@ -352,28 +356,6 @@ def prepare(ctx): _prepare_oauth_fixture() ctx.run("rm -rf /tmp/default_site.json", pty=True) _prepare_site_fixture() - # Updating OAuth2 Service Config - new_ext_ip = os.environ["SITEURL"] - client_id = os.environ["OAUTH2_CLIENT_ID"] - client_secret = os.environ["OAUTH2_CLIENT_SECRET"] - oauth_config = "/geoserver_data/data/security/filter/geonode-oauth2/config.xml" - ctx.run(f'sed -i "s|.*|{client_id}|g" {oauth_config}', pty=True) - ctx.run( - f'sed -i "s|.*|{client_secret}|g" {oauth_config}', - pty=True, - ) - ctx.run( - f'sed -i "s|.*|{new_ext_ip}o/authorize/|g" {oauth_config}', # noqa - pty=True, - ) - ctx.run( - f'sed -i "s|.*|{new_ext_ip}geoserver/index.html|g" {oauth_config}', # noqa - pty=True, - ) - ctx.run( - f'sed -i "s|.*|{new_ext_ip}account/logout/|g" {oauth_config}', - pty=True, - ) @task @@ -400,31 +382,36 @@ def fixtures(ctx): def collectstatic(ctx): print("************************static artifacts******************************") ctx.run( - f"django-admin.py collectstatic --noinput \ + f"django-admin collectstatic --noinput \ --settings={_localsettings()}", pty=True, ) -@task -def geoserverfixture(ctx): - print("********************geoserver fixture********************************") - _geoserver_info_provision(f"{os.environ['GEOSERVER_LOCATION']}rest/") - - @task def monitoringfixture(ctx): - print("*******************monitoring fixture********************************") - ctx.run("rm -rf /tmp/default_monitoring_apps_docker.json", pty=True) - _prepare_monitoring_fixture() - try: - ctx.run( - f"django-admin.py loaddata /tmp/default_monitoring_apps_docker.json \ ---settings={_localsettings()}", - pty=True, - ) - except Exception as e: - logger.error(f"ERROR installing monitoring fixture: {str(e)}") + if ast.literal_eval(os.environ.get("MONITORING_ENABLED", "False")): + print("*******************monitoring fixture********************************") + ctx.run("rm -rf /tmp/default_monitoring_apps_docker.json", pty=True) + _prepare_monitoring_fixture() + try: + ctx.run( + f"django-admin loaddata geonode/monitoring/fixtures/metric_data.json \ + --settings={_localsettings()}", + pty=True, + ) + ctx.run( + f"django-admin loaddata geonode/monitoring/fixtures/notifications.json \ + --settings={_localsettings()}", + pty=True, + ) + ctx.run( + f"django-admin loaddata /tmp/default_monitoring_apps_docker.json \ + --settings={_localsettings()}", + pty=True, + ) + except Exception as e: + logger.error(f"ERROR installing monitoring fixture: {str(e)}") @task @@ -432,10 +419,11 @@ def updateadmin(ctx): print("***********************update admin details**************************") ctx.run("rm -rf /tmp/django_admin_docker.json", pty=True) _prepare_admin_fixture( - os.environ.get("ADMIN_PASSWORD", "admin"), os.environ.get("ADMIN_EMAIL", "admin@example.org") + os.environ.get("ADMIN_PASSWORD", "admin"), + os.environ.get("ADMIN_EMAIL", "admin@example.org"), ) ctx.run( - f"django-admin.py loaddata /tmp/django_admin_docker.json \ + f"django-admin loaddata /tmp/django_admin_docker.json \ --settings={_localsettings()}", pty=True, ) @@ -482,6 +470,14 @@ def _docker_host_ip(): return ip_list[0] +def _is_valid_ip(ip): + try: + ipaddress.IPv4Address(ip) + return True + except Exception as e: + return False + + def _container_exposed_port(component, instname): port = "80" try: @@ -490,7 +486,10 @@ def _container_exposed_port(component, instname): [ c.attrs["Config"]["ExposedPorts"] for c in client.containers.list( - filters={"label": f"org.geonode.component={component}", "status": "running"} + filters={ + "label": f"org.geonode.component={component}", + "status": "running", + } ) if str(instname) in c.name ][0] @@ -529,26 +528,16 @@ def _localsettings(): return settings -def _gs_service_availability(url): - import requests - - try: - r = requests.request("get", url, verify=False) - r.raise_for_status() # Raises a HTTPError if the status is 4xx, 5xxx - except (requests.exceptions.ConnectionError, requests.exceptions.Timeout) as e: - logger.error(f"GeoServer connection error is {e}") - return False - except requests.exceptions.HTTPError as er: - logger.error(f"GeoServer HTTP error is {er}") - return False - else: - logger.info("GeoServer API are available!") - return True +def _geonode_public_host(): + gn_pub_hostip = os.getenv("GEONODE_LB_HOST_IP", None) + if not gn_pub_hostip: + gn_pub_hostip = _docker_host_ip() + return gn_pub_hostip def _geonode_public_host_ip(): gn_pub_hostip = os.getenv("GEONODE_LB_HOST_IP", None) - if not gn_pub_hostip: + if not gn_pub_hostip or not _is_valid_ip(gn_pub_hostip): gn_pub_hostip = _docker_host_ip() return gn_pub_hostip @@ -562,33 +551,8 @@ def _geonode_public_port(): return gn_pub_port -def _geoserver_info_provision(url): - from django.conf import settings - from geoserver.catalog import Catalog - - print("Setting GeoServer Admin Password...") - cat = Catalog(url, username=settings.OGC_SERVER_DEFAULT_USER, password=settings.OGC_SERVER_DEFAULT_PASSWORD) - headers = {"Content-type": "application/xml", "Accept": "application/xml"} - data = f""" - - {(os.getenv('GEOSERVER_ADMIN_PASSWORD', 'geoserver'))} -""" - - response = cat.http_request(f"{cat.service_url}/security/self/password", method="PUT", data=data, headers=headers) - print(f"Response Code: {response.status_code}") - if response.status_code == 200: - print("GeoServer admin password updated SUCCESSFULLY!") - else: - logger.warning(f"WARNING: GeoServer admin password *NOT* updated: code [{response.status_code}]") - - def _prepare_oauth_fixture(): upurl = urlparse(os.environ["SITEURL"]) - net_scheme = upurl.scheme - pub_ip = _geonode_public_host_ip() - print(f"Public Hostname or IP is {pub_ip}") - pub_port = _geonode_public_port() - print(f"Public PORT is {pub_port}") default_fixture = [ { "model": "oauth2_provider.application", @@ -598,9 +562,7 @@ def _prepare_oauth_fixture(): "created": "2018-05-31T10:00:31.661Z", "updated": "2018-05-31T11:30:31.245Z", "algorithm": "RS256", - "redirect_uris": f"{net_scheme}://{pub_ip}:{pub_port}/geoserver/index.html" - if pub_port - else f"{net_scheme}://{pub_ip}/geoserver/index.html", + "redirect_uris": f"{urlunparse(upurl)}geoserver/index.html", "name": "GeoServer", "authorization_grant_type": "authorization-code", "client_type": "confidential", @@ -617,7 +579,11 @@ def _prepare_oauth_fixture(): def _prepare_site_fixture(): upurl = urlparse(os.environ["SITEURL"]) default_fixture = [ - {"model": "sites.site", "pk": 1, "fields": {"domain": str(upurl.hostname), "name": str(upurl.hostname)}} + { + "model": "sites.site", + "pk": 1, + "fields": {"domain": str(upurl.hostname), "name": str(upurl.hostname)}, + } ] with open("/tmp/default_site.json", "w") as fixturefile: json.dump(default_fixture, fixturefile) @@ -642,11 +608,19 @@ def _prepare_monitoring_fixture(): d = "1970-01-01 00:00:00" default_fixture = [ { - "fields": {"active": True, "ip": str(geonode_ip), "name": str(os.environ["MONITORING_HOST_NAME"])}, + "fields": { + "active": True, + "ip": str(geonode_ip), + "name": str(os.environ["MONITORING_HOST_NAME"]), + }, "model": "monitoring.host", "pk": 1, }, - {"fields": {"active": True, "ip": str(geoserver_ip), "name": "geoserver"}, "model": "monitoring.host", "pk": 2}, + { + "fields": {"active": True, "ip": str(geoserver_ip), "name": "geoserver"}, + "model": "monitoring.host", + "pk": 2, + }, { "fields": { "name": str(os.environ["MONITORING_SERVICE_NAME"]), diff --git a/uwsgi.ini b/uwsgi.ini index a0f786b7058..c519637152e 100644 --- a/uwsgi.ini +++ b/uwsgi.ini @@ -1,6 +1,7 @@ [uwsgi] -uwsgi-socket = 0.0.0.0:8000 -http-socket = 0.0.0.0:8001 +# uwsgi-socket = 0.0.0.0:8000 +http-socket = 0.0.0.0:8000 +logto = /var/log/geonode.log # pidfile = /tmp/geonode.pid chdir = /usr/src/geonode/ @@ -13,15 +14,12 @@ vacuum = true ; Delete sockets during shutdown single-interpreter = true die-on-term = true ; Shutdown when receiving SIGTERM (default is respawn) need-app = true - -# logging -# path to where uwsgi logs will be saved -logto = /var/log/geonode.log +thunder-lock = true touch-reload = /usr/src/geonode/geonode/wsgi.py buffer-size = 32768 -harakiri = 60 ; forcefully kill workers after 60 seconds +harakiri = 600 ; forcefully kill workers after 600 seconds py-callos-afterfork = true ; allow workers to trap signals max-requests = 1000 ; Restart workers after this many requests @@ -43,4 +41,4 @@ cheaper-busyness-backlog-alert = 16 ; Spawn emergency workers if more than this cheaper-busyness-backlog-step = 2 ; How many emergency workers to create if there are too many requests in the queue # daemonize = /var/log/uwsgi/geonode.log -# cron = -1 -1 -1 -1 -1 /usr/local/bin/python /usr/src/{{project_name}}/manage.py collect_metrics -n +# cron = -1 -1 -1 -1 -1 /usr/local/bin/python /usr/src/geonode/manage.py collect_metrics -n From 0a5f27ea97f93970660aecb258796b66526dc30a Mon Sep 17 00:00:00 2001 From: afabiani Date: Wed, 23 Aug 2023 09:44:43 +0200 Subject: [PATCH 101/111] - Align setup.cfg to requirements.txt --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index 516ea95be42..01f2836b571 100644 --- a/setup.cfg +++ b/setup.cfg @@ -198,6 +198,7 @@ install_requires = mistune==3.0.1 protobuf==3.20.3 mako==1.2.4 + paramiko==3.3.1 # not directly required, fixes Blowfish deprecation warning certifi>=2022.12.7 # not directly required, pinned by Snyk to avoid a vulnerability jwcrypto>=1.4 # not directly required, pinned by Snyk to avoid a vulnerability cryptography>=41.0.0 # not directly required, pinned by Snyk to avoid a vulnerability From c3f275cb8a9ce489df1cdfb612012c5a65bafdf0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Aug 2023 09:45:46 +0200 Subject: [PATCH 102/111] Bump boto3 from 1.28.15 to 1.28.31 (#11401) * Bump boto3 from 1.28.15 to 1.28.31 Bumps [boto3](https://github.com/boto/boto3) from 1.28.15 to 1.28.31. - [Release notes](https://github.com/boto/boto3/releases) - [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/boto3/compare/1.28.15...1.28.31) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * - Align setup.cfg to requirements.txt --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 8e5702f81fa..17219ad44c6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -113,7 +113,7 @@ django-storages==1.13.2 dropbox==11.36.2 google-cloud-storage==2.10.0 google-cloud-core==2.3.3 -boto3==1.28.15 +boto3==1.28.31 # Django Caches python-memcached<=1.59 diff --git a/setup.cfg b/setup.cfg index 01f2836b571..adda03264b7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -138,7 +138,7 @@ install_requires = dropbox==11.36.2 google-cloud-storage==2.10.0 google-cloud-core==2.3.3 - boto3==1.28.15 + boto3==1.28.31 # Django Caches python-memcached<=1.59 From 597edc12acd997070fca1747d76fd6ebacab0299 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Aug 2023 09:47:30 +0200 Subject: [PATCH 103/111] Bump dj-database-url from 2.0.0 to 2.1.0 (#11393) * Bump dj-database-url from 2.0.0 to 2.1.0 Bumps [dj-database-url](https://github.com/jazzband/dj-database-url) from 2.0.0 to 2.1.0. - [Release notes](https://github.com/jazzband/dj-database-url/releases) - [Changelog](https://github.com/jazzband/dj-database-url/blob/master/CHANGELOG.md) - [Commits](https://github.com/jazzband/dj-database-url/compare/v2.0.0...v2.1.0) --- updated-dependencies: - dependency-name: dj-database-url dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * - Align setup.cfg to requirements.txt --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 17219ad44c6..feb024de6ae 100644 --- a/requirements.txt +++ b/requirements.txt @@ -65,7 +65,7 @@ numpy==1.25.* # # Apps with packages provided in GeoNode's PPA on Launchpad. # Django Apps -dj-database-url==2.0.0 +dj-database-url==2.1.0 dj-pagination==2.5.0 django-select2==8.1.2 django-floppyforms<1.10.0 diff --git a/setup.cfg b/setup.cfg index adda03264b7..a1ad378b070 100644 --- a/setup.cfg +++ b/setup.cfg @@ -91,7 +91,7 @@ install_requires = # # Apps with packages provided in GeoNode's PPA on Launchpad. # Django Apps - dj-database-url==2.0.0 + dj-database-url==2.1.0 dj-pagination==2.5.0 django-select2==8.1.2 django-floppyforms<1.10.0 From 3c47799c6103016170f6d7ff479d2c3cb29277d5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Aug 2023 09:47:53 +0200 Subject: [PATCH 104/111] Bump django-grappelli from 3.0.6 to 3.0.7 (#11392) * Bump django-grappelli from 3.0.6 to 3.0.7 Bumps [django-grappelli](https://github.com/sehmaschine/django-grappelli) from 3.0.6 to 3.0.7. - [Changelog](https://github.com/sehmaschine/django-grappelli/blob/master/docs/changelog.rst) - [Commits](https://github.com/sehmaschine/django-grappelli/compare/3.0.6...3.0.7) --- updated-dependencies: - dependency-name: django-grappelli dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * - Align setup.cfg to requirements.txt --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index feb024de6ae..7ee5c632236 100644 --- a/requirements.txt +++ b/requirements.txt @@ -44,7 +44,7 @@ django-downloadview==2.3.0 django-polymorphic==3.1.0 django-tastypie<0.15.0 django-tinymce==3.6.1 -django-grappelli==3.0.6 +django-grappelli==3.0.7 django-uuid-upload-path==1.0.0 django-widget-tweaks==1.4.12 django-sequences==2.8 diff --git a/setup.cfg b/setup.cfg index a1ad378b070..817f5d27c26 100644 --- a/setup.cfg +++ b/setup.cfg @@ -70,7 +70,7 @@ install_requires = django-polymorphic==3.1.0 django-tastypie<0.15.0 django-tinymce==3.6.1 - django-grappelli==3.0.6 + django-grappelli==3.0.7 django-uuid-upload-path==1.0.0 django-widget-tweaks==1.4.12 django-sequences==2.8 From d60c6945303904fcb49961dd58877de4a983f520 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Aug 2023 09:49:12 +0200 Subject: [PATCH 105/111] Bump sqlalchemy from 2.0.19 to 2.0.20 (#11391) * Bump sqlalchemy from 2.0.19 to 2.0.20 Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 2.0.19 to 2.0.20. - [Release notes](https://github.com/sqlalchemy/sqlalchemy/releases) - [Changelog](https://github.com/sqlalchemy/sqlalchemy/blob/main/CHANGES.rst) - [Commits](https://github.com/sqlalchemy/sqlalchemy/commits) --- updated-dependencies: - dependency-name: sqlalchemy dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * - Align setup.cfg to requirements.txt --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 7ee5c632236..875cb274a00 100644 --- a/requirements.txt +++ b/requirements.txt @@ -56,7 +56,7 @@ pyjwt==2.8.0 pyproj<3.7.0 OWSLib==0.29.2 pycsw==2.6.1 -SQLAlchemy==2.0.19 # required by PyCSW +SQLAlchemy==2.0.20 # required by PyCSW Shapely==1.8.5.post1 mercantile==1.2.1 geoip2==4.7.0 diff --git a/setup.cfg b/setup.cfg index 817f5d27c26..bbf44430f3f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -82,7 +82,7 @@ install_requires = pyproj<3.7.0 OWSLib==0.29.2 pycsw==2.6.1 - SQLAlchemy==2.0.19 # required by PyCSW + SQLAlchemy==2.0.20 # required by PyCSW Shapely==1.8.5.post1 mercantile==1.2.1 geoip2==4.7.0 From 027e97365ea9bd65fb8a5dca725bd584294fb111 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Aug 2023 09:49:32 +0200 Subject: [PATCH 106/111] Update setuptools requirement from <68.1.0,>=59.1.1 to >=59.1.1,<68.2.0 (#11390) * Update setuptools requirement from <68.1.0,>=59.1.1 to >=59.1.1,<68.2.0 Updates the requirements on [setuptools](https://github.com/pypa/setuptools) to permit the latest version. - [Release notes](https://github.com/pypa/setuptools/releases) - [Changelog](https://github.com/pypa/setuptools/blob/main/NEWS.rst) - [Commits](https://github.com/pypa/setuptools/compare/v65.5.1...v68.1.0) --- updated-dependencies: - dependency-name: setuptools dependency-type: direct:production ... Signed-off-by: dependabot[bot] * - Align setup.cfg to requirements.txt --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 875cb274a00..66b5d23adc6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -159,7 +159,7 @@ pytest-bdd==6.1.1 splinter==0.19.0 pytest-splinter==3.3.2 pytest-django==4.5.2 -setuptools>=59.1.1,<68.1.0 +setuptools>=59.1.1,<68.2.0 pip==23.2.1 Twisted==22.10.0 pixelmatch==0.3.0 diff --git a/setup.cfg b/setup.cfg index bbf44430f3f..a620e56c964 100644 --- a/setup.cfg +++ b/setup.cfg @@ -184,7 +184,7 @@ install_requires = splinter==0.19.0 pytest-splinter==3.3.2 pytest-django==4.5.2 - setuptools>=59.1.1,<68.1.0 + setuptools>=59.1.1,<68.2.0 pip==23.2.1 Twisted==22.10.0 pixelmatch==0.3.0 From 16cd8c1c31e6e75127ed1c1f283989506604ecc1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Aug 2023 09:49:49 +0200 Subject: [PATCH 107/111] Bump coverage from 7.2.7 to 7.3.0 (#11377) * Bump coverage from 7.2.7 to 7.3.0 Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.2.7 to 7.3.0. - [Release notes](https://github.com/nedbat/coveragepy/releases) - [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst) - [Commits](https://github.com/nedbat/coveragepy/compare/7.2.7...7.3.0) --- updated-dependencies: - dependency-name: coverage dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * - Align setup.cfg to requirements.txt --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 66b5d23adc6..533b56b3c2a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -150,7 +150,7 @@ docker==6.1.3 invoke==2.2.0 # tests -coverage==7.2.7 +coverage==7.3.0 requests-toolbelt==1.0.0 flake8==6.1.0 black==23.7.0 diff --git a/setup.cfg b/setup.cfg index a620e56c964..109aabe8cd2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -175,7 +175,7 @@ install_requires = invoke==2.2.0 # tests - coverage==7.2.7 + coverage==7.3.0 requests-toolbelt==1.0.0 flake8==6.1.0 black==23.7.0 From 6f3fcbb69557b17356f1bd9de1c0aa42016b7beb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Aug 2023 09:50:22 +0200 Subject: [PATCH 108/111] Bump tqdm from 4.65.0 to 4.66.1 (#11375) * Bump tqdm from 4.65.0 to 4.66.1 Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.65.0 to 4.66.1. - [Release notes](https://github.com/tqdm/tqdm/releases) - [Commits](https://github.com/tqdm/tqdm/compare/v4.65.0...v4.66.1) --- updated-dependencies: - dependency-name: tqdm dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * - Align setup.cfg to requirements.txt --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 533b56b3c2a..bde78f9ac4f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,7 +17,7 @@ decorator==5.1.1 celery==5.3.1 kombu==5.3.1 vine==5.0.0 -tqdm==4.65.0 +tqdm==4.66.1 Deprecated==1.2.14 wrapt==1.15.0 jsonschema==4.18.4 diff --git a/setup.cfg b/setup.cfg index 109aabe8cd2..8f1ebdeb3dd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -43,7 +43,7 @@ install_requires = celery==5.3.1 kombu==5.3.1 vine==5.0.0 - tqdm==4.65.0 + tqdm==4.66.1 Deprecated==1.2.14 wrapt==1.15.0 jsonschema==4.18.4 From c01a408bf9cc230cc52b33798de089b9724605fd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Aug 2023 09:50:49 +0200 Subject: [PATCH 109/111] Bump jsonschema from 4.18.4 to 4.19.0 (#11360) * Bump jsonschema from 4.18.4 to 4.19.0 Bumps [jsonschema](https://github.com/python-jsonschema/jsonschema) from 4.18.4 to 4.19.0. - [Release notes](https://github.com/python-jsonschema/jsonschema/releases) - [Changelog](https://github.com/python-jsonschema/jsonschema/blob/main/CHANGELOG.rst) - [Commits](https://github.com/python-jsonschema/jsonschema/compare/v4.18.4...v4.19.0) --- updated-dependencies: - dependency-name: jsonschema dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * - Align setup.cfg to requirements.txt --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index bde78f9ac4f..700eff43551 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,7 +20,7 @@ vine==5.0.0 tqdm==4.66.1 Deprecated==1.2.14 wrapt==1.15.0 -jsonschema==4.18.4 +jsonschema==4.19.0 zipstream-new==1.1.8 schema==0.7.5 rdflib==6.3.2 diff --git a/setup.cfg b/setup.cfg index 8f1ebdeb3dd..4de6c39e998 100644 --- a/setup.cfg +++ b/setup.cfg @@ -46,7 +46,7 @@ install_requires = tqdm==4.66.1 Deprecated==1.2.14 wrapt==1.15.0 - jsonschema==4.18.4 + jsonschema==4.19.0 zipstream-new==1.1.8 schema==0.7.5 rdflib==6.3.2 From 3779a58de795c80935342ab28ecf77a8afa991ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Aug 2023 09:51:18 +0200 Subject: [PATCH 110/111] Bump psycopg2 from 2.9.6 to 2.9.7 (#11330) * Bump psycopg2 from 2.9.6 to 2.9.7 Bumps [psycopg2](https://github.com/psycopg/psycopg2) from 2.9.6 to 2.9.7. - [Changelog](https://github.com/psycopg/psycopg2/blob/master/NEWS) - [Commits](https://github.com/psycopg/psycopg2/compare/2.9.6...2.9.7) --- updated-dependencies: - dependency-name: psycopg2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * - Align setup.cfg to requirements.txt --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 700eff43551..cf3512a3829 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ # native dependencies Pillow==10.0.0 lxml==4.9.3 -psycopg2==2.9.6 +psycopg2==2.9.7 Django==3.2.20 # Other diff --git a/setup.cfg b/setup.cfg index 4de6c39e998..2485f957baf 100644 --- a/setup.cfg +++ b/setup.cfg @@ -27,7 +27,7 @@ install_requires = # native dependencies Pillow==10.0.0 lxml==4.9.3 - psycopg2==2.9.6 + psycopg2==2.9.7 Django==3.2.20 # Other From 4620143f32d25cc4b848cbfe29aca8d2af8e85f2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Aug 2023 09:53:38 +0200 Subject: [PATCH 111/111] Bump boto3 from 1.28.15 to 1.28.32 (#11403) * Bump boto3 from 1.28.15 to 1.28.32 Bumps [boto3](https://github.com/boto/boto3) from 1.28.15 to 1.28.32. - [Release notes](https://github.com/boto/boto3/releases) - [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/boto3/compare/1.28.15...1.28.32) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * - Align setup.cfg to requirements.txt --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: afabiani --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index cf3512a3829..05db52f8a43 100644 --- a/requirements.txt +++ b/requirements.txt @@ -113,7 +113,7 @@ django-storages==1.13.2 dropbox==11.36.2 google-cloud-storage==2.10.0 google-cloud-core==2.3.3 -boto3==1.28.31 +boto3==1.28.32 # Django Caches python-memcached<=1.59 diff --git a/setup.cfg b/setup.cfg index 2485f957baf..41a528efbc4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -138,7 +138,7 @@ install_requires = dropbox==11.36.2 google-cloud-storage==2.10.0 google-cloud-core==2.3.3 - boto3==1.28.31 + boto3==1.28.32 # Django Caches python-memcached<=1.59