Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions spp_gis_report/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,84 @@
import logging

from psycopg2 import sql

from . import controllers
from . import models
from . import wizards

_logger = logging.getLogger(__name__)

# Mapping: old boolean field name -> dimension technical name
_BOOL_TO_DIMENSION = {
"disaggregate_by_gender": "gender",
"disaggregate_by_age": "age_group",
"disaggregate_by_disability": "disability_status",
}


def _migrate_boolean_disaggregation(env):
"""Post-init hook: migrate boolean disaggregation flags to dimension_ids.

Looks up reports that had boolean flags set and links them to the
corresponding spp.demographic.dimension records. Logs a warning
(does not crash) if a dimension record is not found.
"""
cr = env.cr

# Check if the old boolean columns still exist
cr.execute(
"""
SELECT column_name FROM information_schema.columns
WHERE table_name = 'spp_gis_report'
AND column_name IN ('disaggregate_by_gender', 'disaggregate_by_age', 'disaggregate_by_disability')
"""
)
existing_columns = {row[0] for row in cr.fetchall()}

if not existing_columns:
_logger.info("No legacy boolean disaggregation columns found, skipping migration")
return

# Get the m2m relation table name
Dimension = env["spp.demographic.dimension"]

for bool_field, dim_name in _BOOL_TO_DIMENSION.items():
if bool_field not in existing_columns:
continue

# Find reports with this boolean set. bool_field is a trusted column
# name from _BOOL_TO_DIMENSION; quote it safely via psycopg2.sql.
query = sql.SQL("SELECT id FROM spp_gis_report WHERE {col} = true").format(col=sql.Identifier(bool_field))
cr.execute(query)
report_ids = [row[0] for row in cr.fetchall()]
if not report_ids:
continue

dimension = Dimension.search([("name", "=", dim_name)], limit=1)
if not dimension:
_logger.warning(
"Dimension '%s' not found, cannot migrate %d reports with %s=True",
dim_name,
len(report_ids),
bool_field,
)
continue

# Insert m2m links (skip duplicates)
for report_id in report_ids:
cr.execute(
"""
INSERT INTO spp_gis_report_spp_demographic_dimension_rel
(spp_gis_report_id, spp_demographic_dimension_id)
VALUES (%s, %s)
ON CONFLICT DO NOTHING
""",
(report_id, dimension.id),
)

_logger.info(
"Migrated %d reports from %s=True to dimension '%s'",
len(report_ids),
bool_field,
dim_name,
)
4 changes: 3 additions & 1 deletion spp_gis_report/__manifest__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "OpenSPP GIS Reports",
"version": "19.0.2.0.1",
"version": "19.0.2.1.0",
"category": "OpenSPP",
"summary": "Geographic visualization and reporting for social protection data",
"author": "OpenSPP.org, OpenSPP",
Expand All @@ -10,6 +10,7 @@
"depends": [
"spp_area",
"spp_gis",
"spp_metric_service",
"spp_registry",
"spp_vocabulary",
"spp_cel_domain",
Expand Down Expand Up @@ -47,6 +48,7 @@
"spp_gis_report/static/src/css/gis_report.css",
],
},
"post_init_hook": "_migrate_boolean_disaggregation",
"installable": True,
"application": False,
"auto_install": False,
Expand Down
30 changes: 13 additions & 17 deletions spp_gis_report/controllers/geojson.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,23 +100,19 @@ def list_reports(self):
for report in reports:
admin_levels = report.data_ids.mapped("area_level") if report.data_ids else []

report_list.append(
{
"code": report.code,
"name": report.name,
"category": report.category_id.name if report.category_id else None,
"last_refresh": (report.last_refresh.isoformat() if report.last_refresh else None),
"admin_levels_available": sorted(set(admin_levels)),
"has_disaggregation": any(
[
report.disaggregate_by_gender,
report.disaggregate_by_age,
report.disaggregate_by_disability,
report.disaggregate_by_tag_ids,
]
),
}
)
report_data = {
"code": report.code,
"name": report.name,
"category": report.category_id.name if report.category_id else None,
"last_refresh": (report.last_refresh.isoformat() if report.last_refresh else None),
"admin_levels_available": sorted(set(admin_levels)),
"has_disaggregation": bool(report.dimension_ids) or bool(report.disaggregate_by_tag_ids),
}
if report.dimension_ids:
report_data["disaggregation_dimensions"] = [
{"name": d.name, "label": d.label} for d in report.dimension_ids
]
report_list.append(report_data)

return self._json_response({"reports": report_list})

Expand Down
10 changes: 9 additions & 1 deletion spp_gis_report/models/area_ext.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,15 @@ def get_gis_layers(self, view_id=None, view_type="gis", **options):
# Build legend info from thresholds for display purposes only.
thresholds = report.threshold_ids.sorted("sequence")
if thresholds:
layer_dict["report_legend"] = [{"color": t.color, "label": t.label} for t in thresholds]
layer_dict["report_legend"] = [
{
"color": t.color,
"label": t.label,
"min_value": t.min_value,
"max_value": t.max_value,
}
for t in thresholds
]
layer_dict["report_legend_title"] = report.name

# Build pre-computed features for report layer
Expand Down
Loading