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", ] 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 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