From 5f2a54a87e5e27b20d4f4f21aef9a860f9d5322a Mon Sep 17 00:00:00 2001 From: emjay0921 Date: Fri, 6 Feb 2026 15:19:43 +0800 Subject: [PATCH 1/3] fix(spp_area_hdx): derive ISO3 code from hdx_dataset_id --- spp_area_hdx/models/hdx_cod_source.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/spp_area_hdx/models/hdx_cod_source.py b/spp_area_hdx/models/hdx_cod_source.py index f09ff46..8545793 100644 --- a/spp_area_hdx/models/hdx_cod_source.py +++ b/spp_area_hdx/models/hdx_cod_source.py @@ -40,11 +40,18 @@ class HdxCodSource(models.Model): _country_unique = models.Constraint("UNIQUE(country_id)", "Only one COD source per country is allowed") - @api.depends("country_id") + @api.depends("country_id", "hdx_dataset_id") def _compute_country_iso3(self): - """Compute ISO3 code from country.""" + """Compute ISO3 code from HDX dataset ID or country. + + HDX dataset IDs follow the pattern ``cod-ab-{iso3}`` (e.g., ``cod-ab-phl``), + which contains the correct 3-letter ISO code. Falls back to the 2-letter + ``res.country.code`` when the dataset ID is not set. + """ for record in self: - if record.country_id: + if record.hdx_dataset_id and record.hdx_dataset_id.startswith("cod-ab-"): + record.country_iso3 = record.hdx_dataset_id[7:].upper() + elif record.country_id: record.country_iso3 = record.country_id.code else: record.country_iso3 = False @@ -63,7 +70,12 @@ def action_sync_from_hdx(self): try: client = HdxClient() - dataset = client.search_cod_datasets(self.country_iso3) + + # Use existing dataset ID if available, otherwise search by ISO3 + if self.hdx_dataset_id: + dataset = client.get_dataset(self.hdx_dataset_id) + else: + dataset = client.search_cod_datasets(self.country_iso3) if not dataset: raise UserError(_("No COD dataset found for country %s") % self.country_id.name) From 2d149abfea85f4ab142296f7cffd80d0e09b3502 Mon Sep 17 00:00:00 2001 From: emjay0921 Date: Fri, 6 Feb 2026 15:30:52 +0800 Subject: [PATCH 2/3] chore(spp_area_hdx): promote to Beta, fix pre-commit and tests --- spp_area_hdx/README.rst | 76 +++++++++++++---------- spp_area_hdx/__manifest__.py | 3 +- spp_area_hdx/models/hdx_cod_source.py | 3 +- spp_area_hdx/tests/test_hdx_cod_source.py | 12 ++-- 4 files changed, 54 insertions(+), 40 deletions(-) diff --git a/spp_area_hdx/README.rst b/spp_area_hdx/README.rst index 2f16973..0531393 100644 --- a/spp_area_hdx/README.rst +++ b/spp_area_hdx/README.rst @@ -14,9 +14,9 @@ OpenSPP HDX COD Integration !! source digest: sha256:cf138e08394d940c78e288afd34fd606ac55ac16203d679b29c392b02aa4bdae !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status - :alt: Alpha + :alt: Beta .. |badge2| image:: https://img.shields.io/badge/license-LGPL--3-blue.png :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html :alt: License: LGPL-3 @@ -35,17 +35,17 @@ area lookup using PostGIS spatial queries. Key Capabilities ~~~~~~~~~~~~~~~~ -- Sync COD dataset metadata from HDX API by country -- Auto-detect field mappings from GeoJSON (P-code, name, parent P-code) - using HXL tags -- Import admin boundaries with polygons from HDX or manually uploaded - GeoJSON files -- Match imported features to existing areas by P-code or create new - areas -- GPS-based area lookup using PostGIS ``ST_Contains`` for - point-in-polygon queries -- Standardize area identification with HDX P-codes for inter-agency - coordination +- Sync COD dataset metadata from HDX API by country +- Auto-detect field mappings from GeoJSON (P-code, name, parent P-code) + using HXL tags +- Import admin boundaries with polygons from HDX or manually uploaded + GeoJSON files +- Match imported features to existing areas by P-code or create new + areas +- GPS-based area lookup using PostGIS ``ST_Contains`` for + point-in-polygon queries +- Standardize area identification with HDX P-codes for inter-agency + coordination Key Models ~~~~~~~~~~ @@ -83,10 +83,10 @@ After installing: UI Location ~~~~~~~~~~~ -- **Menu**: Area > Areas > HDX Integration > COD Sources -- **Import**: Area > Areas > HDX Integration > Import COD -- **Area Records**: Extended with HDX P-code field visible in area form - view +- **Menu**: Area > Areas > HDX Integration > COD Sources +- **Import**: Area > Areas > HDX Integration > Import COD +- **Area Records**: Extended with HDX P-code field visible in area form + view Security ~~~~~~~~ @@ -103,27 +103,22 @@ Security Extension Points ~~~~~~~~~~~~~~~~ -- ``spp.area.find_by_coordinates(latitude, longitude, level=None)`` - - Find area containing GPS point -- ``spp.area.find_all_containing(latitude, longitude)`` - Find all - areas in hierarchy containing point -- ``spp.area.find_by_pcode(pcode)`` - Find area by HDX P-code or - fallback to code field -- Inherit ``spp.hdx.cod.source`` to add country-specific dataset - discovery logic -- Inherit ``spp.hdx.cod.import.wizard._process_features()`` to - customize import behavior +- ``spp.area.find_by_coordinates(latitude, longitude, level=None)`` - + Find area containing GPS point +- ``spp.area.find_all_containing(latitude, longitude)`` - Find all areas + in hierarchy containing point +- ``spp.area.find_by_pcode(pcode)`` - Find area by HDX P-code or + fallback to code field +- Inherit ``spp.hdx.cod.source`` to add country-specific dataset + discovery logic +- Inherit ``spp.hdx.cod.import.wizard._process_features()`` to customize + import behavior Dependencies ~~~~~~~~~~~~ ``spp_area``, ``spp_gis`` -.. IMPORTANT:: - This is an alpha version, the data model and design can change at any time without warning. - Only for development or testing purpose, do not use in production. - `More details on development status `_ - **Table of contents** .. contents:: @@ -150,6 +145,23 @@ Authors Maintainers ----------- +.. |maintainer-jeremi| image:: https://github.com/jeremi.png?size=40px + :target: https://github.com/jeremi + :alt: jeremi +.. |maintainer-gonzalesedwin1123| image:: https://github.com/gonzalesedwin1123.png?size=40px + :target: https://github.com/gonzalesedwin1123 + :alt: gonzalesedwin1123 +.. |maintainer-reichie020212| image:: https://github.com/reichie020212.png?size=40px + :target: https://github.com/reichie020212 + :alt: reichie020212 +.. |maintainer-emjay0921| image:: https://github.com/emjay0921.png?size=40px + :target: https://github.com/emjay0921 + :alt: emjay0921 + +Current maintainers: + +|maintainer-jeremi| |maintainer-gonzalesedwin1123| |maintainer-reichie020212| |maintainer-emjay0921| + This module is part of the `OpenSPP/OpenSPP2 `_ project on GitHub. You are welcome to contribute. diff --git a/spp_area_hdx/__manifest__.py b/spp_area_hdx/__manifest__.py index edcc16d..a0f165b 100644 --- a/spp_area_hdx/__manifest__.py +++ b/spp_area_hdx/__manifest__.py @@ -10,7 +10,8 @@ "author": "OpenSPP.org", "website": "https://github.com/OpenSPP/OpenSPP2", "license": "LGPL-3", - "development_status": "Alpha", + "development_status": "Beta", + "maintainers": ["jeremi", "gonzalesedwin1123", "reichie020212", "emjay0921"], "depends": [ "spp_area", "spp_gis", diff --git a/spp_area_hdx/models/hdx_cod_source.py b/spp_area_hdx/models/hdx_cod_source.py index 8545793..586ff36 100644 --- a/spp_area_hdx/models/hdx_cod_source.py +++ b/spp_area_hdx/models/hdx_cod_source.py @@ -100,8 +100,9 @@ def action_sync_from_hdx(self): admin_level = client.detect_admin_level(resource_name) # Check if resource already exists + _rid, _lvl = resource_id, admin_level existing_resource = self.resource_ids.filtered( - lambda r: r.hdx_resource_id == resource_id or r.admin_level == admin_level + lambda r, rid=_rid, lvl=_lvl: r.hdx_resource_id == rid or r.admin_level == lvl ) resource_vals = { diff --git a/spp_area_hdx/tests/test_hdx_cod_source.py b/spp_area_hdx/tests/test_hdx_cod_source.py index e755066..45def6e 100644 --- a/spp_area_hdx/tests/test_hdx_cod_source.py +++ b/spp_area_hdx/tests/test_hdx_cod_source.py @@ -18,8 +18,8 @@ def setUp(self): self.source = self.env.ref("spp_area_hdx.cod_source_lka") def test_compute_country_iso3(self): - """Test ISO3 code computation.""" - self.assertEqual(self.source.country_iso3, "LK") + """Test ISO3 code computation from hdx_dataset_id.""" + self.assertEqual(self.source.country_iso3, "LKA") def test_unique_country_constraint(self): """Test that only one source per country is allowed.""" @@ -31,13 +31,13 @@ def test_unique_country_constraint(self): } ) - @patch("odoo.addons.spp_area_hdx.services.hdx_client.HdxClient.search_cod_datasets") + @patch("odoo.addons.spp_area_hdx.services.hdx_client.HdxClient.get_dataset") @patch("odoo.addons.spp_area_hdx.services.hdx_client.HdxClient.get_geojson_resources") @patch("odoo.addons.spp_area_hdx.services.hdx_client.HdxClient.detect_admin_level") - def test_action_sync_from_hdx(self, mock_detect_level, mock_get_resources, mock_search): + def test_action_sync_from_hdx(self, mock_detect_level, mock_get_resources, mock_get_dataset): """Test syncing resources from HDX.""" - # Mock HDX API responses - mock_search.return_value = { + # Mock HDX API responses — source has hdx_dataset_id set, so get_dataset is called + mock_get_dataset.return_value = { "name": "cod-ab-lka", "title": "Sri Lanka Administrative Boundaries", } From 135302ca7bb97eca92beb6db575ef46953047e4b Mon Sep 17 00:00:00 2001 From: emjay0921 Date: Fri, 6 Feb 2026 15:41:51 +0800 Subject: [PATCH 3/3] fix(spp_area_hdx): replace magic number with len() for prefix slice --- spp_area_hdx/models/hdx_cod_source.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spp_area_hdx/models/hdx_cod_source.py b/spp_area_hdx/models/hdx_cod_source.py index 586ff36..b278780 100644 --- a/spp_area_hdx/models/hdx_cod_source.py +++ b/spp_area_hdx/models/hdx_cod_source.py @@ -50,7 +50,7 @@ def _compute_country_iso3(self): """ for record in self: if record.hdx_dataset_id and record.hdx_dataset_id.startswith("cod-ab-"): - record.country_iso3 = record.hdx_dataset_id[7:].upper() + record.country_iso3 = record.hdx_dataset_id[len("cod-ab-"):].upper() elif record.country_id: record.country_iso3 = record.country_id.code else: