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
10 changes: 8 additions & 2 deletions farm_pack_demo/__manifest__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"name": "Farm Pack Demo",
"summary": "Showcase dataset for the farm pack — partners, products, "
"coops, CSA, routes",
"version": "19.0.1.0.0",
"coops, CSA, routes, fields with polygons, observations, fences, water",
"version": "19.0.1.1.0",
"license": "AGPL-3",
"author": "Ledo Enterprises, Odoo Community Association (OCA)",
"website": "https://github.com/ledoent/farm-pack",
Expand All @@ -11,6 +11,8 @@
"farm_pack",
],
"data": [],
# Demo files must load in dependency order — partners first (referenced
# by everyone), then geo fields (referenced by observation/water/fence).
"demo": [
"demo/res_partner_demo.xml",
"demo/product_demo.xml",
Expand All @@ -20,6 +22,10 @@
"demo/farm_delivery_demo.xml",
"demo/farm_market_demo.xml",
"demo/farm_qbo_demo.xml",
"demo/farm_field_geo_demo.xml",
"demo/farm_observation_demo.xml",
"demo/farm_water_source_demo.xml",
"demo/farm_fence_demo.xml",
],
"installable": True,
"application": False,
Expand Down
66 changes: 66 additions & 0 deletions farm_pack_demo/demo/farm_fence_demo.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Three demo fences:
- perimeter: closed linestring wrapping the demo farm cluster,
no field_id (perimeter-style, spans Alice + Bob);
- paddock_divider: separates North 40 from South Pasture, ties to
North 40 as primary field;
- garden_enclosure: tight loop around the East Garden veg plot.
-->
<odoo noupdate="1">
<!-- Perimeter fence — large rectangle around the whole demo farm. -->
<record id="farm_fence_demo_perimeter" model="farm.fence">
<field name="name">Perimeter (entire farm)</field>
<field name="fence_type">high_tensile</field>
<field name="height_inches">48</field>
<field name="condition">good</field>
<field
name="last_checked_date"
eval="(DateTime.now() - relativedelta(weeks=3)).date()"
/>
<field
name="geom"
>SRID=4326;LINESTRING(-79.2415 40.2350, -79.2345 40.2350, -79.2345 40.2485, -79.2415 40.2485, -79.2415 40.2350)</field>
<field
name="notes"
>Replaced two corner braces during spring walk-through. Northeast corner stays an inspection priority — runoff erodes the bank there each spring.</field>
</record>

<!-- Paddock divider between North 40 and South Pasture. -->
<record id="farm_fence_demo_paddock_divider" model="farm.fence">
<field name="name">North 40 / South Pasture divider</field>
<field name="field_id" ref="farm_field_demo_north_40" />
<field name="fence_type">polywire</field>
<field name="height_inches">36</field>
<field name="condition">fair</field>
<field
name="last_checked_date"
eval="(DateTime.now() - relativedelta(months=2)).date()"
/>
<field
name="geom"
>SRID=4326;LINESTRING(-79.2410 40.2410, -79.2370 40.2410)</field>
<field
name="notes"
>Polywire for rotational grazing. Energizer reads strong; insulator at the midpoint is cracked, swap before next rotation.</field>
</record>

<!-- Garden enclosure — woven wire around the veg plot. -->
<record id="farm_fence_demo_garden_enclosure" model="farm.fence">
<field name="name">East Garden deer fence</field>
<field name="field_id" ref="farm_field_demo_east_garden" />
<field name="fence_type">woven_wire</field>
<field name="height_inches">96</field>
<field name="condition">repair</field>
<field
name="last_checked_date"
eval="(DateTime.now() - relativedelta(weeks=1)).date()"
/>
<field
name="geom"
>SRID=4326;LINESTRING(-79.2370 40.2425, -79.2361 40.2425, -79.2361 40.2434, -79.2370 40.2434, -79.2370 40.2425)</field>
<field
name="notes"
>Deer pushed under the south corner; needs an extra ground stake and a hot wire strand along the base before tomato plants go in next month.</field>
</record>
</odoo>
62 changes: 62 additions & 0 deletions farm_pack_demo/demo/farm_field_geo_demo.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Four demo farm fields with WGS84 polygon boundaries, centered around
Ligonier PA (40.2421°N, 79.2389°W) per
farm-pack/docs/farm-geo-implementation-plan.md line 145.

