From dfc8dc0de3e1d7913b41c8927caba85998683cb3 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 9 Mar 2026 14:36:53 -0500 Subject: [PATCH] feat: add edit/sync support for Material Types + Material Quantities --- src/wuttafarm/app.py | 42 ++++ .../9c53513f8862_add_materialquantity.py | 211 ++++++++++++++++++ src/wuttafarm/db/model/__init__.py | 8 +- src/wuttafarm/db/model/material_type.py | 20 ++ src/wuttafarm/db/model/quantities.py | 64 ++++++ src/wuttafarm/farmos/importing/model.py | 43 ++++ src/wuttafarm/farmos/importing/wuttafarm.py | 19 ++ src/wuttafarm/importing/farmos.py | 74 ++++++ src/wuttafarm/normal.py | 44 ++-- src/wuttafarm/web/forms/schema.py | 82 +++++-- src/wuttafarm/web/forms/widgets.py | 99 ++++++-- src/wuttafarm/web/menus.py | 15 ++ .../web/templates/deform/materialtyperefs.pt | 13 ++ .../web/templates/deform/quantityrefs.pt | 1 + .../web/templates/wuttafarm-components.mako | 168 +++++++++++++- src/wuttafarm/web/util.py | 8 +- src/wuttafarm/web/views/farmos/logs.py | 17 +- src/wuttafarm/web/views/farmos/quantities.py | 112 +++++++++- src/wuttafarm/web/views/logs.py | 39 +++- src/wuttafarm/web/views/quantities.py | 82 ++++++- 20 files changed, 1075 insertions(+), 86 deletions(-) create mode 100644 src/wuttafarm/db/alembic/versions/9c53513f8862_add_materialquantity.py create mode 100644 src/wuttafarm/web/templates/deform/materialtyperefs.pt diff --git a/src/wuttafarm/app.py b/src/wuttafarm/app.py index e83bce0..decd44f 100644 --- a/src/wuttafarm/app.py +++ b/src/wuttafarm/app.py @@ -177,6 +177,48 @@ class WuttaFarmAppHandler(base.AppHandler): with self.short_session(session=session) as sess: return sess.query(model.Unit).order_by(model.Unit.name).all() + def get_material_types(self, session=None): + """ + Returns a list of all known material types. + """ + model = self.model + with self.short_session(session=session) as sess: + return ( + sess.query(model.MaterialType).order_by(model.MaterialType.name).all() + ) + + def get_quantity_models(self): + model = self.model + return { + "standard": model.StandardQuantity, + "material": model.MaterialQuantity, + } + + def get_true_quantity(self, quantity, require=True): + model = self.model + if not isinstance(quantity, model.Quantity): + if require and not quantity: + raise ValueError(f"quantity is not valid: {quantity}") + return quantity + + session = self.get_session(quantity) + models = self.get_quantity_models() + if require and quantity.quantity_type_id not in models: + raise ValueError( + f"quantity has invalid quantity_type_id: {quantity.quantity_type_id}" + ) + + true_quantity = session.get(models[quantity.quantity_type_id], quantity.uuid) + if require and not true_quantity: + raise ValueError(f"quantity has no true/typed quantity record: {quantity}") + + return true_quantity + + def make_true_quantity(self, quantity_type_id, **kwargs): + models = self.get_quantity_models() + kwargs["quantity_type_id"] = quantity_type_id + return models[quantity_type_id](**kwargs) + 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/alembic/versions/9c53513f8862_add_materialquantity.py b/src/wuttafarm/db/alembic/versions/9c53513f8862_add_materialquantity.py new file mode 100644 index 0000000..6f28989 --- /dev/null +++ b/src/wuttafarm/db/alembic/versions/9c53513f8862_add_materialquantity.py @@ -0,0 +1,211 @@ +"""add MaterialQuantity + +Revision ID: 9c53513f8862 +Revises: 1c89f3fbb521 +Create Date: 2026-03-08 18:14:05.587678 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +import wuttjamaican.db.util + + +# revision identifiers, used by Alembic. +revision: str = "9c53513f8862" +down_revision: Union[str, None] = "1c89f3fbb521" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + + # quantity_material + op.create_table( + "quantity_material", + sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False), + sa.ForeignKeyConstraint( + ["uuid"], ["quantity.uuid"], name=op.f("fk_quantity_material_uuid_quantity") + ), + sa.PrimaryKeyConstraint("uuid", name=op.f("pk_quantity_material")), + ) + op.create_table( + "quantity_material_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_quantity_material_version") + ), + ) + op.create_index( + op.f("ix_quantity_material_version_end_transaction_id"), + "quantity_material_version", + ["end_transaction_id"], + unique=False, + ) + op.create_index( + op.f("ix_quantity_material_version_operation_type"), + "quantity_material_version", + ["operation_type"], + unique=False, + ) + op.create_index( + "ix_quantity_material_version_pk_transaction_id", + "quantity_material_version", + ["uuid", sa.literal_column("transaction_id DESC")], + unique=False, + ) + op.create_index( + "ix_quantity_material_version_pk_validity", + "quantity_material_version", + ["uuid", "transaction_id", "end_transaction_id"], + unique=False, + ) + op.create_index( + op.f("ix_quantity_material_version_transaction_id"), + "quantity_material_version", + ["transaction_id"], + unique=False, + ) + + # quantity_material_material_type + op.create_table( + "quantity_material_material_type", + sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False), + sa.Column("quantity_uuid", wuttjamaican.db.util.UUID(), nullable=False), + sa.Column("material_type_uuid", wuttjamaican.db.util.UUID(), nullable=False), + sa.ForeignKeyConstraint( + ["material_type_uuid"], + ["material_type.uuid"], + name=op.f( + "fk_quantity_material_material_type_material_type_uuid_material_type" + ), + ), + sa.ForeignKeyConstraint( + ["quantity_uuid"], + ["quantity_material.uuid"], + name=op.f( + "fk_quantity_material_material_type_quantity_uuid_quantity_material" + ), + ), + sa.PrimaryKeyConstraint( + "uuid", name=op.f("pk_quantity_material_material_type") + ), + ) + op.create_table( + "quantity_material_material_type_version", + sa.Column( + "uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False + ), + sa.Column( + "quantity_uuid", + wuttjamaican.db.util.UUID(), + autoincrement=False, + nullable=True, + ), + sa.Column( + "material_type_uuid", + wuttjamaican.db.util.UUID(), + autoincrement=False, + nullable=True, + ), + 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_quantity_material_material_type_version"), + ), + ) + op.create_index( + op.f("ix_quantity_material_material_type_version_end_transaction_id"), + "quantity_material_material_type_version", + ["end_transaction_id"], + unique=False, + ) + op.create_index( + op.f("ix_quantity_material_material_type_version_operation_type"), + "quantity_material_material_type_version", + ["operation_type"], + unique=False, + ) + op.create_index( + "ix_quantity_material_material_type_version_pk_transaction_id", + "quantity_material_material_type_version", + ["uuid", sa.literal_column("transaction_id DESC")], + unique=False, + ) + op.create_index( + "ix_quantity_material_material_type_version_pk_validity", + "quantity_material_material_type_version", + ["uuid", "transaction_id", "end_transaction_id"], + unique=False, + ) + op.create_index( + op.f("ix_quantity_material_material_type_version_transaction_id"), + "quantity_material_material_type_version", + ["transaction_id"], + unique=False, + ) + + +def downgrade() -> None: + + # quantity_material_material_type + op.drop_index( + op.f("ix_quantity_material_material_type_version_transaction_id"), + table_name="quantity_material_material_type_version", + ) + op.drop_index( + "ix_quantity_material_material_type_version_pk_validity", + table_name="quantity_material_material_type_version", + ) + op.drop_index( + "ix_quantity_material_material_type_version_pk_transaction_id", + table_name="quantity_material_material_type_version", + ) + op.drop_index( + op.f("ix_quantity_material_material_type_version_operation_type"), + table_name="quantity_material_material_type_version", + ) + op.drop_index( + op.f("ix_quantity_material_material_type_version_end_transaction_id"), + table_name="quantity_material_material_type_version", + ) + op.drop_table("quantity_material_material_type_version") + op.drop_table("quantity_material_material_type") + + # quantity_material + op.drop_index( + op.f("ix_quantity_material_version_transaction_id"), + table_name="quantity_material_version", + ) + op.drop_index( + "ix_quantity_material_version_pk_validity", + table_name="quantity_material_version", + ) + op.drop_index( + "ix_quantity_material_version_pk_transaction_id", + table_name="quantity_material_version", + ) + op.drop_index( + op.f("ix_quantity_material_version_operation_type"), + table_name="quantity_material_version", + ) + op.drop_index( + op.f("ix_quantity_material_version_end_transaction_id"), + table_name="quantity_material_version", + ) + op.drop_table("quantity_material_version") + op.drop_table("quantity_material") diff --git a/src/wuttafarm/db/model/__init__.py b/src/wuttafarm/db/model/__init__.py index ba80389..35130da 100644 --- a/src/wuttafarm/db/model/__init__.py +++ b/src/wuttafarm/db/model/__init__.py @@ -32,7 +32,13 @@ from .users import WuttaFarmUser # wuttafarm proper models from .unit import Unit, Measure from .material_type import MaterialType -from .quantities import QuantityType, Quantity, StandardQuantity +from .quantities import ( + QuantityType, + Quantity, + StandardQuantity, + MaterialQuantity, + MaterialQuantityMaterialType, +) from .asset import AssetType, Asset, AssetParent from .asset_land import LandType, LandAsset from .asset_structure import StructureType, StructureAsset diff --git a/src/wuttafarm/db/model/material_type.py b/src/wuttafarm/db/model/material_type.py index e21f38c..a124451 100644 --- a/src/wuttafarm/db/model/material_type.py +++ b/src/wuttafarm/db/model/material_type.py @@ -24,6 +24,8 @@ Model definition for Material Types """ import sqlalchemy as sa +from sqlalchemy import orm +from sqlalchemy.ext.associationproxy import association_proxy from wuttjamaican.db import model @@ -76,5 +78,23 @@ class MaterialType(model.Base): """, ) + _quantities = orm.relationship( + "MaterialQuantityMaterialType", + cascade="all, delete-orphan", + cascade_backrefs=False, + back_populates="material_type", + ) + + def _make_material_quantity(qty): + from wuttafarm.db.model import MaterialQuantityMaterialType + + return MaterialQuantityMaterialType(quantity=qty) + + quantities = association_proxy( + "_quantities", + "quantity", + creator=_make_material_quantity, + ) + def __str__(self): return self.name or "" diff --git a/src/wuttafarm/db/model/quantities.py b/src/wuttafarm/db/model/quantities.py index 4030450..4fa92af 100644 --- a/src/wuttafarm/db/model/quantities.py +++ b/src/wuttafarm/db/model/quantities.py @@ -211,6 +211,9 @@ class QuantityMixin: cascade_backrefs=False, ) + def get_value_decimal(self): + return self.quantity.get_value_decimal() + def render_as_text(self, config=None): return self.quantity.render_as_text(config) @@ -249,3 +252,64 @@ class StandardQuantity(QuantityMixin, model.Base): add_quantity_proxies(StandardQuantity) + + +class MaterialQuantity(QuantityMixin, model.Base): + """ + Represents a Material Quantity from farmOS + """ + + __tablename__ = "quantity_material" + __versioned__ = {} + __wutta_hint__ = { + "model_title": "Material Quantity", + "model_title_plural": "Material Quantities", + "farmos_quantity_type": "material", + } + + _material_types = orm.relationship( + "MaterialQuantityMaterialType", + cascade="all, delete-orphan", + cascade_backrefs=False, + back_populates="quantity", + ) + + material_types = association_proxy( + "_material_types", + "material_type", + creator=lambda mtype: MaterialQuantityMaterialType(material_type=mtype), + ) + + def render_as_text(self, config=None): + text = super().render_as_text(config) + mtypes = ", ".join([str(mt) for mt in self.material_types]) + return f"{mtypes} {text}" + + +add_quantity_proxies(MaterialQuantity) + + +class MaterialQuantityMaterialType(model.Base): + """ + Represents a "material quantity's material type relationship" from + farmOS. + """ + + __tablename__ = "quantity_material_material_type" + __versioned__ = {} + + uuid = model.uuid_column() + + quantity_uuid = model.uuid_fk_column("quantity_material.uuid", nullable=False) + quantity = orm.relationship( + MaterialQuantity, + foreign_keys=quantity_uuid, + back_populates="_material_types", + ) + + material_type_uuid = model.uuid_fk_column("material_type.uuid", nullable=False) + material_type = orm.relationship( + "MaterialType", + foreign_keys=material_type_uuid, + back_populates="_quantities", + ) diff --git a/src/wuttafarm/farmos/importing/model.py b/src/wuttafarm/farmos/importing/model.py index 980c329..4905a5e 100644 --- a/src/wuttafarm/farmos/importing/model.py +++ b/src/wuttafarm/farmos/importing/model.py @@ -617,6 +617,49 @@ class ToFarmOSQuantity(ToFarmOS): return payload +class MaterialQuantityImporter(ToFarmOSQuantity): + + model_title = "MaterialQuantity" + farmos_quantity_type = "material" + + def get_supported_fields(self): + fields = list(super().get_supported_fields()) + fields.extend( + [ + "material_types", + ] + ) + return fields + + def normalize_target_object(self, quantity): + data = super().normalize_target_object(quantity) + + if "material_types" in self.fields: + data["material_types"] = [ + UUID(mtype["id"]) + for mtype in quantity["relationships"]["material_type"]["data"] + ] + + return data + + def get_quantity_payload(self, source_data): + payload = super().get_quantity_payload(source_data) + + rels = {} + if "material_types" in self.fields: + rels["material_type"] = {"data": []} + for uuid in source_data["material_types"]: + rels["material_type"]["data"].append( + { + "id": str(uuid), + "type": "taxonomy_term--material_type", + } + ) + + payload.setdefault("relationships", {}).update(rels) + return payload + + class StandardQuantityImporter(ToFarmOSQuantity): model_title = "StandardQuantity" diff --git a/src/wuttafarm/farmos/importing/wuttafarm.py b/src/wuttafarm/farmos/importing/wuttafarm.py index 549d3b0..7bb9538 100644 --- a/src/wuttafarm/farmos/importing/wuttafarm.py +++ b/src/wuttafarm/farmos/importing/wuttafarm.py @@ -106,6 +106,7 @@ class FromWuttaFarmToFarmOS(FromWuttaFarmHandler, ToFarmOSHandler): importers["PlantAsset"] = PlantAssetImporter importers["Unit"] = UnitImporter importers["MaterialType"] = MaterialTypeImporter + importers["MaterialQuantity"] = MaterialQuantityImporter importers["StandardQuantity"] = StandardQuantityImporter importers["ActivityLog"] = ActivityLogImporter importers["HarvestLog"] = HarvestLogImporter @@ -449,6 +450,24 @@ class FromWuttaFarmQuantity(FromWuttaFarm): } +class MaterialQuantityImporter( + FromWuttaFarmQuantity, farmos_importing.model.MaterialQuantityImporter +): + """ + WuttaFarm → farmOS API exporter for Material Quantities + """ + + source_model_class = model.MaterialQuantity + + def normalize_source_object(self, quantity): + data = super().normalize_source_object(quantity) + + if "material_types" in self.fields: + data["material_types"] = [mt.farmos_uuid for mt in quantity.material_types] + + return data + + class StandardQuantityImporter( FromWuttaFarmQuantity, farmos_importing.model.StandardQuantityImporter ): diff --git a/src/wuttafarm/importing/farmos.py b/src/wuttafarm/importing/farmos.py index 58eff68..695d373 100644 --- a/src/wuttafarm/importing/farmos.py +++ b/src/wuttafarm/importing/farmos.py @@ -117,6 +117,7 @@ class FromFarmOSToWuttaFarm(FromFarmOSHandler, ToWuttaFarmHandler): importers["MaterialType"] = MaterialTypeImporter importers["QuantityType"] = QuantityTypeImporter importers["StandardQuantity"] = StandardQuantityImporter + importers["MaterialQuantity"] = MaterialQuantityImporter importers["LogType"] = LogTypeImporter importers["ActivityLog"] = ActivityLogImporter importers["HarvestLog"] = HarvestLogImporter @@ -1419,3 +1420,76 @@ class StandardQuantityImporter(QuantityImporterBase): "units_uuid", "label", ] + + +class MaterialQuantityImporter(QuantityImporterBase): + """ + farmOS API → WuttaFarm importer for Material Quantities + """ + + model_class = model.MaterialQuantity + + def get_supported_fields(self): + fields = list(super().get_supported_fields()) + fields.extend( + [ + "material_types", + ] + ) + return fields + + def normalize_source_object(self, quantity): + """ """ + data = super().normalize_source_object(quantity) + + if "material_types" in self.fields: + data["material_types"] = [ + UUID(mtype["id"]) + for mtype in quantity["relationships"]["material_type"]["data"] + ] + + return data + + def normalize_target_object(self, quantity): + data = super().normalize_target_object(quantity) + + if "material_types" in self.fields: + data["material_types"] = [ + mtype.farmos_uuid for mtype in quantity.material_types + ] + + return data + + def update_target_object(self, quantity, source_data, target_data=None): + model = self.app.model + quantity = super().update_target_object(quantity, source_data, target_data) + + if "material_types" in self.fields: + if ( + not target_data + or target_data["material_types"] != source_data["material_types"] + ): + + for farmos_uuid in source_data["material_types"]: + if ( + not target_data + or farmos_uuid not in target_data["material_types"] + ): + mtype = ( + self.target_session.query(model.MaterialType) + .filter(model.MaterialType.farmos_uuid == farmos_uuid) + .one() + ) + quantity.material_types.append(mtype) + + if target_data: + for farmos_uuid in target_data["material_types"]: + if farmos_uuid not in source_data["material_types"]: + mtype = ( + self.target_session.query(model.MaterialType) + .filter(model.MaterialType.farmos_uuid == farmos_uuid) + .one() + ) + quantity.material_types.remove(mtype) + + return quantity diff --git a/src/wuttafarm/normal.py b/src/wuttafarm/normal.py index 38edbab..d03a3ec 100644 --- a/src/wuttafarm/normal.py +++ b/src/wuttafarm/normal.py @@ -260,27 +260,29 @@ class Normalizer(GenericHandler): measure_id = attrs["measure"] - quantity_objects.append( - { - "uuid": quantity["id"], - "drupal_id": attrs["drupal_internal__id"], - "quantity_type_uuid": rels["quantity_type"]["data"][ - "id" - ], - "quantity_type_id": rels["quantity_type"]["data"][ - "meta" - ]["drupal_internal__target_id"], - "measure_id": measure_id, - "measure_name": self.get_farmos_measure_name( - measure_id - ), - "value_numerator": value["numerator"], - "value_decimal": value["decimal"], - "value_denominator": value["denominator"], - "unit_uuid": unit_uuid, - "unit_name": unit["attributes"]["name"], - } - ) + quantity_object = { + "uuid": quantity["id"], + "drupal_id": attrs["drupal_internal__id"], + "quantity_type_uuid": rels["quantity_type"]["data"]["id"], + "quantity_type_id": rels["quantity_type"]["data"]["meta"][ + "drupal_internal__target_id" + ], + "measure_id": measure_id, + "measure_name": self.get_farmos_measure_name(measure_id), + "value_numerator": value["numerator"], + "value_decimal": value["decimal"], + "value_denominator": value["denominator"], + "unit_uuid": unit_uuid, + "unit_name": unit["attributes"]["name"], + } + if quantity_object["quantity_type_id"] == "material": + quantity_object["material_types"] = [ + {"uuid": mtype["id"]} + for mtype in quantity["relationships"]["material_type"][ + "data" + ] + ] + quantity_objects.append(quantity_object) if owners := relationships.get("owner"): for user in owners["data"]: diff --git a/src/wuttafarm/web/forms/schema.py b/src/wuttafarm/web/forms/schema.py index f9dc33a..9543c51 100644 --- a/src/wuttafarm/web/forms/schema.py +++ b/src/wuttafarm/web/forms/schema.py @@ -164,10 +164,9 @@ class FarmOSRefs(WuttaSet): self.route_prefix = route_prefix def serialize(self, node, appstruct): - if appstruct is colander.null: + if not appstruct: return colander.null - - return json.dumps(appstruct) + return appstruct def widget_maker(self, **kwargs): from wuttafarm.web.forms.widgets import FarmOSRefsWidget @@ -288,6 +287,37 @@ class PlantTypeRefs(WuttaSet): return PlantTypeRefsWidget(self.request, **kwargs) +class MaterialTypeRefs(colander.List): + """ + Schema type for Material Types field (on a Material Asset). + """ + + 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 + + mtypes = [] + for mtype in appstruct: + mtypes.append( + { + "uuid": mtype.uuid.hex, + "name": mtype.name, + } + ) + return mtypes + + def widget_maker(self, **kwargs): + from wuttafarm.web.forms.widgets import MaterialTypeRefsWidget + + return MaterialTypeRefsWidget(self.request, **kwargs) + + class SeasonRefs(WuttaSet): """ Schema type for Plant Types field (on a Plant Asset). @@ -454,22 +484,36 @@ class QuantityRefs(colander.List): 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), - } - ) + + quantity = { + "uuid": qty.uuid.hex, + "quantity_type": { + "drupal_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), + # nb. always include this regardless of quantity type, + # for sake of easier frontend logic + "material_types": [], + } + + if qty.quantity_type_id == "material": + quantity["material_types"] = [] + for mtype in qty.material_types: + quantity["material_types"].append( + { + "uuid": mtype.uuid.hex, + "name": mtype.name, + } + ) + + quantities.append(quantity) return quantities diff --git a/src/wuttafarm/web/forms/widgets.py b/src/wuttafarm/web/forms/widgets.py index f1cf067..355adcc 100644 --- a/src/wuttafarm/web/forms/widgets.py +++ b/src/wuttafarm/web/forms/widgets.py @@ -124,7 +124,7 @@ class FarmOSRefsWidget(Widget): return HTML.tag("span") links = [] - for obj in json.loads(cstruct): + for obj in cstruct: url = self.request.route_url( f"{self.route_prefix}.view", uuid=obj["uuid"] ) @@ -332,6 +332,72 @@ class PlantTypeRefsWidget(Widget): return set(pstruct.split(",")) +class MaterialTypeRefsWidget(Widget): + """ + Widget for Material Types field (on a Material Asset). + """ + + template = "materialtyperefs" + values = () + + 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 + session = Session() + + if not cstruct: + cstruct = [] + + if readonly := kw.get("readonly", self.readonly): + items = [] + for mtype in cstruct: + items.append( + HTML.tag( + "li", + c=tags.link_to( + mtype["name"], + self.request.route_url( + "material_types.view", uuid=mtype["uuid"] + ), + ), + ) + ) + return HTML.tag("ul", c=items) + + tmpl_values = self.get_template_values(field, cstruct, kw) + return field.renderer(self.template, **tmpl_values) + + def get_template_values(self, field, cstruct, kw): + """ """ + values = super().get_template_values(field, cstruct, kw) + session = Session() + + material_types = [] + for mtype in self.app.get_material_types(session): + material_types.append( + { + "uuid": mtype.uuid.hex, + "name": mtype.name, + } + ) + values["material_types"] = json.dumps(material_types) + + return values + + def deserialize(self, field, pstruct): + """ """ + if not pstruct: + return [] + + return json.loads(pstruct) + + class SeasonRefsWidget(Widget): """ Widget for Seasons field (on a Plant Asset). @@ -550,11 +616,10 @@ class QuantityRefsWidget(Widget): return "" quantities = [] - for qty in cstruct: - # TODO: support more quantity types url = self.request.route_url( - "quantities_standard.view", uuid=qty["uuid"] + f"quantities_{qty['quantity_type']['drupal_id']}.view", + uuid=qty["uuid"], ) quantities.append(HTML.tag("li", c=tags.link_to(qty["as_text"], url))) @@ -570,17 +635,25 @@ class QuantityRefsWidget(Widget): 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, - } - ) + qtypes.append( + { + "uuid": qtype.uuid.hex, + "drupal_id": qtype.drupal_id, + "name": qtype.name, + } + ) values["quantity_types"] = qtypes + material_types = [] + for mtype in self.app.get_material_types(session): + material_types.append( + { + "uuid": mtype.uuid.hex, + "name": mtype.name, + } + ) + values["material_types"] = material_types + measures = [] for measure in self.app.get_measures(session): measures.append( diff --git a/src/wuttafarm/web/menus.py b/src/wuttafarm/web/menus.py index 9252e5b..249c210 100644 --- a/src/wuttafarm/web/menus.py +++ b/src/wuttafarm/web/menus.py @@ -182,6 +182,11 @@ class WuttaFarmMenuHandler(base.MenuHandler): "route": "quantities", "perm": "quantities.list", }, + { + "title": "Material Quantities", + "route": "quantities_material", + "perm": "quantities_material.list", + }, { "title": "Standard Quantities", "route": "quantities_standard", @@ -322,6 +327,11 @@ class WuttaFarmMenuHandler(base.MenuHandler): "route": "farmos_quantity_types", "perm": "farmos_quantity_types.list", }, + { + "title": "Material Quantities", + "route": "farmos_quantities_material", + "perm": "farmos_quantities_material.list", + }, { "title": "Standard Quantities", "route": "farmos_quantities_standard", @@ -451,6 +461,11 @@ class WuttaFarmMenuHandler(base.MenuHandler): "route": "farmos_quantity_types", "perm": "farmos_quantity_types.list", }, + { + "title": "Material Quantities", + "route": "farmos_quantities_material", + "perm": "farmos_quantities_material.list", + }, { "title": "Standard Quantities", "route": "farmos_quantities_standard", diff --git a/src/wuttafarm/web/templates/deform/materialtyperefs.pt b/src/wuttafarm/web/templates/deform/materialtyperefs.pt new file mode 100644 index 0000000..44ac6e8 --- /dev/null +++ b/src/wuttafarm/web/templates/deform/materialtyperefs.pt @@ -0,0 +1,13 @@ +
+ + + +
diff --git a/src/wuttafarm/web/templates/deform/quantityrefs.pt b/src/wuttafarm/web/templates/deform/quantityrefs.pt index 5c98b84..cc65f77 100644 --- a/src/wuttafarm/web/templates/deform/quantityrefs.pt +++ b/src/wuttafarm/web/templates/deform/quantityrefs.pt @@ -7,6 +7,7 @@ diff --git a/src/wuttafarm/web/templates/wuttafarm-components.mako b/src/wuttafarm/web/templates/wuttafarm-components.mako index d735274..d8f94b3 100644 --- a/src/wuttafarm/web/templates/wuttafarm-components.mako +++ b/src/wuttafarm/web/templates/wuttafarm-components.mako @@ -2,6 +2,7 @@ <%def name="make_wuttafarm_components()"> ${self.make_assets_picker_component()} ${self.make_animal_type_picker_component()} + ${self.make_material_types_picker_component()} ${self.make_quantity_editor_component()} ${self.make_quantities_editor_component()} ${self.make_plant_types_picker_component()} @@ -241,10 +242,134 @@ +<%def name="make_material_types_picker_component()"> + + + + <%def name="make_quantity_editor_component()">