feat: geofence-based geographic targeting for programs#76
feat: geofence-based geographic targeting for programs#76
Conversation
…operators
The Operator class only supported Point, LineString, and Polygon types
in domain queries. When shapely's unary_union creates a MultiPolygon
from non-overlapping polygons, the operator validation silently rejected
it, returning SQL("FALSE") and matching zero registrants.
Add ST_GeomFromGeoJSON path for complex geometry types that cannot be
easily constructed from coordinates.
New module that adds geofence-based geographic targeting to programs: - Program-level geofence_ids field on Overview tab - Geofence eligibility manager with hybrid two-tier spatial queries (GPS coordinates + administrative area fallback) - Preview button showing matched registrant count - Composable with other eligibility managers via AND logic
…ibility - Use gis_intersects instead of gis_within for Tier 1 spatial query; gis_within generates ST_Within(value, field) which is backwards for point-in-polygon checks, while gis_intersects is symmetric - Use disabled=None instead of disabled=False in domain (Datetime field) - Use fields.Datetime.now() for disabled test data (not Boolean True) - Use group_ids with Command.link() for Odoo 19 compatibility in tests
- Escape single quotes in create_from_geojson to prevent SQL injection - Make preview_count/preview_error regular fields instead of computed; spatial queries now only run when the Preview button is clicked - Use elif instead of two independent if statements for target_type - Simplify _import_registrants loop to list comprehension
…ent UI - Add fallback_area_type_id field to restrict Tier 2 area fallback to a specific administrative level (e.g. District), preventing overly broad matches from large provinces or regions - Add geofence list/form/search views with menu under Area top-level, so users can browse and manage geofences independently - Allow inline geofence creation from the program form - Add 3 tests for area type filter behavior
When no MapTiler API key is configured, the map widget now falls back to OpenStreetMap raster tiles instead of failing silently. This makes the GIS features work out of the box without requiring a third-party API key. Users who want vector tiles can still configure a MapTiler key.
…back - Fix WebGL context leak: destroy previous map before creating new one in renderMap() to prevent accumulating WebGL contexts on onPatched - Fix draw control stacking: remove previous MapboxDraw control before adding a new one in addDrawInteraction() - Fix removeSourceAndLayer: remove all three layer IDs (polygon, point, linestring) instead of the source ID which doesn't match any layer - Remove console.log debug statements from updateArea and onTrash - Remove hardcoded laos_farm.png placeholder popup on polygon click - Fix SQL injection in create_from_geojson: use SQL() with bound parameters instead of manual string escaping - Apply OSM raster tile fallback to gis_renderer (matching edit widget) - Guard GeocodingControl behind API key check in renderer - Fix early-return-in-loop in eligibility manager methods with ensure_one() - Log exceptions in preview instead of silently swallowing them - Use efficient set lookup for beneficiary exclusion - Use Command.set()/Command.clear() instead of tuple syntax in tests
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces a robust geofence-based geographic targeting system for programs, allowing for precise and flexible definition of eligibility zones. It significantly enhances the underlying GIS infrastructure by adding support for complex geometric types and improving the reliability and user experience of map widgets. These changes collectively empower programs with more advanced spatial management tools and ensure a more secure and resilient mapping environment. Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces a major new feature for geofence-based geographic targeting, including a new spp_program_geofence module and extensive tests. It also enhances the spp_gis module by adding support for MultiPolygon and GeometryCollection, and crucially fixes a potential SQL injection vulnerability by using parameterized queries. The map widgets are improved by making the MapTiler API key optional with an OpenStreetMap fallback, and several pre-existing bugs related to WebGL context leaks and control stacking are fixed. The changes are well-structured and of high quality. I have a few suggestions to improve maintainability and robustness.
| function updateArea(e) { | ||
| console.log(e); | ||
| var data = self.draw.getAll(); | ||
| self.props.record.update({ | ||
| [self.props.name]: JSON.stringify(data.features[0].geometry), | ||
| }); | ||
| } |
There was a problem hiding this comment.
The updateArea function assumes that self.draw.getAll().features will always contain at least one feature. If it's empty for any reason, data.features[0] will be undefined and accessing .geometry will cause a runtime error. It's safer to add a check.
Additionally, the draw.create and draw.update events pass the affected features in the event object e, which is more direct and efficient to use than calling getAll().
function updateArea(e) {
const features = e.features;
if (features && features.length > 0) {
self.props.record.update({
[self.props.name]: JSON.stringify(features[0].geometry),
});
}
}| if ben_count < 1000: | ||
| self._import_registrants(new_beneficiaries, state=state, do_count=True) | ||
| else: | ||
| self._import_registrants_async(new_beneficiaries, state=state) |
There was a problem hiding this comment.
The threshold 1000 for switching to asynchronous import, and the chunk size 10000 used in _import_registrants_async (line 203), are magic numbers. It would be better to define them as constants at the class or module level, for example ASYNC_IMPORT_THRESHOLD = 1000 and IMPORT_CHUNK_SIZE = 10000. This improves readability and makes these values easier to find and adjust.
| _getMapStyle() { | ||
| if (this.mapTilerKey) { | ||
| return maptilersdk.MapStyle.STREETS; | ||
| } | ||
| // Fallback: OSM raster tiles (no API key required) | ||
| return { | ||
| version: 8, | ||
| sources: { | ||
| osm: { | ||
| type: "raster", | ||
| tiles: ["https://tile.openstreetmap.org/{z}/{x}/{y}.png"], | ||
| tileSize: 256, | ||
| attribution: | ||
| '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors', | ||
| }, | ||
| }, | ||
| layers: [ | ||
| { | ||
| id: "osm-tiles", | ||
| type: "raster", | ||
| source: "osm", | ||
| minzoom: 0, | ||
| maxzoom: 19, | ||
| }, | ||
| ], | ||
| }; | ||
| } |
There was a problem hiding this comment.
The code to generate the fallback OpenStreetMap map style object is duplicated from spp_gis/static/src/js/views/gis/gis_renderer/gis_renderer.esm.js. To improve maintainability and avoid inconsistencies, this object could be defined as a constant or returned by a utility function in a shared file, and then imported in both field_gis_edit_map.esm.js and gis_renderer.esm.js.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## 19.0 #76 +/- ##
==========================================
+ Coverage 66.31% 66.36% +0.05%
==========================================
Files 369 374 +5
Lines 22311 22467 +156
==========================================
+ Hits 14795 14911 +116
- Misses 7516 7556 +40
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
The default system parameter value "YOUR_MAPTILER_API_KEY_HERE" was being returned as a valid key, causing 403 errors from MapTiler instead of falling back to OSM tiles.
renderMap() was overriding the OSM fallback style with a MapTiler style reference when defaultRaster was set, even without an API key. Guard the raster style override behind mapTilerKey check.
…nu order The GeoPolygonField edit widget requires a GIS view (ir.ui.view with type=gis) with data and raster layers to render the map. Without it, opening a geofence form raised "No GIS view defined". Also moved the Geofences menu item to sequence 200 so it appears last in the Area menu.
When renderMap() destroys the old map, the MapboxDraw control's internal map reference becomes null. Later, addDrawInteraction() tried to removeControl(this.draw) from the new map, but the draw control called this.map.off() on its now-null internal reference. Fix: set this.draw = null before map.remove() so addDrawInteraction skips the removeControl call for stale controls.
Summary
spp_program_geofencemodule: geofence-based eligibility manager for programs with hybrid two-tier spatial targeting (GPS coordinates + administrative area fallback)spp_gisoperators, enablingunary_unionof multiple geofencesNew module:
spp_program_geofencePrograms can now define geographic scope via geofences on the Overview tab. The geofence eligibility manager uses a two-tier approach:
ST_Intersects)Includes standalone geofence management UI under the Area menu, preview button on the manager form, and 23 tests covering all spatial query paths.
spp_gisimprovementsoperators.py: MultiPolygon/GeometryCollection support viaST_GeomFromGeoJSONwith proper SQL parameterizationfield_gis_edit_map.esm.js: Fixed WebGL context leak ononPatched, draw control stacking,removeSourceAndLayerbug, removed debugconsole.logand placeholder image popupgis_renderer.esm.js: Added OSM fallback, conditional geocoding controlTest plan
./spp t spp_gis(86 passed)./spp t spp_program_geofence(23 passed)ruff,ruff-format,prettier)