From 985d224cb816c44f153c758848d45400e59152ff Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 14 Feb 2026 14:37:41 -0600 Subject: [PATCH] fix: update sterile, archived flags per farmOS 4.x 3.x should still work okay too though --- src/wuttafarm/app.py | 18 ++++++++ src/wuttafarm/farmos/handler.py | 29 ++++++++++++ src/wuttafarm/importing/farmos.py | 37 +++++++++++++-- src/wuttafarm/web/views/farmos/animals.py | 46 ++++++++++++++----- src/wuttafarm/web/views/farmos/groups.py | 17 +++++-- src/wuttafarm/web/views/farmos/land_assets.py | 17 +++++-- src/wuttafarm/web/views/farmos/master.py | 1 + src/wuttafarm/web/views/farmos/structures.py | 17 +++++-- 8 files changed, 156 insertions(+), 26 deletions(-) diff --git a/src/wuttafarm/app.py b/src/wuttafarm/app.py index 72dd675..9cfe25d 100644 --- a/src/wuttafarm/app.py +++ b/src/wuttafarm/app.py @@ -65,6 +65,24 @@ class WuttaFarmAppHandler(base.AppHandler): handler = self.get_farmos_handler() return handler.get_farmos_client(*args, **kwargs) + def is_farmos_3x(self, *args, **kwargs): + """ + Check if the farmOS version is 3.x. This is a convenience + wrapper around + :meth:`~wuttafarm.farmos.handler.FarmOSHandler.is_farmos_3x()`. + """ + handler = self.get_farmos_handler() + return handler.is_farmos_3x(*args, **kwargs) + + def is_farmos_4x(self, *args, **kwargs): + """ + Check if the farmOS version is 4.x. This is a convenience + wrapper around + :meth:`~wuttafarm.farmos.handler.FarmOSHandler.is_farmos_4x()`. + """ + handler = self.get_farmos_handler() + return handler.is_farmos_4x(*args, **kwargs) + class WuttaFarmAppProvider(base.AppProvider): """ diff --git a/src/wuttafarm/farmos/handler.py b/src/wuttafarm/farmos/handler.py index 78e76b6..6eee14f 100644 --- a/src/wuttafarm/farmos/handler.py +++ b/src/wuttafarm/farmos/handler.py @@ -42,6 +42,35 @@ class FarmOSHandler(GenericHandler): hostname = self.get_farmos_url() return farmOS(hostname, **kwargs) + def get_farmos_version(self, client=None, *args, **kwargs): + """ + Returns the farmOS version in use. + """ + if not client: + client = self.get_farmos_client(*args, **kwargs) + info = client.info() + return info["meta"]["farm"]["version"] + + def is_farmos_3x(self, client=None, *args, **kwargs): + """ + Check if the farmOS version is 3.x. + """ + if not client: + client = self.get_farmos_client(*args, **kwargs) + + version = self.get_farmos_version(client) + return version[0] == "3" + + def is_farmos_4x(self, client=None, *args, **kwargs): + """ + Check if the farmOS version is 4.x. + """ + if not client: + client = self.get_farmos_client(*args, **kwargs) + + version = self.get_farmos_version(client) + return version[0] == "4" + def get_farmos_url(self, path=None, require=True): """ Returns the base URL for farmOS, or one with ``path`` appended. diff --git a/src/wuttafarm/importing/farmos.py b/src/wuttafarm/importing/farmos.py index 4717c78..4acbe24 100644 --- a/src/wuttafarm/importing/farmos.py +++ b/src/wuttafarm/importing/farmos.py @@ -52,6 +52,7 @@ class FromFarmOSHandler(ImportHandler): """ token = self.get_farmos_oauth2_token() self.farmos_client = self.app.get_farmos_client(token=token) + self.farmos_4x = self.app.is_farmos_4x(self.farmos_client) def get_farmos_oauth2_token(self): @@ -74,6 +75,7 @@ class FromFarmOSHandler(ImportHandler): def get_importer_kwargs(self, key, **kwargs): kwargs = super().get_importer_kwargs(key, **kwargs) kwargs["farmos_client"] = self.farmos_client + kwargs["farmos_4x"] = self.farmos_4x return kwargs @@ -241,18 +243,28 @@ class AnimalImporter(FromFarmOS, ToWutta): birthdate = self.app.localtime(birthdate) birthdate = self.app.make_utc(birthdate) + if self.farmos_4x: + sterile = animal["attributes"]["is_sterile"] + else: + sterile = animal["attributes"]["is_castrated"] + if notes := animal["attributes"]["notes"]: notes = notes["value"] + if self.farmos_4x: + active = not animal["attributes"]["archived"] + else: + active = animal["attributes"]["status"] == "active" + return { "farmos_uuid": UUID(animal["id"]), "drupal_id": animal["attributes"]["drupal_internal__id"], "name": animal["attributes"]["name"], "animal_type_uuid": animal_type.uuid, "sex": animal["attributes"]["sex"], - "is_sterile": animal["attributes"]["is_castrated"], + "is_sterile": sterile, "birthdate": birthdate, - "active": animal["attributes"]["status"] == "active", + "active": active, "notes": notes, "image_url": image_url, } @@ -345,13 +357,18 @@ class GroupImporter(FromFarmOS, ToWutta): if notes := group["attributes"]["notes"]: notes = notes["value"] + if self.farmos_4x: + active = not group["attributes"]["archived"] + else: + active = group["attributes"]["status"] == "active" + return { "farmos_uuid": UUID(group["id"]), "drupal_id": group["attributes"]["drupal_internal__id"], "name": group["attributes"]["name"], "is_location": group["attributes"]["is_location"], "is_fixed": group["attributes"]["is_fixed"], - "active": group["attributes"]["status"] == "active", + "active": active, "notes": notes, } @@ -400,6 +417,11 @@ class LandAssetImporter(FromFarmOS, ToWutta): if notes := land["attributes"]["notes"]: notes = notes["value"] + if self.farmos_4x: + active = not land["attributes"]["archived"] + else: + active = land["attributes"]["status"] == "active" + return { "farmos_uuid": UUID(land["id"]), "drupal_id": land["attributes"]["drupal_internal__id"], @@ -407,7 +429,7 @@ class LandAssetImporter(FromFarmOS, ToWutta): "land_type_uuid": land_type.uuid, "is_location": land["attributes"]["is_location"], "is_fixed": land["attributes"]["is_fixed"], - "active": land["attributes"]["status"] == "active", + "active": active, "notes": notes, } @@ -527,6 +549,11 @@ class StructureImporter(FromFarmOS, ToWutta): ): image_url = image_style["large"] + if self.farmos_4x: + active = not structure["attributes"]["archived"] + else: + active = structure["attributes"]["status"] == "active" + return { "farmos_uuid": UUID(structure["id"]), "drupal_id": structure["attributes"]["drupal_internal__id"], @@ -534,7 +561,7 @@ class StructureImporter(FromFarmOS, ToWutta): "structure_type_uuid": structure_type.uuid, "is_location": structure["attributes"]["is_location"], "is_fixed": structure["attributes"]["is_fixed"], - "active": structure["attributes"]["status"] == "active", + "active": active, "notes": notes, "image_url": image_url, } diff --git a/src/wuttafarm/web/views/farmos/animals.py b/src/wuttafarm/web/views/farmos/animals.py index 760ad34..d181a02 100644 --- a/src/wuttafarm/web/views/farmos/animals.py +++ b/src/wuttafarm/web/views/farmos/animals.py @@ -51,7 +51,6 @@ class AnimalView(FarmOSMasterView): labels = { "animal_type": "Species / Breed", - "is_castrated": "Castrated", "location": "Current Location", } @@ -59,8 +58,8 @@ class AnimalView(FarmOSMasterView): "name", "birthdate", "sex", - "is_castrated", - "status", + "is_sterile", + "archived", ] sort_defaults = "name" @@ -70,8 +69,8 @@ class AnimalView(FarmOSMasterView): "animal_type", "birthdate", "sex", - "is_castrated", - "status", + "is_sterile", + "archived", "owners", "location", "notes", @@ -96,6 +95,12 @@ class AnimalView(FarmOSMasterView): # birthdate g.set_renderer("birthdate", "date") + # is_sterile + g.set_renderer("is_sterile", "boolean") + + # archived + g.set_renderer("archived", "boolean") + def get_instance(self): animal = self.farmos_client.resource.get_id( @@ -173,16 +178,30 @@ class AnimalView(FarmOSMasterView): birthdate = datetime.datetime.fromisoformat(birthdate) birthdate = self.app.localtime(birthdate) + sterile = None + if self.farmos_4x: + sterile = animal["attributes"]["is_sterile"] + else: + sterile = animal["attributes"]["is_castrated"] + + if notes := animal["attributes"]["notes"]: + notes = notes["value"] + + if self.farmos_4x: + archived = animal["attributes"]["archived"] + else: + archived = animal["attributes"]["status"] == "archived" + return { "uuid": animal["id"], "drupal_id": animal["attributes"]["drupal_internal__id"], "name": animal["attributes"]["name"], "birthdate": birthdate, - "sex": animal["attributes"]["sex"], - "is_castrated": animal["attributes"]["is_castrated"], - "location": "", # TODO - "status": animal["attributes"]["status"], - "notes": animal["attributes"]["notes"]["value"], + "sex": animal["attributes"]["sex"] or colander.null, + "is_sterile": sterile, + "location": colander.null, # TODO + "archived": archived, + "notes": notes or colander.null, } def configure_form(self, form): @@ -197,8 +216,8 @@ class AnimalView(FarmOSMasterView): f.set_node("birthdate", WuttaDateTime()) f.set_widget("birthdate", WuttaDateTimeWidget(self.request)) - # is_castrated - f.set_node("is_castrated", colander.Boolean()) + # is_sterile + f.set_node("is_sterile", colander.Boolean()) # location f.set_node("location", StructureType(self.request)) @@ -209,6 +228,9 @@ class AnimalView(FarmOSMasterView): # notes f.set_widget("notes", "notes") + # archived + f.set_node("archived", colander.Boolean()) + # image if url := animal.get("large_image_url"): f.set_widget("image", ImageWidget("animal image")) diff --git a/src/wuttafarm/web/views/farmos/groups.py b/src/wuttafarm/web/views/farmos/groups.py index 66224fe..df54b04 100644 --- a/src/wuttafarm/web/views/farmos/groups.py +++ b/src/wuttafarm/web/views/farmos/groups.py @@ -50,7 +50,7 @@ class GroupView(FarmOSMasterView): "name", "is_fixed", "is_location", - "status", + "archived", "changed", ] @@ -60,7 +60,7 @@ class GroupView(FarmOSMasterView): "name", "is_fixed", "is_location", - "status", + "archived", "notes", "created", "changed", @@ -87,6 +87,9 @@ class GroupView(FarmOSMasterView): # changed g.set_renderer("changed", "datetime") + # archived + g.set_renderer("archived", "boolean") + def get_instance(self): group = self.farmos_client.resource.get_id( "asset", "group", self.request.matchdict["uuid"] @@ -107,6 +110,11 @@ class GroupView(FarmOSMasterView): changed = datetime.datetime.fromisoformat(changed) changed = self.app.localtime(changed) + if self.farmos_4x: + archived = group["attributes"]["archived"] + else: + archived = group["attributes"]["status"] == "archived" + return { "uuid": group["id"], "drupal_id": group["attributes"]["drupal_internal__id"], @@ -115,7 +123,7 @@ class GroupView(FarmOSMasterView): "changed": changed, "is_fixed": group["attributes"]["is_fixed"], "is_location": group["attributes"]["is_location"], - "status": group["attributes"]["status"], + "archived": archived, "notes": group["attributes"]["notes"]["value"], } @@ -140,6 +148,9 @@ class GroupView(FarmOSMasterView): f.set_node("changed", WuttaDateTime()) f.set_widget("changed", WuttaDateTimeWidget(self.request)) + # archived + f.set_node("archived", colander.Boolean()) + def get_xref_buttons(self, group): model = self.app.model session = self.Session() diff --git a/src/wuttafarm/web/views/farmos/land_assets.py b/src/wuttafarm/web/views/farmos/land_assets.py index 64f43cc..ffea76d 100644 --- a/src/wuttafarm/web/views/farmos/land_assets.py +++ b/src/wuttafarm/web/views/farmos/land_assets.py @@ -52,7 +52,7 @@ class LandAssetView(FarmOSMasterView): "land_type", "is_fixed", "is_location", - "status", + "archived", "changed", ] @@ -63,7 +63,7 @@ class LandAssetView(FarmOSMasterView): "land_type", "is_fixed", "is_location", - "status", + "archived", "notes", "created", "changed", @@ -93,6 +93,9 @@ class LandAssetView(FarmOSMasterView): # changed g.set_renderer("changed", "datetime") + # archived + g.set_renderer("archived", "boolean") + def get_instance(self): land_asset = self.farmos_client.resource.get_id( "asset", "land", self.request.matchdict["uuid"] @@ -116,6 +119,11 @@ class LandAssetView(FarmOSMasterView): if notes := land["attributes"]["notes"]: notes = notes["value"] + if self.farmos_4x: + archived = land["attributes"]["archived"] + else: + archived = land["attributes"]["status"] == "archived" + return { "uuid": land["id"], "drupal_id": land["attributes"]["drupal_internal__id"], @@ -125,7 +133,7 @@ class LandAssetView(FarmOSMasterView): "changed": changed, "is_fixed": land["attributes"]["is_fixed"], "is_location": land["attributes"]["is_location"], - "status": land["attributes"]["status"], + "archived": archived, "notes": notes or colander.null, } @@ -150,6 +158,9 @@ class LandAssetView(FarmOSMasterView): f.set_node("changed", WuttaDateTime()) f.set_widget("changed", WuttaDateTimeWidget(self.request)) + # archived + f.set_node("archived", colander.Boolean()) + def get_xref_buttons(self, land): return [ self.make_button( diff --git a/src/wuttafarm/web/views/farmos/master.py b/src/wuttafarm/web/views/farmos/master.py index 955120b..fff3d2c 100644 --- a/src/wuttafarm/web/views/farmos/master.py +++ b/src/wuttafarm/web/views/farmos/master.py @@ -58,6 +58,7 @@ class FarmOSMasterView(MasterView): def __init__(self, request, context=None): super().__init__(request, context=context) self.farmos_client = self.get_farmos_client() + self.farmos_4x = self.app.is_farmos_4x(self.farmos_client) self.raw_json = None def get_farmos_client(self): diff --git a/src/wuttafarm/web/views/farmos/structures.py b/src/wuttafarm/web/views/farmos/structures.py index 3626fb1..618c2fa 100644 --- a/src/wuttafarm/web/views/farmos/structures.py +++ b/src/wuttafarm/web/views/farmos/structures.py @@ -50,7 +50,7 @@ class StructureView(FarmOSMasterView): grid_columns = [ "name", - "status", + "archived", "created", "changed", ] @@ -59,7 +59,7 @@ class StructureView(FarmOSMasterView): form_fields = [ "name", - "status", + "archived", "structure_type", "is_location", "is_fixed", @@ -90,6 +90,9 @@ class StructureView(FarmOSMasterView): # changed g.set_renderer("changed", "datetime") + # archived + g.set_renderer("archived", "boolean") + def get_instance(self): structure = self.farmos_client.resource.get_id( "asset", "structure", self.request.matchdict["uuid"] @@ -145,6 +148,11 @@ class StructureView(FarmOSMasterView): changed = datetime.datetime.fromisoformat(changed) changed = self.app.localtime(changed) + if self.farmos_4x: + archived = structure["attributes"]["archived"] + else: + archived = structure["attributes"]["status"] == "archived" + return { "uuid": structure["id"], "drupal_id": structure["attributes"]["drupal_internal__id"], @@ -153,7 +161,7 @@ class StructureView(FarmOSMasterView): "is_fixed": structure["attributes"]["is_fixed"], "is_location": structure["attributes"]["is_location"], "notes": structure["attributes"]["notes"] or colander.null, - "status": structure["attributes"]["status"], + "archived": archived, "created": created, "changed": changed, } @@ -180,6 +188,9 @@ class StructureView(FarmOSMasterView): f.set_node("changed", WuttaDateTime()) f.set_widget("changed", WuttaDateTimeWidget(self.request)) + # archived + f.set_node("archived", colander.Boolean()) + # image if url := structure.get("large_image_url"): f.set_widget("image", ImageWidget("structure image"))