Polygon sizes are tuned so the Albers-Equal-Area acreage compute lands
near the conceptual "40 / 25 / 2 / 5 ac" headlines documented in the
field names. At 40°N a 0.01° degree of longitude is ~85 km and a
0.01° degree of latitude is ~111 km, so a 0.004° × 0.004° square is
roughly 37 acres.
-->
<odoo noupdate="1">
<!-- Alice's 40-acre corn plot, north of the farmhouse. -->
<record id="farm_field_demo_north_40" model="farm.field">
<field name="name">North 40</field>
<field name="code">N-40</field>
<field name="farm_partner_id" ref="farm_pack_demo.partner_demo_alice" />
<field name="crop_id" ref="farm_crop.crop_corn" />
<field
name="geom"
>SRID=4326;POLYGON((-79.2410 40.2440, -79.2370 40.2440, -79.2370 40.2480, -79.2410 40.2480, -79.2410 40.2440))</field>
<field name="organic_certified" eval="False" />
</record>

<!-- Alice's 25-acre pasture, south of the farmhouse. Alfalfa as the
closest seeded crop to "pasture" in the catalog. -->
<record id="farm_field_demo_south_pasture" model="farm.field">
<field name="name">South Pasture</field>
<field name="code">S-PAS</field>
<field name="farm_partner_id" ref="farm_pack_demo.partner_demo_alice" />
<field name="crop_id" ref="farm_crop.crop_alfalfa" />
<field
name="geom"
>SRID=4326;POLYGON((-79.2410 40.2350, -79.2380 40.2350, -79.2380 40.2380, -79.2410 40.2380, -79.2410 40.2350))</field>
<field name="organic_certified" eval="True" />
</record>

<!-- Bob's 2-acre mixed-vegetable garden, just east of the farmhouse. -->
<record id="farm_field_demo_east_garden" model="farm.field">
<field name="name">East Garden</field>
<field name="code">E-GAR</field>
<field name="farm_partner_id" ref="farm_pack_demo.partner_demo_bob" />
<field name="crop_id" ref="farm_crop.crop_lettuce" />
<field
name="geom"
>SRID=4326;POLYGON((-79.2370 40.2425, -79.2361 40.2425, -79.2361 40.2434, -79.2370 40.2434, -79.2370 40.2425))</field>
<field name="organic_certified" eval="True" />
</record>

<!-- Bob's 5-acre berry patch up on the hill. -->
<record id="farm_field_demo_berry_hill" model="farm.field">
<field name="name">Berry Hill</field>
<field name="code">B-HILL</field>
<field name="farm_partner_id" ref="farm_pack_demo.partner_demo_bob" />
<field name="crop_id" ref="farm_crop.crop_strawberry" />
<field
name="geom"
>SRID=4326;POLYGON((-79.2365 40.2420, -79.2350 40.2420, -79.2350 40.2435, -79.2365 40.2435, -79.2365 40.2420))</field>
<field name="organic_certified" eval="True" />
</record>
</odoo>
72 changes: 72 additions & 0 deletions farm_pack_demo/demo/farm_observation_demo.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Six demo observations spread across the four demo fields, mixing
types (pest, disease, water, photo, soil, weed) and urgency levels
so the kanban grouped-by-urgency view has color in every column.
Three carry GeoPoint locations inside their field polygons; the
other three leave geom null to exercise the manual-point-pick path.
-->
<odoo noupdate="1">
<!-- North 40 (corn): aphid pressure on the east edge, high urgency. -->
<record id="farm_observation_demo_north40_aphids" model="farm.observation">
<field name="field_id" ref="farm_field_demo_north_40" />
<field name="observation_type">pest</field>
<field name="urgency">high</field>
<field
name="notes"
>Aphid colony spreading along the east windbreak. Recommend scouting twice this week and consider a beneficial-insect release before the next nymph generation.</field>
<field name="geom">SRID=4326;POINT(-79.2375 40.2460)</field>
</record>

