From a43f98c304397706ccccb4eb4251c8a6fda6b7e7 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sun, 8 Mar 2026 12:27:05 -0500 Subject: [PATCH] feat: add edit/sync support for Log Quantities er, just Standard Quantities so far..and just supported enough to move the ball forward, it still needs lots more polish --- src/wuttafarm/app.py | 26 ++ src/wuttafarm/db/model/quantities.py | 6 +- src/wuttafarm/web/forms/schema.py | 33 +- src/wuttafarm/web/forms/widgets.py | 85 ++++- .../web/templates/deform/quantityrefs.pt | 13 + .../web/templates/wuttafarm-components.mako | 352 ++++++++++++++++++ src/wuttafarm/web/views/logs.py | 61 ++- src/wuttafarm/web/views/master.py | 5 +- 8 files changed, 555 insertions(+), 26 deletions(-) create mode 100644 src/wuttafarm/web/templates/deform/quantityrefs.pt diff --git a/src/wuttafarm/app.py b/src/wuttafarm/app.py index cb9aed3..6bbca34 100644 --- a/src/wuttafarm/app.py +++ b/src/wuttafarm/app.py @@ -151,6 +151,32 @@ class WuttaFarmAppHandler(base.AppHandler): factory = self.load_object(spec) return factory(self.config, farmos_client) + def get_quantity_types(self, session=None): + """ + Returns a list of all known quantity types. + """ + model = self.model + with self.short_session(session=session) as sess: + return ( + sess.query(model.QuantityType).order_by(model.QuantityType.name).all() + ) + + def get_measures(self, session=None): + """ + Returns a list of all known measures. + """ + model = self.model + with self.short_session(session=session) as sess: + return sess.query(model.Measure).order_by(model.Measure.name).all() + + def get_units(self, session=None): + """ + Returns a list of all known units. + """ + model = self.model + with self.short_session(session=session) as sess: + return sess.query(model.Unit).order_by(model.Unit.name).all() + def auto_sync_to_farmos(self, obj, model_name=None, client=None, require=True): """ Export the given object to farmOS, using configured handler. diff --git a/src/wuttafarm/db/model/quantities.py b/src/wuttafarm/db/model/quantities.py index 4bed6a0..bcc2183 100644 --- a/src/wuttafarm/db/model/quantities.py +++ b/src/wuttafarm/db/model/quantities.py @@ -181,9 +181,13 @@ class Quantity(model.Base): creator=make_log_quantity, ) + def get_value_decimal(self): + # TODO: should actually return a decimal here? + return self.value_numerator / self.value_denominator + def render_as_text(self, config=None): measure = str(self.measure or self.measure_id or "") - value = self.value_numerator / self.value_denominator + value = self.get_value_decimal() if config: app = config.get_app() value = app.render_quantity(value) diff --git a/src/wuttafarm/web/forms/schema.py b/src/wuttafarm/web/forms/schema.py index 2d56cd4..f9dc33a 100644 --- a/src/wuttafarm/web/forms/schema.py +++ b/src/wuttafarm/web/forms/schema.py @@ -437,21 +437,46 @@ class AssetRefs(WuttaSet): return AssetRefsWidget(self.request, **kwargs) -class LogQuantityRefs(WuttaSet): +class QuantityRefs(colander.List): """ Schema type for Quantities field (on a Log record) """ + def __init__(self, request): + super().__init__() + self.request = request + self.config = self.request.wutta_config + self.app = self.config.get_app() + def serialize(self, node, appstruct): if not appstruct: return colander.null - return {qty.uuid for qty in appstruct} + quantities = [] + for qty in appstruct: + quantities.append( + { + "uuid": qty.uuid.hex, + "quantity_type": { + "id": qty.quantity_type_id, + "name": qty.quantity_type.name, + }, + "measure": qty.measure_id, + "value": qty.get_value_decimal(), + "units": { + "uuid": qty.units.uuid.hex, + "name": qty.units.name, + }, + "as_text": qty.render_as_text(self.config), + } + ) + + return quantities def widget_maker(self, **kwargs): - from wuttafarm.web.forms.widgets import LogQuantityRefsWidget + from wuttafarm.web.forms.widgets import QuantityRefsWidget - return LogQuantityRefsWidget(self.request, **kwargs) + return QuantityRefsWidget(self.request, **kwargs) class OwnerRefs(WuttaSet): diff --git a/src/wuttafarm/web/forms/widgets.py b/src/wuttafarm/web/forms/widgets.py index 545ea78..f1cf067 100644 --- a/src/wuttafarm/web/forms/widgets.py +++ b/src/wuttafarm/web/forms/widgets.py @@ -526,11 +526,19 @@ class AssetRefsWidget(Widget): return set(pstruct.split(",")) -class LogQuantityRefsWidget(WuttaCheckboxChoiceWidget): +class QuantityRefsWidget(Widget): """ Widget for Quantities field (on a Log record) """ + template = "quantityrefs" + + def __init__(self, request, *args, **kwargs): + super().__init__(*args, **kwargs) + self.request = request + self.config = self.request.wutta_config + self.app = self.config.get_app() + def serialize(self, field, cstruct, **kw): """ """ model = self.app.model @@ -538,24 +546,71 @@ class LogQuantityRefsWidget(WuttaCheckboxChoiceWidget): readonly = kw.get("readonly", self.readonly) if readonly: + if not cstruct: + return "" + quantities = [] - for uuid in cstruct or []: - qty = session.get(model.Quantity, uuid) - quantities.append( - HTML.tag( - "li", - c=tags.link_to( - qty.render_as_text(self.config), - # TODO - self.request.route_url( - "quantities_standard.view", uuid=qty.uuid - ), - ), - ) + + for qty in cstruct: + # TODO: support more quantity types + url = self.request.route_url( + "quantities_standard.view", uuid=qty["uuid"] ) + quantities.append(HTML.tag("li", c=tags.link_to(qty["as_text"], url))) + return HTML.tag("ul", c=quantities) - return super().serialize(field, cstruct, **kw) + tmpl_values = self.get_template_values(field, cstruct, kw) + return field.renderer(self.template, **tmpl_values) + + def get_template_values(self, field, cstruct, kw): + model = self.app.model + session = Session() + values = super().get_template_values(field, cstruct, kw) + + qtypes = [] + for qtype in self.app.get_quantity_types(session): + # TODO: add support for other quantity types + if qtype.drupal_id == "standard": + qtypes.append( + { + "uuid": qtype.uuid.hex, + "drupal_id": qtype.drupal_id, + "name": qtype.name, + } + ) + values["quantity_types"] = qtypes + + measures = [] + for measure in self.app.get_measures(session): + measures.append( + { + "uuid": measure.uuid.hex, + "drupal_id": measure.drupal_id, + "name": measure.name, + } + ) + values["measures"] = measures + + units = [] + for unit in self.app.get_units(session): + units.append( + { + "uuid": unit.uuid.hex, + "drupal_id": unit.drupal_id, + "name": unit.name, + } + ) + values["units"] = units + + return values + + def deserialize(self, field, pstruct): + """ """ + if not pstruct: + return set() + + return json.loads(pstruct) class OwnerRefsWidget(WuttaCheckboxChoiceWidget): diff --git a/src/wuttafarm/web/templates/deform/quantityrefs.pt b/src/wuttafarm/web/templates/deform/quantityrefs.pt new file mode 100644 index 0000000..5c98b84 --- /dev/null +++ b/src/wuttafarm/web/templates/deform/quantityrefs.pt @@ -0,0 +1,13 @@ +
+ + + +
diff --git a/src/wuttafarm/web/templates/wuttafarm-components.mako b/src/wuttafarm/web/templates/wuttafarm-components.mako index c537489..d735274 100644 --- a/src/wuttafarm/web/templates/wuttafarm-components.mako +++ b/src/wuttafarm/web/templates/wuttafarm-components.mako @@ -2,6 +2,8 @@ <%def name="make_wuttafarm_components()"> ${self.make_assets_picker_component()} ${self.make_animal_type_picker_component()} + ${self.make_quantity_editor_component()} + ${self.make_quantities_editor_component()} ${self.make_plant_types_picker_component()} ${self.make_seasons_picker_component()} @@ -239,6 +241,356 @@ +<%def name="make_quantity_editor_component()"> + + + + +<%def name="make_quantities_editor_component()"> + + + + <%def name="make_plant_types_picker_component()">