From ca5e1420e4ab48e3f16cdfeb972c521ebdc3653c Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 21 Mar 2026 15:03:06 -0500 Subject: [PATCH 1/6] fix: use correct uuid when processing webhook to delete record --- src/wuttafarm/cli/process_webhooks.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/wuttafarm/cli/process_webhooks.py b/src/wuttafarm/cli/process_webhooks.py index 9731247..9d66a70 100644 --- a/src/wuttafarm/cli/process_webhooks.py +++ b/src/wuttafarm/cli/process_webhooks.py @@ -94,8 +94,7 @@ class ChangeProcessor: return # delete corresponding record from our app - obj = importer.get_target_object((change.uuid,)) - if obj: + if obj := importer.get_target_object((change.farmos_uuid,)): importer.delete_target_object(obj) # TODO: this should live elsewhere From cc4b94a7b8c37642e4391a74dc9170c1d1ac7296 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 21 Mar 2026 15:06:26 -0500 Subject: [PATCH 2/6] fix: improve behavior when deleting mirrored record from farmOS in some cases (maybe just dev?) the record does not exist in farmOS; if so we should silently ignore. and there seemed to be a problem with the sequence of events: - user clicks delete in WF - record is deleted from WF DB - delete request sent to farmOS API - webhook on farmOS side calls back to WF webhook URI somewhere in there, in practice things seemed to hang after user clicks delete. i suppose the thread handling user's request is "tied up" somehow, such that the webhook receiver can't process that request? that doesn't exactly make sense to me, but if we split off to a separate thread to request the farmOS deletion, things seem to work okay. so maybe that idea is more accurate than i'd expect --- src/wuttafarm/web/views/master.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/wuttafarm/web/views/master.py b/src/wuttafarm/web/views/master.py index d9fe986..ac9e2ed 100644 --- a/src/wuttafarm/web/views/master.py +++ b/src/wuttafarm/web/views/master.py @@ -23,6 +23,9 @@ Base class for WuttaFarm master views """ +import threading + +import requests from webhelpers2.html import tags from wuttaweb.views import MasterView @@ -145,10 +148,24 @@ class WuttaFarmMasterView(MasterView): # maybe delete from farmOS also if farmos_uuid: - entity_type = self.get_farmos_entity_type() - bundle = self.get_farmos_bundle() client = get_farmos_client_for_user(self.request) + # nb. must use separate thread to avoid some kind of race + # condition (?) - seems as though maybe a "boomerang" + # effect is happening; this seems to help anyway + thread = threading.Thread( + target=self.delete_from_farmos, args=(client, farmos_uuid) + ) + thread.start() + + def delete_from_farmos(self, client, farmos_uuid): + entity_type = self.get_farmos_entity_type() + bundle = self.get_farmos_bundle() + try: client.resource.delete(entity_type, bundle, farmos_uuid) + except requests.HTTPError as exc: + # ignore if record not found in farmOS + if exc.response.status_code != 404: + raise class TaxonomyMasterView(WuttaFarmMasterView): From f0fa189bcdf47b5eeeeaee2ad68bb51e861bfd50 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 21 Mar 2026 15:11:36 -0500 Subject: [PATCH 3/6] =?UTF-8?q?bump:=20version=200.11.0=20=E2=86=92=200.11?= =?UTF-8?q?.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 7 +++++++ pyproject.toml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ee6b10..00ff67e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to WuttaFarm will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## v0.11.1 (2026-03-21) + +### Fix + +- improve behavior when deleting mirrored record from farmOS +- use correct uuid when processing webhook to delete record + ## v0.11.0 (2026-03-15) ### Feat diff --git a/pyproject.toml b/pyproject.toml index c1a5cc0..e165b20 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "hatchling.build" [project] name = "WuttaFarm" -version = "0.11.0" +version = "0.11.1" description = "Web app to integrate with and extend farmOS" readme = "README.md" authors = [ From 969497826d556e68e355cc7f0fda89b353e50e76 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 21 Mar 2026 15:24:36 -0500 Subject: [PATCH 4/6] fix: avoid error if asset has no geometry --- src/wuttafarm/web/views/assets.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/wuttafarm/web/views/assets.py b/src/wuttafarm/web/views/assets.py index 35c3b21..1ada778 100644 --- a/src/wuttafarm/web/views/assets.py +++ b/src/wuttafarm/web/views/assets.py @@ -372,21 +372,23 @@ class AssetMasterView(WuttaFarmMasterView): # TODO: eventually sync GIS data, avoid this API call? client = get_farmos_client_for_user(self.request) result = client.asset.get_id(asset.asset_type, asset.farmos_uuid) - geometry = result["data"]["attributes"]["intrinsic_geometry"] + if geometry := result["data"]["attributes"]["intrinsic_geometry"]: - context["map_center"] = [geometry["lon"], geometry["lat"]] + context["map_center"] = [geometry["lon"], geometry["lat"]] - context["map_bounds"] = [ - [geometry["left"], geometry["bottom"]], - [geometry["right"], geometry["top"]], - ] + context["map_bounds"] = [ + [geometry["left"], geometry["bottom"]], + [geometry["right"], geometry["top"]], + ] - if match := re.match( - r"^POLYGON \(\((?P[^\)]+)\)\)$", geometry["value"] - ): - points = match.group("points").split(", ") - points = [[float(pt) for pt in pair.split(" ")] for pair in points] - context["map_polygon"] = [points] + if match := re.match( + r"^POLYGON \(\((?P[^\)]+)\)\)$", geometry["value"] + ): + points = match.group("points").split(", ") + points = [ + [float(pt) for pt in pair.split(" ")] for pair in points + ] + context["map_polygon"] = [points] return context From 9707c365538373267970065c8319542254e49fac Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 21 Mar 2026 20:18:32 -0500 Subject: [PATCH 5/6] fix: use separate thread to sync changes to farmOS i.e. when creating or editing an asset/log, or submitting quick eggs form --- src/wuttafarm/web/views/master.py | 26 +++++++++++++++-- src/wuttafarm/web/views/quick/eggs.py | 40 ++++++++++++++++++++++++--- 2 files changed, 59 insertions(+), 7 deletions(-) diff --git a/src/wuttafarm/web/views/master.py b/src/wuttafarm/web/views/master.py index ac9e2ed..c828b96 100644 --- a/src/wuttafarm/web/views/master.py +++ b/src/wuttafarm/web/views/master.py @@ -24,6 +24,7 @@ Base class for WuttaFarm master views """ import threading +import time import requests from webhelpers2.html import tags @@ -110,17 +111,36 @@ class WuttaFarmMasterView(MasterView): f.set_readonly("drupal_id") def persist(self, obj, session=None): + session = session or self.Session() # save per usual super().persist(obj, session) # maybe also sync change to farmOS if self.app.is_farmos_mirror(): + if self.creating: + session.flush() # need the new uuid client = get_farmos_client_for_user(self.request) - self.auto_sync_to_farmos(client, obj) + thread = threading.Thread( + target=self.auto_sync_to_farmos, args=(client, obj.uuid) + ) + thread.start() - def auto_sync_to_farmos(self, client, obj): - self.app.auto_sync_to_farmos(obj, client=client, require=False) + def auto_sync_to_farmos(self, client, uuid): + model = self.app.model + model_class = self.get_model_class() + + with self.app.short_session(commit=True) as session: + if user := session.query(model.User).filter_by(username="farmos").first(): + session.info["continuum_user_id"] = user.uuid + + obj = None + while not obj: + obj = session.get(model_class, uuid) + if not obj: + time.sleep(0.1) + + self.app.auto_sync_to_farmos(obj, client=client, require=False) def get_farmos_entity_type(self): if self.farmos_entity_type: diff --git a/src/wuttafarm/web/views/quick/eggs.py b/src/wuttafarm/web/views/quick/eggs.py index 8aae46e..fded73c 100644 --- a/src/wuttafarm/web/views/quick/eggs.py +++ b/src/wuttafarm/web/views/quick/eggs.py @@ -24,6 +24,8 @@ Quick Form for "Eggs" """ import json +import threading +import time import colander from deform.widget import SelectWidget @@ -331,13 +333,43 @@ class EggsQuickForm(QuickFormView): session.flush() if self.app.is_farmos_mirror(): - if new_unit: - self.app.auto_sync_to_farmos(unit, client=self.farmos_client) - self.app.auto_sync_to_farmos(quantity, client=self.farmos_client) - self.app.auto_sync_to_farmos(log, client=self.farmos_client) + thread = threading.Thread( + target=self.auto_sync_to_farmos, + args=(log.uuid, quantity.uuid, new_unit.uuid if new_unit else None), + ) + thread.start() return log + def auto_sync_to_farmos(self, log_uuid, quantity_uuid, new_unit_uuid): + model = self.app.model + + with self.app.short_session(commit=True) as session: + if user := session.query(model.User).filter_by(username="farmos").first(): + session.info["continuum_user_id"] = user.uuid + + if new_unit_uuid: + new_unit = None + while not new_unit: + new_unit = session.get(model.Unit, new_unit_uuid) + if not new_unit: + time.sleep(0.1) + self.app.auto_sync_to_farmos(unit, client=self.farmos_client) + + quantity = None + while not quantity: + quantity = session.get(model.StandardQuantity, quantity_uuid) + if not quantity: + time.sleep(0.1) + self.app.auto_sync_to_farmos(quantity, client=self.farmos_client) + + log = None + while not log: + log = session.get(model.HarvestLog, log_uuid) + if not log: + time.sleep(0.1) + self.app.auto_sync_to_farmos(log, client=self.farmos_client) + def redirect_after_save(self, log): model = self.app.model From a5b699a52ab7f92cae7ef87e2d5eff62d547880c Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 21 Mar 2026 20:21:52 -0500 Subject: [PATCH 6/6] =?UTF-8?q?bump:=20version=200.11.1=20=E2=86=92=200.11?= =?UTF-8?q?.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 7 +++++++ pyproject.toml | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 00ff67e..579fc2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to WuttaFarm will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## v0.11.2 (2026-03-21) + +### Fix + +- use separate thread to sync changes to farmOS +- avoid error if asset has no geometry + ## v0.11.1 (2026-03-21) ### Fix diff --git a/pyproject.toml b/pyproject.toml index e165b20..b702d8c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "hatchling.build" [project] name = "WuttaFarm" -version = "0.11.1" +version = "0.11.2" description = "Web app to integrate with and extend farmOS" readme = "README.md" authors = [ @@ -34,7 +34,7 @@ dependencies = [ "pyramid_exclog", "uvicorn[standard]", "WuttaSync", - "WuttaWeb[continuum]>=0.29.3", + "WuttaWeb[continuum]>=0.30.1", ]