<!-- North 40: low-urgency photo survey. -->
<record id="farm_observation_demo_north40_photo" model="farm.observation">
<field name="field_id" ref="farm_field_demo_north_40" />
<field name="observation_type">photo</field>
<field name="urgency">low</field>
<field
name="notes"
>Routine canopy photo, V8 stage. Stand looks uniform; no obvious nutrient deficiency yet.</field>
</record>

<!-- South Pasture: drainage issue after the storm, medium urgency. -->
<record id="farm_observation_demo_south_drainage" model="farm.observation">
<field name="field_id" ref="farm_field_demo_south_pasture" />
<field name="observation_type">water</field>
<field name="urgency">med</field>
<field
name="notes"
>Water pooling in the southwest corner after Sunday's storm. Subsoil compaction suspected — schedule an aeration pass before fall regrowth.</field>
<field name="geom">SRID=4326;POINT(-79.2406 40.2355)</field>
</record>

<!-- East Garden: powdery mildew on summer squash, high urgency. -->
<record id="farm_observation_demo_garden_mildew" model="farm.observation">
<field name="field_id" ref="farm_field_demo_east_garden" />
<field name="observation_type">disease</field>
<field name="urgency">high</field>
<field
name="notes"
>Powdery mildew breaking out on the summer squash row near the gate. Pull infected leaves tonight, apply potassium bicarbonate spray tomorrow morning before sun.</field>
<field name="geom">SRID=4326;POINT(-79.2367 40.2430)</field>
</record>

<!-- East Garden: low-urgency soil note. -->
<record id="farm_observation_demo_garden_soil" model="farm.observation">
<field name="field_id" ref="farm_field_demo_east_garden" />
<field name="observation_type">soil</field>
<field name="urgency">low</field>
<field
name="notes"
>Soil-test kit pulled samples from rows 1, 4, and 7. Results due Friday.</field>
</record>

<!-- Berry Hill: pigweed escape, medium urgency. -->
<record id="farm_observation_demo_berry_weeds" model="farm.observation">
<field name="field_id" ref="farm_field_demo_berry_hill" />
<field name="observation_type">weed</field>
<field name="urgency">med</field>
<field
name="notes"
>Pigweed coming up between the strawberry rows on the upslope side. Hand-pull this weekend; mulch is thinning in that area.</field>
</record>
</odoo>
41 changes: 41 additions & 0 deletions farm_pack_demo/demo/farm_water_source_demo.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Two demo water sources around the demo farmhouse. The geo field is a
GeoPoint (no GeoMultiGeometry in base_geoengine 19.0 — see
farm_water_source/readme/DESCRIPTION.md for the trade-off).
-->
<odoo noupdate="1">
<!-- The main farmhouse well, serves all of Alice's fields. -->
<record id="farm_water_source_demo_house_well" model="farm.water.source">
<field name="name">Farmhouse Well</field>
<field name="source_type">well</field>
<field
name="field_ids"
eval="[
(4, ref('farm_field_demo_north_40')),
(4, ref('farm_field_demo_south_pasture')),
]"
/>
<field name="capacity_gallons">2000</field>
<field
name="last_tested_date"
eval="(DateTime.now() - relativedelta(months=8)).date()"
/>
<field name="geom">SRID=4326;POINT(-79.2390 40.2421)</field>
<field
name="quality_notes"
>Annual potability test passed last summer. Filter changed in February. Iron staining trending up slightly — recheck mid-season.</field>
</record>

