Skip to content

refactor: move spp.gis.geofence from spp_api_v2_gis to spp_gis#74

Open
jeremi wants to merge 6 commits into19.0from
improve/spp-simulation
Open

refactor: move spp.gis.geofence from spp_api_v2_gis to spp_gis#74
jeremi wants to merge 6 commits into19.0from
improve/spp-simulation

Conversation

@jeremi
Copy link
Member

@jeremi jeremi commented Mar 6, 2026

Summary

Move the core spp.gis.geofence model from spp_api_v2_gis to spp_gis so other modules can use geofences without pulling in the full API stack (spp_api_v2, spp_aggregation, etc.).

  • New spp_gis/models/geofence.py: Core geofence model with UUID field (replaces DB id in GeoJSON), core types (area_of_interest, custom), tag_ids, GeoJSON methods, and create_from_geojson factory
  • New spp_hazard/models/geofence.py: _inherit extension adding incident_id and hazard_zone geofence type
  • Slimmed spp_api_v2_gis/models/geofence.py: Now just an _inherit extension adding service_area/targeting_area types and incident properties in GeoJSON output
  • ACLs moved from spp_api_v2_gis to spp_gis
  • Tests split: 24 core tests in spp_gis, 4 extension tests in spp_api_v2_gis

Quality improvements over original

  • Batched _compute_area_sqkm into single SQL query (was N+1)
  • psycopg2.sql.Identifier for safe table name interpolation (was string formatting)
  • Narrowed exception handling in to_geojson to specific types
  • Prefetch related fields in to_geojson_collection to avoid N+1
  • Removed redundant created_by_id field (use Odoo's built-in create_uid)
  • Removed mail.thread/mail.activity.mixin (geofences don't need chatter)
  • Fixed missing spp_vocabulary and spp_registry dependencies in spp_gis
  • Fixed spp_gis category to OpenSPP/Core

Test plan

  • ./spp t spp_gis passes (67 tests, 0 failures)
  • ./spp t spp_hazard passes (33 tests, 0 failures)
  • ./spp t spp_api_v2_gis passes (190 tests, 0 failures)
  • Linters pass on all changed files
  • Verify geofence CRUD via API endpoints still works end-to-end
  • Verify QGIS plugin can still create geofences

jeremi added 3 commits March 6, 2026 10:30
Move the core geofence model to spp_gis so other modules can use
geofences without pulling in the full API stack.

Key changes:
- New spp_gis/models/geofence.py with UUID field, core types
  (area_of_interest, custom), tag_ids, and GeoJSON methods
- spp_hazard extends geofence with incident_id and hazard_zone type
- spp_api_v2_gis slimmed to _inherit extension (service_area,
  targeting_area types + incident properties override)
- Use uuid instead of DB id in GeoJSON properties
- Use create_uid instead of custom created_by_id
- Catch psycopg2.Error instead of bare Exception in area compute
- Use self._table instead of hardcoded table name
- Fix spp_gis category to OpenSPP/Core
- Add spp_vocabulary, spp_registry to spp_gis depends
- Add spp_gis to spp_hazard depends
- Move ACL rows from spp_api_v2_gis to spp_gis
- Split tests: core tests in spp_gis, extension tests in spp_api_v2_gis
- Batch _compute_area_sqkm into single SQL query (was N+1)
- Use psycopg2.sql.Identifier for safe table name interpolation
- Narrow exception catch in to_geojson to specific types
- Prefetch tag_ids and create_uid in to_geojson_collection
- Add spp_gis. prefix to geofence ACL model_id references
- Remove redundant field guard in spp_api_v2_gis properties override
@gemini-code-assist
Copy link

Summary of Changes

Hello, 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 undertakes a significant refactoring of the spp.gis.geofence model, extracting its core logic from spp_api_v2_gis into the more fundamental spp_gis module. This architectural change enhances modularity and reusability across the application by decoupling the geofence model from API-specific concerns. The refactoring also incorporates several quality improvements, including optimized database interactions, enhanced security, and a cleaner model design, while ensuring that existing API and hazard-related extensions correctly inherit and build upon the new core model.

Highlights

  • Core Geofence Model Relocation: The core spp.gis.geofence model has been moved from spp_api_v2_gis to spp_gis, promoting modularity and allowing other modules to utilize geofence capabilities without the full API stack dependency.
  • Module Extensions and Inheritance: spp_api_v2_gis and spp_hazard now extend the new core spp.gis.geofence model, adding their specific geofence types (e.g., service_area, targeting_area, hazard_zone) and properties.
  • Performance and Security Enhancements: The _compute_area_sqkm method in the core geofence model has been optimized with batched SQL queries and psycopg2.sql.Identifier for improved performance and security against SQL injection.
  • UUID Integration for GeoJSON: A uuid field has been introduced in the core geofence model, replacing the internal database ID in GeoJSON representations for better external identification and system independence.
  • Model Simplification: Unnecessary Odoo mixins (mail.thread, mail.activity.mixin) and the redundant created_by_id field have been removed from the core geofence model, streamlining its design.
  • ACL and Test Refactoring: Geofence Access Control Lists (ACLs) have been relocated to spp_gis, and tests have been split and refactored to align with the new module structure, with core tests in spp_gis and extension-specific tests in spp_api_v2_gis.
Changelog
  • spp_api_v2_gis/models/geofence.py
    • Refactored to inherit from spp.gis.geofence.
    • Removed core geofence fields, methods, and constraints.
    • Added service_area and targeting_area to geofence_type selection.
    • Extended _get_geojson_properties to include incident_id (UUID) and incident_name.
  • spp_api_v2_gis/security/ir.model.access.csv
    • Removed geofence access control list entries.
  • spp_api_v2_gis/tests/test_geofence_model.py
    • Renamed class to TestGeofenceExtensions.
    • Updated docstring to reflect focus on extensions.
    • Removed core geofence tests, retaining only tests for service_area, targeting_area types, and incident properties in GeoJSON.
    • Removed unused imports (psycopg2, ValidationError).
  • spp_gis/manifest.py
    • Updated module category to OpenSPP/Core.
    • Added spp_vocabulary and spp_registry to dependencies.
  • spp_gis/models/init.py
    • Added import for geofence.py.
  • spp_gis/models/geofence.py
    • Added new file containing the core spp.gis.geofence model.
    • Implemented uuid field for external identification.
    • Defined core fields: name, description, geometry, geofence_type (area_of_interest, custom), tag_ids, active, created_from.
    • Implemented _compute_area_sqkm with batched SQL queries and psycopg2.sql.Identifier.
    • Added constraints for unique active names and valid geometry.
    • Implemented to_geojson, _get_geojson_properties (using UUID), and to_geojson_collection (with prefetching).
    • Implemented create_from_geojson factory method.
    • Removed mail.thread, mail.activity.mixin, and created_by_id (using Odoo's create_uid).
  • spp_gis/security/ir.model.access.csv
    • Added geofence access control list entries.
  • spp_gis/tests/init.py
    • Added import for test_geofence.py.
  • spp_gis/tests/test_geofence.py
    • Added new file containing comprehensive tests for the core spp.gis.geofence model.
    • Covers basic creation, type validation, unique name constraints, geometry requirements, GeoJSON conversion, area computation, UUID generation, and tag handling.
  • spp_hazard/manifest.py
    • Added spp_gis to module dependencies.
  • spp_hazard/models/init.py
    • Added import for geofence.py.
  • spp_hazard/models/geofence.py
    • Added new file to extend the core geofence model with hazard-specific fields.
Activity
  • ./spp t spp_gis tests passed (67 tests, 0 failures).
  • ./spp t spp_hazard tests passed (33 tests, 0 failures).
  • ./spp t spp_api_v2_gis tests passed (190 tests, 0 failures).
  • Linters passed on all changed files.
  • Verification of geofence CRUD via API endpoints and QGIS plugin functionality is pending.
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@codecov
Copy link

codecov bot commented Mar 6, 2026

Codecov Report

❌ Patch coverage is 89.21569% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.22%. Comparing base (624e3cd) to head (ebca8f5).
⚠️ Report is 9 commits behind head on 19.0.

Files with missing lines Patch % Lines
spp_gis/models/geofence.py 87.64% 11 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             19.0      #74      +/-   ##
==========================================
+ Coverage   58.09%   65.22%   +7.13%     
==========================================
  Files         188      309     +121     
  Lines       10797    18825    +8028     
==========================================
+ Hits         6272    12279    +6007     
- Misses       4525     6546    +2021     
Flag Coverage Δ
spp_api_v2_gis 71.52% <100.00%> (-0.72%) ⬇️
spp_area_hdx 81.43% <ø> (?)
spp_base_common 90.26% <ø> (ø)
spp_dci_demo 69.23% <ø> (?)
spp_drims 79.55% <ø> (?)
spp_drims_sl_demo 68.33% <ø> (?)
spp_gis 74.09% <87.77%> (?)
spp_gis_indicators 88.04% <ø> (ø)
spp_gis_report 82.60% <ø> (?)
spp_hazard 92.51% <100.00%> (?)
spp_hxl_area 63.74% <ø> (?)
spp_mis_demo_v2 67.08% <ø> (?)
spp_programs 45.43% <ø> (ø)
spp_registrant_gis 85.71% <ø> (?)
spp_security 66.66% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
spp_api_v2_gis/models/geofence.py 100.00% <100.00%> (+12.19%) ⬆️
spp_gis/__manifest__.py 0.00% <ø> (ø)
spp_gis/models/__init__.py 100.00% <100.00%> (ø)
spp_hazard/__manifest__.py 0.00% <ø> (ø)
spp_hazard/models/__init__.py 100.00% <100.00%> (ø)
spp_hazard/models/geofence.py 100.00% <100.00%> (ø)
spp_gis/models/geofence.py 87.64% <87.64%> (ø)

... and 115 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request is a significant and well-executed refactoring that moves the geofence model into the core spp_gis module. This is a great architectural improvement that will increase reusability. The new implementation includes several notable quality improvements, such as batching SQL queries to fix N+1 problems, using parameterized queries to prevent SQL injection, and prefetching data to optimize performance. My review identified one high-severity issue in spp_api_v2_gis related to unsafe access of a field from a dependent module, which could lead to runtime errors. I also have a suggestion to improve test coverage for the new GeoJSON properties.

Comment on lines +24 to +25
props["incident_id"] = self.incident_id.uuid if self.incident_id and hasattr(self.incident_id, "uuid") else None
props["incident_name"] = self.incident_id.name if self.incident_id else None

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This implementation has two issues:

  1. It will raise an AttributeError if the spp_hazard module is not installed, because the incident_id field will not exist on the model. It's safer to use getattr to access optional fields from other modules.
  2. You're trying to access self.incident_id.uuid, but the spp.hazard.incident model does not have a uuid field. This will cause props['incident_id'] to always be None.

I've suggested a fix that uses getattr for safety and falls back to using id for the incident. If the intent is to use UUIDs, a uuid field should be added to the spp.hazard.incident model.

Suggested change
props["incident_id"] = self.incident_id.uuid if self.incident_id and hasattr(self.incident_id, "uuid") else None
props["incident_name"] = self.incident_id.name if self.incident_id else None
incident = getattr(self, "incident_id", None)
props["incident_id"] = incident.id if incident else None
props["incident_name"] = incident.name if incident else None

Comment on lines +82 to +83
self.assertIsNone(props["incident_id"])
self.assertIsNone(props["incident_name"])

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This test only covers the case where no incident is linked to the geofence. To make the test more robust and ensure properties are correctly serialized, please consider adding a test case where an incident is created and linked to the geofence. This would also help verify that the correct identifier (e.g., id or uuid) is being used.

jeremi added 3 commits March 6, 2026 11:01
…sing tests

Bug fix:
- spp_api_v2_gis _get_geojson_properties used incident_id.uuid but
  spp.hazard.incident has no uuid field; use .code instead

New tests in spp_gis (11 added, 78 total):
- UUID copy=False generates new UUID on copy
- Rename to duplicate active name raises ValidationError
- Reactivate archived record with duplicate name raises ValidationError
- create_from_geojson accepts dict input (not just string)
- Different geometry sizes produce different area_sqkm values
- Empty recordset to_geojson_collection returns empty features
- GeoJSON property values are correct (not just keys present)
- Description defaults to empty string in properties
- geofence_type defaults to "custom" when omitted
- area_sqkm is strictly positive for known test polygon
- Archived geofences excluded from default search

New tests in spp_hazard (5 added, 38 total):
- hazard_zone geofence type via selection_add
- Geofence linked to actual hazard incident
- incident_id ondelete set null behavior
- hazard_zone type label in GeoJSON properties
- Geofence without incident works fine

New test in spp_api_v2_gis (1 added, 191 total):
- Linked incident code and name appear in GeoJSON properties
…drims

In Odoo 19, SQL constraint violations (models.Constraint, required=True)
raise psycopg2.IntegrityError at the ORM level, not ValidationError.
The conversion to ValidationError happens in the RPC layer, not during
direct create() calls in tests.

Fixes 5 pre-existing test failures:
- spp_area_hdx: test_hdx_pcode_unique_constraint
- spp_area_hdx: test_required_fields (source_id NOT NULL)
- spp_area_hdx: test_unique_country_constraint
- spp_drims: test_4w_wizard_requires_incident
- spp_drims: test_urgency_state_resolved (missing resolution notes)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant