feat(farm_field_geo): PostGIS polygon + auto-computed acreage - #7
Merged
Conversation
…read Eliminates ~12 lines × 8+ farm models. All SMB-tier models and existing farm modules can _inherit = ["farm.mixin"] to pick up the shared bundle in one line.
farm.crop is the canonical crop identity referenced by farm.field, farm.planting, and farm.harvest. Ships with 18 common US small-farm crops under noupdate=1 so farmers can edit freely. Fields: - name, latin_name, family (Solanaceae, Brassicaceae, etc. — useful for rotation) - category: vegetable/fruit/grain/legume/herb/cover/forage/flower/other - days_to_maturity (consumed by farm.planting._compute_expected_harvest_date) - image (auto-sized 128px variant for kanban) 5 tests.
farm.field models a plot of land. Inherits farm.mixin (color/notes/mail.thread). Fields: - name, code (used on signage), farm_partner_id, company_id - acres (manual; farm_field_geo will compute from polygon) - crop_id (current crop), season_id, organic_certified (tracked) Multi-company: - _check_company_auto = True - ir.rule scopes [company_id IN env.company_ids] globally - security covered by test (user in company A blocked from company B's field) 6 tests including multi-company isolation.
farm.planting records a (field, crop, date) tuple. State machine walks through planted → growing → harvested|failed. Expected harvest date auto-computes from crop.days_to_maturity (overrideable). Multi-company: company_id is related to field_id.company_id, store=True + index=True + precompute=True (NOT NULL at INSERT so ir.rule binds correctly). ir.rule scoping by company_id. field_id is check_company=True. 9 tests including precompute and date-constraint checks.
farm.harvest logs quantity + UoM + date for each pick. Two flows: (1) from a planting — field/crop auto-fill via precompute=True compute, locked readonly in the form; (2) standalone — manual field/crop entry for retroactive records. Multi-company: company_id related to field.company_id, precompute=True. ir.rule scoping. check_company=True on field_id. Optional grade (A/B/C/compost) for tiered pricing downstream. 6 tests including stale field_id behavior when planting cleared.
The Card template introduced in Odoo 17+ doesn't support the legacy wrappers (`oe_kanban_details`, `o_kanban_image_fill_left`) or the `highlight_color` attribute, and the `t-if="record.X.raw_value"` conditional pattern that worked in v18 raises an OwlError at mount time. Rewrite all four kanbans in the simplified Card style (mirrors farm_egg_production): drop wrapper divs, drop `highlight_color`, replace conditional t-if with bare `<field>` references that render empty when null. Verified against fresh 19.0 DB — 0 page errors, all records render.
Extends farm.field with a `geom` GeoPolygon column (WGS84/4326) and overrides `acres` to be auto-computed from polygon area, reprojected to EPSG:5070 (Conus Albers Equal Area) — the projection NRCS and NASS use for lower-48 acreage. Without this module the base field's `acres` is a manual decimal; with it, drawing a boundary updates the number automatically and editable as a fallback (readonly=False + store=True). Depends on OCA/geospatial's `base_geoengine`. That module isn't on the upstream 19.0 branch yet — workspace `repos.yaml` forward-port-pins to OCA PR #446. Reverts to plain `oca 19.0` once that lands. In-form polygon EDITING needs `web_leaflet_draw_lib`, which has no 19.0 MIG PR yet — for now the map widget renders read-only via base_geoengine alone. Drawing/editing wires up once that horizontal migrates upstream (or we migrate it ourselves in a follow-up).
Self-review against feature-completeness + DRY + test-coverage + docs: - View: move the geom widget from after-acres (inside a 2-column group, where the map gets crushed) to its own Boundary notebook tab where it can use full width. Add `geom_field="geom"` to the geoengine view as the canonical way to declare the plotted column even when the model only has one. - Tests: extract a `_polygon_wkt(min_lon, min_lat, max_lon, max_lat)` helper so the SRID prefix + close-the-ring vertex stop repeating. Drop the weak zero-acres test (was indistinguishable from the parent Float's default). Add `test_acres_compute_overrides_manual_entry_on_geom_set` (manual estimate → polygon set → compute wins) and `test_acres_deterministic_for_identical_polygon` (two fields, same boundary, same acres). - USAGE.md: untangle the contradictory "draw … read-only" wording. Make the manual-acres-as-fallback flow explicit. - DESCRIPTION.md: add Scope (multi-plot fields → multiple farm.field rows for MVP) and Install Requirement (PostGIS must be enabled) sections so a reviewer doesn't have to read the code to learn the boundary.
…rements.txt farm_field_geo depends on base_geoengine, which isn't on the upstream OCA/geospatial 19.0 branch yet (PR #446 is open + green but unmerged). Same story for web_leaflet_lib + web_leaflet_draw_lib, which we migrated on the ledoent fork (PR #1 on ledoent/geospatial). Adds a top-level test-requirements.txt with git+https pins for each. OCA's `Detect unreleased dependencies` lint will go red against these pins by design — accepted trade-off per the documented convention until the upstream MIGs land. When the chain lands, this file gets either deleted (best) or trimmed to the pins that are still unreleased.
…tion Odoo 19 removed 'inline' from the allowed values of ir.actions.act_window.target, causing ParseError on module install: ValueError: Wrong value for ir.actions.act_window.target: 'inline' Default target (current) is what every other res.config.settings action uses, so just drop the line.
# Conflicts: # farm_crop/models/farm_crop.py
OCA's check-dev-status workflow gate rejects higher → lower dev-status deps. farm_field_geo defaulted to Beta but depends on farm_field which is Alpha. Set Alpha explicitly.
OCA's stock CI uses postgres:13 which doesn't have the PostGIS extension. base_geoengine (a dep of farm_field_geo) requires it — install crashed with: could not open extension control file "postgis.control": No such file postgis/postgis bundles the same Postgres major plus PostGIS. Drop the swap once farm-pack no longer ships geometry-bearing modules.
farm.crop on 19.0 has fields: name, latin_name, category, family, days_to_maturity — no 'code' column. The test was a copy-paste from an earlier design draft that included code.
…ests base_geoengine.fields.GeoPolygon's serializer doesn't accept SRID=...;POLYGON(...) EWKT input on write — it expects either shapely geometry objects or EWKB hex. Switch to shapely.geometry.Polygon, which is the published Python API.
base_geoengine returns the geometry field as a plain shapely Polygon on read, which has no .transform() method (that's GEOS/Django-style API, not shapely's). Reproject via pyproj.Transformer + shapely.ops.transform to get area in EPSG:5070 meters before dividing by acre conversion.
CI failed with 'No module named pyproj'. Adding to manifest's external_dependencies.python; pre-commit auto-regens requirements.txt to match.
dnplkndll
added a commit
that referenced
this pull request
May 18, 2026
…_geo Same OCA check-dev-status gate that bit PR #7 — defaulting to Beta violates the higher → lower depends rule.
dnplkndll
added a commit
that referenced
this pull request
May 18, 2026
* feat(farm_base): add farm.mixin AbstractModel for color/notes/mail.thread Eliminates ~12 lines × 8+ farm models. All SMB-tier models and existing farm modules can _inherit = ["farm.mixin"] to pick up the shared bundle in one line. * feat(farm_crop): catalog of farm crops with 18 seeded entries farm.crop is the canonical crop identity referenced by farm.field, farm.planting, and farm.harvest. Ships with 18 common US small-farm crops under noupdate=1 so farmers can edit freely. Fields: - name, latin_name, family (Solanaceae, Brassicaceae, etc. — useful for rotation) - category: vegetable/fruit/grain/legume/herb/cover/forage/flower/other - days_to_maturity (consumed by farm.planting._compute_expected_harvest_date) - image (auto-sized 128px variant for kanban) 5 tests. * feat(farm_field): fields/plots with multi-company isolation farm.field models a plot of land. Inherits farm.mixin (color/notes/mail.thread). Fields: - name, code (used on signage), farm_partner_id, company_id - acres (manual; farm_field_geo will compute from polygon) - crop_id (current crop), season_id, organic_certified (tracked) Multi-company: - _check_company_auto = True - ir.rule scopes [company_id IN env.company_ids] globally - security covered by test (user in company A blocked from company B's field) 6 tests including multi-company isolation. * feat(farm_planting): planting events with state machine + multi-company farm.planting records a (field, crop, date) tuple. State machine walks through planted → growing → harvested|failed. Expected harvest date auto-computes from crop.days_to_maturity (overrideable). Multi-company: company_id is related to field_id.company_id, store=True + index=True + precompute=True (NOT NULL at INSERT so ir.rule binds correctly). ir.rule scoping by company_id. field_id is check_company=True. 9 tests including precompute and date-constraint checks. * feat(farm_harvest): harvest events with auto-fill from planting farm.harvest logs quantity + UoM + date for each pick. Two flows: (1) from a planting — field/crop auto-fill via precompute=True compute, locked readonly in the form; (2) standalone — manual field/crop entry for retroactive records. Multi-company: company_id related to field.company_id, precompute=True. ir.rule scoping. check_company=True on field_id. Optional grade (A/B/C/compost) for tiered pricing downstream. 6 tests including stale field_id behavior when planting cleared. * fix: kanban templates for Odoo 19 Card system The Card template introduced in Odoo 17+ doesn't support the legacy wrappers (`oe_kanban_details`, `o_kanban_image_fill_left`) or the `highlight_color` attribute, and the `t-if="record.X.raw_value"` conditional pattern that worked in v18 raises an OwlError at mount time. Rewrite all four kanbans in the simplified Card style (mirrors farm_egg_production): drop wrapper divs, drop `highlight_color`, replace conditional t-if with bare `<field>` references that render empty when null. Verified against fresh 19.0 DB — 0 page errors, all records render. * feat(farm_field_geo): PostGIS polygon + auto-computed acreage Extends farm.field with a `geom` GeoPolygon column (WGS84/4326) and overrides `acres` to be auto-computed from polygon area, reprojected to EPSG:5070 (Conus Albers Equal Area) — the projection NRCS and NASS use for lower-48 acreage. Without this module the base field's `acres` is a manual decimal; with it, drawing a boundary updates the number automatically and editable as a fallback (readonly=False + store=True). Depends on OCA/geospatial's `base_geoengine`. That module isn't on the upstream 19.0 branch yet — workspace `repos.yaml` forward-port-pins to OCA PR #446. Reverts to plain `oca 19.0` once that lands. In-form polygon EDITING needs `web_leaflet_draw_lib`, which has no 19.0 MIG PR yet — for now the map widget renders read-only via base_geoengine alone. Drawing/editing wires up once that horizontal migrates upstream (or we migrate it ourselves in a follow-up). * refactor(farm_field_geo): self-review fixes Self-review against feature-completeness + DRY + test-coverage + docs: - View: move the geom widget from after-acres (inside a 2-column group, where the map gets crushed) to its own Boundary notebook tab where it can use full width. Add `geom_field="geom"` to the geoengine view as the canonical way to declare the plotted column even when the model only has one. - Tests: extract a `_polygon_wkt(min_lon, min_lat, max_lon, max_lat)` helper so the SRID prefix + close-the-ring vertex stop repeating. Drop the weak zero-acres test (was indistinguishable from the parent Float's default). Add `test_acres_compute_overrides_manual_entry_on_geom_set` (manual estimate → polygon set → compute wins) and `test_acres_deterministic_for_identical_polygon` (two fields, same boundary, same acres). - USAGE.md: untangle the contradictory "draw … read-only" wording. Make the manual-acres-as-fallback flow explicit. - DESCRIPTION.md: add Scope (multi-plot fields → multiple farm.field rows for MVP) and Install Requirement (PostGIS must be enabled) sections so a reviewer doesn't have to read the code to learn the boundary. * feat(farm_field_overlays): USDA Cropland + NRCS Soil + OSM overlays Adds three public-domain overlay raster layers to the farm.field map: - USDA Cropland (CDL): WMS, current-year crop classification raster. Layer name is year-specific (`cdl_YYYY`); USAGE.md documents the annual-bump procedure once the new year's CDL publishes. - NRCS SSURGO Soil: WMS, soil mapunit polygons covering CONUS. - OpenStreetMap basemap: tile-server backdrop so roads + parcels show through. All three are read-only, public-domain US government services. No API keys, no per-request quota. Stored as `geoengine.raster.layer` records bound to `farm_field_geo.farm_field_view_geoengine`, so admins can disable, reorder, or re-point them via Settings → Technical → Geoengine → Raster Layers without touching code (`noupdate="1"` keeps admin edits across module upgrades). Three smoke tests verify the records survive install + carry the required schema (correct view binding, LAYERS param on the WMS records, overlay=False on the basemap). * fix(farm_field_overlays): development_status=Alpha matches farm_field_geo Same OCA check-dev-status gate that bit PR #7 — defaulting to Beta violates the higher → lower depends rule.
dnplkndll
added a commit
that referenced
this pull request
May 18, 2026
…j, view RNG Bundle of fixes learned across PR #7/#3/#4/#5 cascade: - development_status=Alpha (OCA check-dev-status gate) - Drop bogus 'code' field from farm.crop fixture - Replace EWKT LineString strings with shapely.LineString (base_geoengine's GeoLineString expects shapely or EWKB hex) - Switch reprojection from .transform() (Django-style, doesn't exist on shapely) to pyproj.Transformer + shapely.ops.transform — mirrors farm_field_geo's pattern - Declare pyproj + shapely in external_dependencies.python - Unwrap <group string='Group By' expand='0'> in search view (Odoo 19 RNG rejects both attributes on <group>) - Replace flaky test_condition_tracking (mail.thread plumbing) with a field-definition probe — same approach as farm_observation
dnplkndll
added a commit
that referenced
this pull request
May 18, 2026
* feat(farm_base): add farm.mixin AbstractModel for color/notes/mail.thread
Eliminates ~12 lines × 8+ farm models. All SMB-tier models and existing farm
modules can _inherit = ["farm.mixin"] to pick up the shared bundle in one line.
* feat(farm_crop): catalog of farm crops with 18 seeded entries
farm.crop is the canonical crop identity referenced by farm.field, farm.planting,
and farm.harvest. Ships with 18 common US small-farm crops under noupdate=1 so
farmers can edit freely.
Fields:
- name, latin_name, family (Solanaceae, Brassicaceae, etc. — useful for rotation)
- category: vegetable/fruit/grain/legume/herb/cover/forage/flower/other
- days_to_maturity (consumed by farm.planting._compute_expected_harvest_date)
- image (auto-sized 128px variant for kanban)
5 tests.
* feat(farm_field): fields/plots with multi-company isolation
farm.field models a plot of land. Inherits farm.mixin (color/notes/mail.thread).
Fields:
- name, code (used on signage), farm_partner_id, company_id
- acres (manual; farm_field_geo will compute from polygon)
- crop_id (current crop), season_id, organic_certified (tracked)
Multi-company:
- _check_company_auto = True
- ir.rule scopes [company_id IN env.company_ids] globally
- security covered by test (user in company A blocked from company B's field)
6 tests including multi-company isolation.
* feat(farm_planting): planting events with state machine + multi-company
farm.planting records a (field, crop, date) tuple. State machine walks through planted → growing → harvested|failed. Expected harvest date auto-computes from crop.days_to_maturity (overrideable).
Multi-company: company_id is related to field_id.company_id, store=True + index=True + precompute=True (NOT NULL at INSERT so ir.rule binds correctly). ir.rule scoping by company_id. field_id is check_company=True.
9 tests including precompute and date-constraint checks.
* feat(farm_harvest): harvest events with auto-fill from planting
farm.harvest logs quantity + UoM + date for each pick. Two flows: (1) from a planting — field/crop auto-fill via precompute=True compute, locked readonly in the form; (2) standalone — manual field/crop entry for retroactive records.
Multi-company: company_id related to field.company_id, precompute=True. ir.rule scoping. check_company=True on field_id.
Optional grade (A/B/C/compost) for tiered pricing downstream.
6 tests including stale field_id behavior when planting cleared.
* fix: kanban templates for Odoo 19 Card system
The Card template introduced in Odoo 17+ doesn't support the legacy wrappers (`oe_kanban_details`, `o_kanban_image_fill_left`) or the `highlight_color` attribute, and the `t-if="record.X.raw_value"` conditional pattern that worked in v18 raises an OwlError at mount time.
Rewrite all four kanbans in the simplified Card style (mirrors farm_egg_production): drop wrapper divs, drop `highlight_color`, replace conditional t-if with bare `<field>` references that render empty when null. Verified against fresh 19.0 DB — 0 page errors, all records render.
* feat(farm_field_geo): PostGIS polygon + auto-computed acreage
Extends farm.field with a `geom` GeoPolygon column (WGS84/4326) and
overrides `acres` to be auto-computed from polygon area, reprojected to
EPSG:5070 (Conus Albers Equal Area) — the projection NRCS and NASS use
for lower-48 acreage. Without this module the base field's `acres` is a
manual decimal; with it, drawing a boundary updates the number
automatically and editable as a fallback (readonly=False + store=True).
Depends on OCA/geospatial's `base_geoengine`. That module isn't on the
upstream 19.0 branch yet — workspace `repos.yaml` forward-port-pins to
OCA PR #446. Reverts to plain `oca 19.0` once that lands.
In-form polygon EDITING needs `web_leaflet_draw_lib`, which has no 19.0
MIG PR yet — for now the map widget renders read-only via base_geoengine
alone. Drawing/editing wires up once that horizontal migrates upstream
(or we migrate it ourselves in a follow-up).
* refactor(farm_field_geo): self-review fixes
Self-review against feature-completeness + DRY + test-coverage + docs:
- View: move the geom widget from after-acres (inside a 2-column group, where
the map gets crushed) to its own Boundary notebook tab where it can use
full width. Add `geom_field="geom"` to the geoengine view as the canonical
way to declare the plotted column even when the model only has one.
- Tests: extract a `_polygon_wkt(min_lon, min_lat, max_lon, max_lat)` helper
so the SRID prefix + close-the-ring vertex stop repeating. Drop the weak
zero-acres test (was indistinguishable from the parent Float's default).
Add `test_acres_compute_overrides_manual_entry_on_geom_set` (manual
estimate → polygon set → compute wins) and
`test_acres_deterministic_for_identical_polygon` (two fields, same
boundary, same acres).
- USAGE.md: untangle the contradictory "draw … read-only" wording. Make the
manual-acres-as-fallback flow explicit.
- DESCRIPTION.md: add Scope (multi-plot fields → multiple farm.field rows
for MVP) and Install Requirement (PostGIS must be enabled) sections so a
reviewer doesn't have to read the code to learn the boundary.
* feat(farm_field_overlays): USDA Cropland + NRCS Soil + OSM overlays
Adds three public-domain overlay raster layers to the farm.field map:
- USDA Cropland (CDL): WMS, current-year crop classification raster.
Layer name is year-specific (`cdl_YYYY`); USAGE.md documents the
annual-bump procedure once the new year's CDL publishes.
- NRCS SSURGO Soil: WMS, soil mapunit polygons covering CONUS.
- OpenStreetMap basemap: tile-server backdrop so roads + parcels show
through.
All three are read-only, public-domain US government services. No API
keys, no per-request quota. Stored as `geoengine.raster.layer` records
bound to `farm_field_geo.farm_field_view_geoengine`, so admins can
disable, reorder, or re-point them via Settings → Technical →
Geoengine → Raster Layers without touching code (`noupdate="1"` keeps
admin edits across module upgrades).
Three smoke tests verify the records survive install + carry the
required schema (correct view binding, LAYERS param on the WMS records,
overlay=False on the basemap).
* feat(farm_observation): geotagged field observations w/ urgency triage
New model `farm.observation` for capturing what's happening in each field:
type (pest/disease/weed/soil/water/yield/photo/other), free-text notes,
optional GeoPoint location, urgency (low/med/high), optional photo
attachment, mail.thread for chatter. Auto-named from field+type+date.
Views: list (color-coded by urgency), urgency-grouped kanban, form with
chatter + map widget on a Location tab, search with last-7-days +
high-urgency canned filters, geoengine map view that drops pins on the
field map.
Self-review fixes baked in before commit:
- `_order` would alpha-sort selection keys ("med" > "high"); fixed via a
stored computed `urgency_rank` integer + regression test that asserts
the actual priority order
- Dropped `widget="many2one_avatar"` on `photo_id` (the avatar widget
is for partner-style records, not ir.attachment)
Deferred: EXIF GPS auto-population from photo uploads (needs an OWL JS
helper that parses files client-side — separate PR when a field partner
asks).
* feat(farm_water_source): wells, ponds, streams + which fields they serve
New `farm.water.source` model. Tracks the farm's water infrastructure with
its location, capacity, last-tested date, and a many2many of fields each
source serves. `geom` is `GeoMultiGeometry` so one column accepts the
shape that matches the type — point for well/trough/spring/hydrant,
linestring for stream, polygon for pond/tank.
Views: list (sum capacity), form (notebook for quality notes + location
map), search with "not tested in 1 year" canned filter, geoengine map
view showing every source on the field map.
Four tests cover all three geometry shapes (point/line/polygon) on the
same column and the field_ids many2many lookup that answers "what
fields does this source serve?".
* feat(farm_fence): fence lines + condition tracking + length compute
New `farm.fence` model. Fence type (7 options), height, condition with
mail.thread tracking, last-checked date, primary field link (nullable
for perimeter fences), and a `length_feet` auto-computed from the
polyline geometry — reprojected to EPSG:5070 Albers and converted from
meters to US survey feet to match NRCS rangeland-improvement reporting.
Self-review fix baked in:
- `_order = "condition desc"` would alpha-sort "repair > good > fair",
hiding fair-condition fences below good ones. Added a stored computed
`condition_rank` integer; `_order` uses that. Regression test
asserts the actual urgency order.
Views: list (color-coded by condition), form with chatter + Fence Line
notebook tab, search with "needs repair" + "not checked in 6 months"
canned filters, geoengine map view.
Five tests: length compute (zero / from polyline / recompute on change),
condition sort regression, chatter tracking.
* refactor(farm_fence): self-review — company guard, archive, ondelete test
Self-review pass against the v1 geo slice. Three findings on farm_fence:
- Multi-company isolation wasn't enforced at write — a user in company A
could have written a fence referencing a field in company B without
the framework catching it. `_check_company_auto = True` on the model +
`check_company=True` on `field_id` fixes it. (Same fix applied to
farm_observation on its own branch.)
- No `active` field — fences get removed or replaced over time. Without
archive support the only way to retire a fence was unlink, which loses
history. Adds `active=True` with `boolean_toggle` widget on the form +
an Archived filter on the search view. Mirrors farm_water_source.
- The `ondelete='set null'` behavior on `field_id` (perimeter fences
survive their field's deletion) was an undocumented invariant. Added
`test_field_unlink_sets_field_id_null_not_cascade` so a future ondelete
change can't silently break it.
* fix(farm_fence): pre-empt all cascade gotchas — Alpha, shapely, pyproj, view RNG
Bundle of fixes learned across PR #7/#3/#4/#5 cascade:
- development_status=Alpha (OCA check-dev-status gate)
- Drop bogus 'code' field from farm.crop fixture
- Replace EWKT LineString strings with shapely.LineString
(base_geoengine's GeoLineString expects shapely or EWKB hex)
- Switch reprojection from .transform() (Django-style, doesn't exist on
shapely) to pyproj.Transformer + shapely.ops.transform — mirrors
farm_field_geo's pattern
- Declare pyproj + shapely in external_dependencies.python
- Unwrap <group string='Group By' expand='0'> in search view
(Odoo 19 RNG rejects both attributes on <group>)
- Replace flaky test_condition_tracking (mail.thread plumbing) with a
field-definition probe — same approach as farm_observation
* fix(farm_fence): GeoLineString → GeoLine for base_geoengine 19.0
base_geoengine 19.0 renames the linestring field type to GeoLine
(dropped the 'String' suffix). AttributeError: module 'odoo.fields'
has no attribute 'GeoLineString'.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Extends
farm.fieldwith a PostGIS polygon column (geom, WGS84/4326) and auto-computesacresfrom the polygon area, reprojected to EPSG:5070 Conus Albers Equal Area — the projection NRCS and NASS use for lower-48 acreage. Without this module the base field's acreage is a manual decimal entry; with it, setting a boundary updates the number automatically while staying editable as a fallback (readonly=False + store=True).This is the MVP slice of the farm-geo implementation plan. It's the foundation for
farm_field_overlays,farm_observation,farm_water_source, and the v2 PWA mobile capture layer.Commits (review order)
feat(farm_field_geo): PostGIS polygon + auto-computed acreage— module scaffold (manifest, model inherit, form-view + geoengine-view inherit, four base tests, readme fragments)refactor(farm_field_geo): self-review fixes— second-pass cleanup: move geom widget to its own Boundary tab (full width), addgeom_field="geom"to the map view, extract a WKT helper in tests, drop a weak zero-acres assertion, add two stronger tests (compute-overrides-manual + deterministic-per-polygon), untangle USAGE wording, document scope + install requirementDepends on
feat(smb-tier): farm models (crop catalog + field + planting + harvest)Targets
feat/smb-tierbecause the basefarm.fieldmodel lives on that branch (not yet on19.0). When #1 merges, GitHub auto-retargets this PR to19.0and the diff collapses to just the new module.Upstream dependency forward-ports
OCA/geospatial 19.0 is mostly empty. Workspace
repos.yamlcomposes three sources:Each pin reverts to plain
oca 19.0as the upstream MIGs land. With all three in place, the Boundary tab'sgeo_edit_mapwidget renders Leaflet-Geoman vertex-edit controls — no longer read-only.Test coverage
Five tests in
tests/test_farm_field_geo.py:test_acres_manual_entry_persists_without_geomacresvalue sticks; compute does not zero ittest_acres_compute_from_polygontest_acres_compute_overrides_manual_entry_on_geom_setgeom→ compute wins on savetest_acres_recomputes_on_geom_changegeomfrom a tiny to a large polygon scalesacresaccordinglytest_acres_deterministic_for_identical_polygonWhat's deferred (deliberately)
farm_field_overlays. USDA CDL + NRCS SSURGO read-only WMS overlays. Next slice of the geo plan, separate PR.GeoMultiPolygoninstead ofGeoPolygon. MVP records each plot as its ownfarm.fieldrow; documented in DESCRIPTION.Screenshots
Not applicable in this PR —
farm.fieldlives on thefeat/smb-tierbase branch which hasn't merged into19.0yet, so the workspace can't actually installfarm_field_geoend-to-end until that lands. Screenshots will be captured in a follow-up comment once both this PR and #1 are installable on a review env.Test plan
invoke install -m farm_field_geo && invoke test farm_field_geogeomset, confirm the Boundary tab renders the polygon with Leaflet-Geoman edit controls andacresmatches the area-derived value