<!-- Frost-free hydrant feeding the pasture trough. -->
<record id="farm_water_source_demo_pasture_trough" model="farm.water.source">
<field name="name">Pasture Trough</field>
<field name="source_type">trough</field>
<field name="field_ids" eval="[(4, ref('farm_field_demo_south_pasture'))]" />
<field name="capacity_gallons">120</field>
<field name="geom">SRID=4326;POINT(-79.2395 40.2365)</field>
<field
name="quality_notes"
>Frost-free hydrant. Trough float-valve serviced last spring; clean monthly during grazing season.</field>
</record>
</odoo>
1 change: 1 addition & 0 deletions farm_pack_demo/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import test_geo_demo_data
90 changes: 90 additions & 0 deletions farm_pack_demo/tests/test_geo_demo_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
from odoo.tests.common import TransactionCase


class TestGeoDemoData(TransactionCase):
"""Smoke-check the geo demo fixtures load and produce sensible values.

Catches XML parse errors, broken xmlid refs, and acreage-compute
regressions on the demo polygons. The thresholds are wide because
Albers reprojection over a small rectangle has noticeable edge
effects — the test is here to detect "10x wrong" not "1% wrong".
"""

def _ref(self, xmlid):
return self.env.ref(f"farm_pack_demo.{xmlid}")

def test_four_demo_fields_exist(self):
fields = self.env["farm.field"].search(
[
(
"name",
"in",
["North 40", "South Pasture", "East Garden", "Berry Hill"],
)
]
)
self.assertEqual(len(fields), 4, "expected 4 named demo fields")

def test_north_40_acreage_in_range(self):
# Polygon at 40°N is roughly 0.004° × 0.004° → ~37 acres. Allow
# a generous band: alarm only if we drop below 20 or above 60.
field = self._ref("farm_field_demo_north_40")
self.assertGreater(field.acres, 20)
self.assertLess(field.acres, 60)

def test_south_pasture_smaller_than_north_40(self):
# The relative ordering is the most useful assertion — exact acreage
# depends on projection details that vary by base_geoengine version.
north = self._ref("farm_field_demo_north_40")
south = self._ref("farm_field_demo_south_pasture")
self.assertGreater(north.acres, south.acres)

def test_east_garden_smallest_field(self):
garden = self._ref("farm_field_demo_east_garden")
# All other demo fields should be larger than the 2-ac veg plot.
for other_xmlid in (
"farm_field_demo_north_40",
"farm_field_demo_south_pasture",
"farm_field_demo_berry_hill",
):
other = self._ref(other_xmlid)
self.assertGreater(other.acres, garden.acres)

def test_six_observations_loaded(self):
# Each demo observation links to one of the four demo fields.
demo_field_ids = (
self.env["farm.field"]
.search(
[
(
"name",
"in",
["North 40", "South Pasture", "East Garden", "Berry Hill"],
)
]
)
.ids
)
obs = self.env["farm.observation"].search([("field_id", "in", demo_field_ids)])
self.assertEqual(len(obs), 6)
# Cover the full urgency selection — each tier should appear.
self.assertEqual(set(obs.mapped("urgency")), {"low", "med", "high"})

def test_water_sources_serve_demo_fields(self):
well = self._ref("farm_water_source_demo_house_well")
self.assertEqual(well.source_type, "well")
self.assertEqual(len(well.field_ids), 2)

def test_three_fences_with_distinct_conditions(self):
fences = [
self._ref("farm_fence_demo_perimeter"),
self._ref("farm_fence_demo_paddock_divider"),
self._ref("farm_fence_demo_garden_enclosure"),
]
conditions = {f.condition for f in fences}
self.assertEqual(conditions, {"good", "fair", "repair"})
# Length compute fires on every fence's GeoLine.
for fence in fences:
self.assertGreater(
fence.length_feet, 0, f"{fence.name} should have positive length"
)
Loading