From 03dacd9957ecc955f10d4448d1b4805c750a5e13 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 30 May 2026 20:37:20 -0500 Subject: [PATCH 1/6] fix: sort related assets field when viewing a log --- src/wuttafarm/web/forms/widgets.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/wuttafarm/web/forms/widgets.py b/src/wuttafarm/web/forms/widgets.py index db79eae..6897660 100644 --- a/src/wuttafarm/web/forms/widgets.py +++ b/src/wuttafarm/web/forms/widgets.py @@ -667,10 +667,17 @@ class AssetRefsWidget(Widget): readonly = kw.get("readonly", self.readonly) if readonly: + assets = [] for uuid in cstruct or []: - asset = session.get(model.Asset, uuid) - assets.append( + if asset := session.get(model.Asset, uuid): + assets.append(asset) + + assets.sort(key=lambda asset: asset.asset_name) + + html = [] + for asset in assets: + html.append( HTML.tag( "li", c=tags.link_to( @@ -681,7 +688,8 @@ class AssetRefsWidget(Widget): ), ) ) - return HTML.tag("ul", c=assets) + + return HTML.tag("ul", c=html) values = kw.get("values", self.values) if not isinstance(values, sequence_types): From a26f16a67e1d246ee12a7096be730cb8ba97c5ea Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 30 May 2026 20:52:06 -0500 Subject: [PATCH 2/6] fix: fix Location column for All Logs subgrid when viewing Asset --- src/wuttafarm/web/views/assets.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/wuttafarm/web/views/assets.py b/src/wuttafarm/web/views/assets.py index 1ada778..27bb925 100644 --- a/src/wuttafarm/web/views/assets.py +++ b/src/wuttafarm/web/views/assets.py @@ -103,6 +103,7 @@ class AssetMasterView(WuttaFarmMasterView): row_labels = { "message": "Log Name", + "locations": "Location", } row_grid_columns = [ @@ -112,7 +113,7 @@ class AssetMasterView(WuttaFarmMasterView): "message", "log_type", "assets", - "location", + "locations", "quantity", "is_group_assignment", ] @@ -446,6 +447,9 @@ class AssetMasterView(WuttaFarmMasterView): # assets g.set_renderer("assets", self.render_assets_for_grid) + # locations + g.set_renderer("locations", self.render_assets_for_grid) + def render_assets_for_grid(self, log, field, value): assets = getattr(log, field) From be528f08544cf9bccd0499a81bd4d3fd939f1009 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 30 May 2026 21:30:01 -0500 Subject: [PATCH 3/6] feat: add support for Compost Assets --- .../versions/a240a1449de9_add_compostasset.py | 102 ++++++++++++++++++ src/wuttafarm/db/model/__init__.py | 1 + src/wuttafarm/db/model/asset_compost.py | 45 ++++++++ src/wuttafarm/farmos/importing/model.py | 15 +++ src/wuttafarm/farmos/importing/wuttafarm.py | 11 ++ src/wuttafarm/importing/farmos.py | 9 ++ src/wuttafarm/web/menus.py | 15 +++ src/wuttafarm/web/views/__init__.py | 1 + src/wuttafarm/web/views/assets.py | 12 +++ src/wuttafarm/web/views/compost.py | 62 +++++++++++ src/wuttafarm/web/views/farmos/__init__.py | 1 + src/wuttafarm/web/views/farmos/assets.py | 6 ++ src/wuttafarm/web/views/farmos/compost.py | 79 ++++++++++++++ 13 files changed, 359 insertions(+) create mode 100644 src/wuttafarm/db/alembic/versions/a240a1449de9_add_compostasset.py create mode 100644 src/wuttafarm/db/model/asset_compost.py create mode 100644 src/wuttafarm/web/views/compost.py create mode 100644 src/wuttafarm/web/views/farmos/compost.py diff --git a/src/wuttafarm/db/alembic/versions/a240a1449de9_add_compostasset.py b/src/wuttafarm/db/alembic/versions/a240a1449de9_add_compostasset.py new file mode 100644 index 0000000..902cf0c --- /dev/null +++ b/src/wuttafarm/db/alembic/versions/a240a1449de9_add_compostasset.py @@ -0,0 +1,102 @@ +"""add CompostAsset + +Revision ID: a240a1449de9 +Revises: dd4d4142b96d +Create Date: 2026-05-30 21:12:36.464851 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +import wuttjamaican.db.util + + +# revision identifiers, used by Alembic. +revision: str = "a240a1449de9" +down_revision: Union[str, None] = "dd4d4142b96d" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + + # asset_compost + op.create_table( + "asset_compost", + sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False), + sa.ForeignKeyConstraint( + ["uuid"], ["asset.uuid"], name=op.f("fk_asset_compost_uuid_asset") + ), + sa.PrimaryKeyConstraint("uuid", name=op.f("pk_asset_compost")), + ) + op.create_table( + "asset_compost_version", + sa.Column( + "uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False + ), + sa.Column( + "transaction_id", sa.BigInteger(), autoincrement=False, nullable=False + ), + sa.Column("end_transaction_id", sa.BigInteger(), nullable=True), + sa.Column("operation_type", sa.SmallInteger(), nullable=False), + sa.PrimaryKeyConstraint( + "uuid", "transaction_id", name=op.f("pk_asset_compost_version") + ), + ) + op.create_index( + op.f("ix_asset_compost_version_end_transaction_id"), + "asset_compost_version", + ["end_transaction_id"], + unique=False, + ) + op.create_index( + op.f("ix_asset_compost_version_operation_type"), + "asset_compost_version", + ["operation_type"], + unique=False, + ) + op.create_index( + "ix_asset_compost_version_pk_transaction_id", + "asset_compost_version", + ["uuid", sa.literal_column("transaction_id DESC")], + unique=False, + ) + op.create_index( + "ix_asset_compost_version_pk_validity", + "asset_compost_version", + ["uuid", "transaction_id", "end_transaction_id"], + unique=False, + ) + op.create_index( + op.f("ix_asset_compost_version_transaction_id"), + "asset_compost_version", + ["transaction_id"], + unique=False, + ) + + +def downgrade() -> None: + + # asset_compost + op.drop_index( + op.f("ix_asset_compost_version_transaction_id"), + table_name="asset_compost_version", + ) + op.drop_index( + "ix_asset_compost_version_pk_validity", table_name="asset_compost_version" + ) + op.drop_index( + "ix_asset_compost_version_pk_transaction_id", table_name="asset_compost_version" + ) + op.drop_index( + op.f("ix_asset_compost_version_operation_type"), + table_name="asset_compost_version", + ) + op.drop_index( + op.f("ix_asset_compost_version_end_transaction_id"), + table_name="asset_compost_version", + ) + op.drop_table("asset_compost_version") + op.drop_table("asset_compost") diff --git a/src/wuttafarm/db/model/__init__.py b/src/wuttafarm/db/model/__init__.py index 716fd1c..923f163 100644 --- a/src/wuttafarm/db/model/__init__.py +++ b/src/wuttafarm/db/model/__init__.py @@ -53,6 +53,7 @@ from .asset_plant import ( PlantAssetSeason, ) from .asset_water import WaterAsset +from .asset_compost import CompostAsset from .log import LogType, Log, LogAsset, LogGroup, LogLocation, LogQuantity, LogOwner from .log_activity import ActivityLog from .log_harvest import HarvestLog diff --git a/src/wuttafarm/db/model/asset_compost.py b/src/wuttafarm/db/model/asset_compost.py new file mode 100644 index 0000000..37b5bb5 --- /dev/null +++ b/src/wuttafarm/db/model/asset_compost.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8; -*- +################################################################################ +# +# WuttaFarm --Web app to integrate with and extend farmOS +# Copyright © 2026 Lance Edgar +# +# This file is part of WuttaFarm. +# +# WuttaFarm is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# WuttaFarm is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# WuttaFarm. If not, see . +# +################################################################################ +""" +Model definition for Compost +""" + +from wuttjamaican.db import model + +from wuttafarm.db.model.asset import AssetMixin, add_asset_proxies + + +class CompostAsset(AssetMixin, model.Base): + """ + Represents an compost asset from farmOS + """ + + __tablename__ = "asset_compost" + __versioned__ = {} + __wutta_hint__ = { + "model_title": "Compost Asset", + "model_title_plural": "Compost Assets", + "farmos_asset_type": "compost", + } + + +add_asset_proxies(CompostAsset) diff --git a/src/wuttafarm/farmos/importing/model.py b/src/wuttafarm/farmos/importing/model.py index e7ebe23..f351d37 100644 --- a/src/wuttafarm/farmos/importing/model.py +++ b/src/wuttafarm/farmos/importing/model.py @@ -574,6 +574,21 @@ class WaterAssetImporter(ToFarmOSAsset): ] +class CompostAssetImporter(ToFarmOSAsset): + + model_title = "CompostAsset" + farmos_asset_type = "compost" + + supported_fields = [ + "uuid", + "asset_name", + "is_location", + "is_fixed", + "notes", + "archived", + ] + + ############################## # quantity importers ############################## diff --git a/src/wuttafarm/farmos/importing/wuttafarm.py b/src/wuttafarm/farmos/importing/wuttafarm.py index d60a96f..9792278 100644 --- a/src/wuttafarm/farmos/importing/wuttafarm.py +++ b/src/wuttafarm/farmos/importing/wuttafarm.py @@ -99,6 +99,7 @@ class FromWuttaFarmToFarmOS(FromWuttaFarmHandler, ToFarmOSHandler): importers["LandAsset"] = LandAssetImporter importers["StructureAsset"] = StructureAssetImporter importers["WaterAsset"] = WaterAssetImporter + importers["CompostAsset"] = CompostAssetImporter importers["EquipmentType"] = EquipmentTypeImporter importers["EquipmentAsset"] = EquipmentAssetImporter importers["AnimalType"] = AnimalTypeImporter @@ -475,6 +476,16 @@ class WaterAssetImporter(FromWuttaFarmAsset, farmos_importing.model.WaterAssetIm source_model_class = model.WaterAsset +class CompostAssetImporter( + FromWuttaFarmAsset, farmos_importing.model.CompostAssetImporter +): + """ + WuttaFarm → farmOS API exporter for Compost Assets + """ + + source_model_class = model.CompostAsset + + ############################## # quantity importers ############################## diff --git a/src/wuttafarm/importing/farmos.py b/src/wuttafarm/importing/farmos.py index eb82fe1..81fedf0 100644 --- a/src/wuttafarm/importing/farmos.py +++ b/src/wuttafarm/importing/farmos.py @@ -107,6 +107,7 @@ class FromFarmOSToWuttaFarm(FromFarmOSHandler, ToWuttaFarmHandler): importers["StructureType"] = StructureTypeImporter importers["StructureAsset"] = StructureAssetImporter importers["WaterAsset"] = WaterAssetImporter + importers["CompostAsset"] = CompostAssetImporter importers["EquipmentType"] = EquipmentTypeImporter importers["EquipmentAsset"] = EquipmentAssetImporter importers["AnimalType"] = AnimalTypeImporter @@ -1033,6 +1034,14 @@ class WaterAssetImporter(AssetImporterBase): model_class = model.WaterAsset +class CompostAssetImporter(AssetImporterBase): + """ + farmOS API → WuttaFarm importer for Compost Assets + """ + + model_class = model.CompostAsset + + class UserImporter(FromFarmOS, ToWutta): """ farmOS API → WuttaFarm importer for Users diff --git a/src/wuttafarm/web/menus.py b/src/wuttafarm/web/menus.py index 62d3d9c..71b9e5d 100644 --- a/src/wuttafarm/web/menus.py +++ b/src/wuttafarm/web/menus.py @@ -92,6 +92,11 @@ class WuttaFarmMenuHandler(base.MenuHandler): "route": "animal_assets", "perm": "animal_assets.list", }, + { + "title": "Compost", + "route": "compost_assets", + "perm": "compost_assets.list", + }, { "title": "Equipment", "route": "equipment_assets", @@ -259,6 +264,11 @@ class WuttaFarmMenuHandler(base.MenuHandler): "route": "farmos_animal_assets", "perm": "farmos_animal_assets.list", }, + { + "title": "Compost Assets", + "route": "farmos_compost_assets", + "perm": "farmos_compost_assets.list", + }, { "title": "Equipment Assets", "route": "farmos_equipment_assets", @@ -403,6 +413,11 @@ class WuttaFarmMenuHandler(base.MenuHandler): "route": "farmos_animal_assets", "perm": "farmos_animal_assets.list", }, + { + "title": "Compost", + "route": "farmos_compost_assets", + "perm": "farmos_compost_assets.list", + }, { "title": "Equipment", "route": "farmos_equipment_assets", diff --git a/src/wuttafarm/web/views/__init__.py b/src/wuttafarm/web/views/__init__.py index b663cf5..2069aa7 100644 --- a/src/wuttafarm/web/views/__init__.py +++ b/src/wuttafarm/web/views/__init__.py @@ -59,6 +59,7 @@ def includeme(config): config.include("wuttafarm.web.views.groups") config.include("wuttafarm.web.views.plants") config.include("wuttafarm.web.views.water") + config.include("wuttafarm.web.views.compost") config.include("wuttafarm.web.views.logs") config.include("wuttafarm.web.views.logs_activity") config.include("wuttafarm.web.views.logs_harvest") diff --git a/src/wuttafarm/web/views/assets.py b/src/wuttafarm/web/views/assets.py index 27bb925..d9516a5 100644 --- a/src/wuttafarm/web/views/assets.py +++ b/src/wuttafarm/web/views/assets.py @@ -71,6 +71,18 @@ class AssetMasterView(WuttaFarmMasterView): "groups": "Group Membership", } + grid_columns = [ + "thumbnail", + "drupal_id", + "asset_name", + "groups", + "asset_type", + "parents", + "owners", + "locations", + "archived", + ] + sort_defaults = "asset_name" filter_defaults = { diff --git a/src/wuttafarm/web/views/compost.py b/src/wuttafarm/web/views/compost.py new file mode 100644 index 0000000..c1634bb --- /dev/null +++ b/src/wuttafarm/web/views/compost.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8; -*- +################################################################################ +# +# WuttaFarm --Web app to integrate with and extend farmOS +# Copyright © 2026 Lance Edgar +# +# This file is part of WuttaFarm. +# +# WuttaFarm is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# WuttaFarm is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# WuttaFarm. If not, see . +# +################################################################################ +""" +Master view for Compost Assets +""" + +from wuttafarm.db.model import CompostAsset +from wuttafarm.web.views.assets import AssetMasterView + + +class CompostAssetView(AssetMasterView): + """ + Master view for Compost Assets + """ + + model_class = CompostAsset + route_prefix = "compost_assets" + url_prefix = "/assets/compost" + + farmos_bundle = "compost" + farmos_refurl_path = "/assets/compost" + + grid_columns = [ + "thumbnail", + "drupal_id", + "asset_name", + "groups", + "parents", + "owners", + "locations", + "archived", + ] + + +def defaults(config, **kwargs): + base = globals() + + CompostAssetView = kwargs.get("CompostAssetView", base["CompostAssetView"]) + CompostAssetView.defaults(config) + + +def includeme(config): + defaults(config) diff --git a/src/wuttafarm/web/views/farmos/__init__.py b/src/wuttafarm/web/views/farmos/__init__.py index 708b553..6ea7181 100644 --- a/src/wuttafarm/web/views/farmos/__init__.py +++ b/src/wuttafarm/web/views/farmos/__init__.py @@ -42,6 +42,7 @@ def includeme(config): config.include("wuttafarm.web.views.farmos.groups") config.include("wuttafarm.web.views.farmos.plants") config.include("wuttafarm.web.views.farmos.water") + config.include("wuttafarm.web.views.farmos.compost") config.include("wuttafarm.web.views.farmos.log_types") config.include("wuttafarm.web.views.farmos.logs_activity") config.include("wuttafarm.web.views.farmos.logs_harvest") diff --git a/src/wuttafarm/web/views/farmos/assets.py b/src/wuttafarm/web/views/farmos/assets.py index 24dd145..517e8da 100644 --- a/src/wuttafarm/web/views/farmos/assets.py +++ b/src/wuttafarm/web/views/farmos/assets.py @@ -327,6 +327,12 @@ class AssetMasterView(FarmOSMasterView): # archived f.set_node("archived", colander.Boolean()) + # drupal_id + if self.creating: + f.remove("drupal_id") + else: + f.set_readonly("drupal_id") + # thumbnail_url if self.creating or self.editing: f.remove("thumbnail_url") diff --git a/src/wuttafarm/web/views/farmos/compost.py b/src/wuttafarm/web/views/farmos/compost.py new file mode 100644 index 0000000..50ef62c --- /dev/null +++ b/src/wuttafarm/web/views/farmos/compost.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8; -*- +################################################################################ +# +# WuttaFarm --Web app to integrate with and extend farmOS +# Copyright © 2026 Lance Edgar +# +# This file is part of WuttaFarm. +# +# WuttaFarm is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# WuttaFarm is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# WuttaFarm. If not, see . +# +################################################################################ +""" +Master view for farmOS Compost Assets +""" + +from webhelpers2.html import tags + +from wuttafarm.web.views.farmos.assets import AssetMasterView + + +class CompostAssetView(AssetMasterView): + """ + Master view for farmOS Compost Assets + """ + + model_name = "farmos_compost_assets" + model_title = "farmOS Compost Asset" + model_title_plural = "farmOS Compost Assets" + + route_prefix = "farmos_compost_assets" + url_prefix = "/farmOS/assets/compost" + + farmos_asset_type = "compost" + farmos_refurl_path = "/assets/compost" + + def get_xref_buttons(self, compost): + model = self.app.model + session = self.Session() + + buttons = super().get_xref_buttons(compost) + + if wf_compost := ( + session.query(model.Asset) + .filter(model.Asset.farmos_uuid == compost["uuid"]) + .first() + ): + buttons.append( + self.make_button( + f"View {self.app.get_title()} record", + primary=True, + url=self.request.route_url( + "compost_assets.view", uuid=wf_compost.uuid + ), + icon_left="eye", + ) + ) + + return buttons + + +def defaults(config, **kwargs): + base = globals() + + CompostAssetView = kwargs.get("CompostAssetView", base["CompostAssetView"]) + CompostAssetView.defaults(config) + + +def includeme(config): + defaults(config) From 913d5801cda8acef00600ce5284e15fad926c563 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 30 May 2026 21:30:32 -0500 Subject: [PATCH 4/6] =?UTF-8?q?bump:=20version=200.11.4=20=E2=86=92=200.12?= =?UTF-8?q?.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 11 +++++++++++ pyproject.toml | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20691f6..e6d7b06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ 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.12.0 (2026-05-30) + +### Feat + +- add support for Compost Assets + +### Fix + +- fix Location column for All Logs subgrid when viewing Asset +- sort related assets field when viewing a log + ## v0.11.4 (2026-05-30) ### Fix diff --git a/pyproject.toml b/pyproject.toml index 6956f88..cc46ec7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "hatchling.build" [project] name = "WuttaFarm" -version = "0.11.4" +version = "0.12.0" description = "Web app to integrate with and extend farmOS" readme = "README.md" authors = [ From fbae0bcfc8839fe4bb60e31aac1445c024327770 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sun, 31 May 2026 11:57:37 -0500 Subject: [PATCH 5/6] fix: allow null for `Quantity.measure` field since apparently that is optional, e.g. for "Precipitation" quick form logs --- ...54f9_allow_null_for_quantity_measure_id.py | 36 +++++++++++++++++++ src/wuttafarm/db/model/quantities.py | 9 +++-- src/wuttafarm/web/views/quantities.py | 3 +- 3 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 src/wuttafarm/db/alembic/versions/c30a725b54f9_allow_null_for_quantity_measure_id.py diff --git a/src/wuttafarm/db/alembic/versions/c30a725b54f9_allow_null_for_quantity_measure_id.py b/src/wuttafarm/db/alembic/versions/c30a725b54f9_allow_null_for_quantity_measure_id.py new file mode 100644 index 0000000..2c5952d --- /dev/null +++ b/src/wuttafarm/db/alembic/versions/c30a725b54f9_allow_null_for_quantity_measure_id.py @@ -0,0 +1,36 @@ +"""allow null for quantity.measure_id + +Revision ID: c30a725b54f9 +Revises: a240a1449de9 +Create Date: 2026-05-30 22:14:21.056993 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +import wuttjamaican.db.util + + +# revision identifiers, used by Alembic. +revision: str = "c30a725b54f9" +down_revision: Union[str, None] = "a240a1449de9" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + + # quantity + op.alter_column( + "quantity", "measure_id", existing_type=sa.VARCHAR(length=20), nullable=True + ) + + +def downgrade() -> None: + + # quantity + op.alter_column( + "quantity", "measure_id", existing_type=sa.VARCHAR(length=20), nullable=False + ) diff --git a/src/wuttafarm/db/model/quantities.py b/src/wuttafarm/db/model/quantities.py index 4fa92af..c04f85b 100644 --- a/src/wuttafarm/db/model/quantities.py +++ b/src/wuttafarm/db/model/quantities.py @@ -109,7 +109,7 @@ class Quantity(model.Base): measure_id = sa.Column( sa.String(length=20), sa.ForeignKey("measure.drupal_id"), - nullable=False, + nullable=True, doc=""" Measure for the quantity. """, @@ -192,7 +192,12 @@ class Quantity(model.Base): app = config.get_app() value = app.render_quantity(value) units = str(self.units or "") - return f"( {measure} ) {value} {units}" + + if measure: + return f"( {measure} ) {value} {units}" + + label = self.label or "" + return f"{label} {value} {units}" def __str__(self): return self.render_as_text() diff --git a/src/wuttafarm/web/views/quantities.py b/src/wuttafarm/web/views/quantities.py index 9a91941..05f624b 100644 --- a/src/wuttafarm/web/views/quantities.py +++ b/src/wuttafarm/web/views/quantities.py @@ -298,7 +298,8 @@ class QuantityMasterView(WuttaFarmMasterView): f.remove("measure") else: f.set_readonly("measure") - f.set_default("measure", quantity.measure.name) + if quantity.measure: + f.set_default("measure", quantity.measure.name) # value if self.creating: From 2998e675489e780b8cc1f2290217f64acd3716c7 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sun, 31 May 2026 11:58:48 -0500 Subject: [PATCH 6/6] =?UTF-8?q?bump:=20version=200.12.0=20=E2=86=92=200.12?= =?UTF-8?q?.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 6 ++++++ pyproject.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6d7b06..d392b59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ 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.12.1 (2026-05-31) + +### Fix + +- allow null for `Quantity.measure` field + ## v0.12.0 (2026-05-30) ### Feat diff --git a/pyproject.toml b/pyproject.toml index cc46ec7..16c7c60 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "hatchling.build" [project] name = "WuttaFarm" -version = "0.12.0" +version = "0.12.1" description = "Web app to integrate with and extend farmOS" readme = "README.md" authors = [