diff --git a/src/wuttafarm/db/alembic/versions/e9b8664e1f39_add_equipmentasset.py b/src/wuttafarm/db/alembic/versions/e9b8664e1f39_add_equipmentasset.py new file mode 100644 index 0000000..2a8ed15 --- /dev/null +++ b/src/wuttafarm/db/alembic/versions/e9b8664e1f39_add_equipmentasset.py @@ -0,0 +1,218 @@ +"""add EquipmentAsset + +Revision ID: e9b8664e1f39 +Revises: e5b27eac471c +Create Date: 2026-03-09 18:05:54.917562 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +import wuttjamaican.db.util + + +# revision identifiers, used by Alembic. +revision: str = "e9b8664e1f39" +down_revision: Union[str, None] = "e5b27eac471c" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + + # asset_equipment + op.create_table( + "asset_equipment", + sa.Column("manufacturer", sa.String(length=255), nullable=True), + sa.Column("model", sa.String(length=255), nullable=True), + sa.Column("serial_number", sa.String(length=255), nullable=True), + sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False), + sa.ForeignKeyConstraint( + ["uuid"], ["asset.uuid"], name=op.f("fk_asset_equipment_uuid_asset") + ), + sa.PrimaryKeyConstraint("uuid", name=op.f("pk_asset_equipment")), + ) + op.create_table( + "asset_equipment_version", + sa.Column( + "manufacturer", sa.String(length=255), autoincrement=False, nullable=True + ), + sa.Column("model", sa.String(length=255), autoincrement=False, nullable=True), + sa.Column( + "serial_number", sa.String(length=255), autoincrement=False, nullable=True + ), + 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_equipment_version") + ), + ) + op.create_index( + op.f("ix_asset_equipment_version_end_transaction_id"), + "asset_equipment_version", + ["end_transaction_id"], + unique=False, + ) + op.create_index( + op.f("ix_asset_equipment_version_operation_type"), + "asset_equipment_version", + ["operation_type"], + unique=False, + ) + op.create_index( + "ix_asset_equipment_version_pk_transaction_id", + "asset_equipment_version", + ["uuid", sa.literal_column("transaction_id DESC")], + unique=False, + ) + op.create_index( + "ix_asset_equipment_version_pk_validity", + "asset_equipment_version", + ["uuid", "transaction_id", "end_transaction_id"], + unique=False, + ) + op.create_index( + op.f("ix_asset_equipment_version_transaction_id"), + "asset_equipment_version", + ["transaction_id"], + unique=False, + ) + + # asset_equipment_equipment_type + op.create_table( + "asset_equipment_equipment_type", + sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False), + sa.Column("equipment_asset_uuid", wuttjamaican.db.util.UUID(), nullable=False), + sa.Column("equipment_type_uuid", wuttjamaican.db.util.UUID(), nullable=False), + sa.ForeignKeyConstraint( + ["equipment_asset_uuid"], + ["asset_equipment.uuid"], + name=op.f( + "fk_asset_equipment_equipment_type_equipment_asset_uuid_asset_equipment" + ), + ), + sa.ForeignKeyConstraint( + ["equipment_type_uuid"], + ["equipment_type.uuid"], + name=op.f( + "fk_asset_equipment_equipment_type_equipment_type_uuid_equipment_type" + ), + ), + sa.PrimaryKeyConstraint("uuid", name=op.f("pk_asset_equipment_equipment_type")), + ) + op.create_table( + "asset_equipment_equipment_type_version", + sa.Column( + "uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False + ), + sa.Column( + "equipment_asset_uuid", + wuttjamaican.db.util.UUID(), + autoincrement=False, + nullable=True, + ), + sa.Column( + "equipment_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_asset_equipment_equipment_type_version"), + ), + ) + op.create_index( + op.f("ix_asset_equipment_equipment_type_version_end_transaction_id"), + "asset_equipment_equipment_type_version", + ["end_transaction_id"], + unique=False, + ) + op.create_index( + op.f("ix_asset_equipment_equipment_type_version_operation_type"), + "asset_equipment_equipment_type_version", + ["operation_type"], + unique=False, + ) + op.create_index( + "ix_asset_equipment_equipment_type_version_pk_transaction_id", + "asset_equipment_equipment_type_version", + ["uuid", sa.literal_column("transaction_id DESC")], + unique=False, + ) + op.create_index( + "ix_asset_equipment_equipment_type_version_pk_validity", + "asset_equipment_equipment_type_version", + ["uuid", "transaction_id", "end_transaction_id"], + unique=False, + ) + op.create_index( + op.f("ix_asset_equipment_equipment_type_version_transaction_id"), + "asset_equipment_equipment_type_version", + ["transaction_id"], + unique=False, + ) + + +def downgrade() -> None: + + # asset_equipment_equipment_type + op.drop_index( + op.f("ix_asset_equipment_equipment_type_version_transaction_id"), + table_name="asset_equipment_equipment_type_version", + ) + op.drop_index( + "ix_asset_equipment_equipment_type_version_pk_validity", + table_name="asset_equipment_equipment_type_version", + ) + op.drop_index( + "ix_asset_equipment_equipment_type_version_pk_transaction_id", + table_name="asset_equipment_equipment_type_version", + ) + op.drop_index( + op.f("ix_asset_equipment_equipment_type_version_operation_type"), + table_name="asset_equipment_equipment_type_version", + ) + op.drop_index( + op.f("ix_asset_equipment_equipment_type_version_end_transaction_id"), + table_name="asset_equipment_equipment_type_version", + ) + op.drop_table("asset_equipment_equipment_type_version") + op.drop_table("asset_equipment_equipment_type") + + # asset_equipment + op.drop_index( + op.f("ix_asset_equipment_version_transaction_id"), + table_name="asset_equipment_version", + ) + op.drop_index( + "ix_asset_equipment_version_pk_validity", table_name="asset_equipment_version" + ) + op.drop_index( + "ix_asset_equipment_version_pk_transaction_id", + table_name="asset_equipment_version", + ) + op.drop_index( + op.f("ix_asset_equipment_version_operation_type"), + table_name="asset_equipment_version", + ) + op.drop_index( + op.f("ix_asset_equipment_version_end_transaction_id"), + table_name="asset_equipment_version", + ) + op.drop_table("asset_equipment_version") + op.drop_table("asset_equipment") diff --git a/src/wuttafarm/db/model/__init__.py b/src/wuttafarm/db/model/__init__.py index 7475543..df4115a 100644 --- a/src/wuttafarm/db/model/__init__.py +++ b/src/wuttafarm/db/model/__init__.py @@ -42,7 +42,7 @@ from .quantities import ( from .asset import AssetType, Asset, AssetParent from .asset_land import LandType, LandAsset from .asset_structure import StructureType, StructureAsset -from .asset_equipment import EquipmentType +from .asset_equipment import EquipmentType, EquipmentAsset, EquipmentAssetEquipmentType from .asset_animal import AnimalType, AnimalAsset from .asset_group import GroupAsset from .asset_plant import ( diff --git a/src/wuttafarm/db/model/asset_equipment.py b/src/wuttafarm/db/model/asset_equipment.py index 6a589f1..51af9ee 100644 --- a/src/wuttafarm/db/model/asset_equipment.py +++ b/src/wuttafarm/db/model/asset_equipment.py @@ -23,14 +23,19 @@ Model definition for Equipment """ +import sqlalchemy as sa +from sqlalchemy import orm +from sqlalchemy.ext.associationproxy import association_proxy + from wuttjamaican.db import model from wuttafarm.db.model.taxonomy import TaxonomyMixin +from wuttafarm.db.model.asset import AssetMixin, add_asset_proxies class EquipmentType(TaxonomyMixin, model.Base): """ - Represents a "equipment type" (taxonomy term) from farmOS + Represents an "equipment type" (taxonomy term) from farmOS """ __tablename__ = "equipment_type" @@ -39,3 +44,90 @@ class EquipmentType(TaxonomyMixin, model.Base): "model_title": "Equipment Type", "model_title_plural": "Equipment Types", } + + _equipment_assets = orm.relationship( + "EquipmentAssetEquipmentType", + cascade_backrefs=False, + back_populates="equipment_type", + ) + + +class EquipmentAsset(AssetMixin, model.Base): + """ + Represents an equipment asset from farmOS + """ + + __tablename__ = "asset_equipment" + __versioned__ = {} + __wutta_hint__ = { + "model_title": "Equipment Asset", + "model_title_plural": "Equipment Assets", + "farmos_asset_type": "equipment", + } + + manufacturer = sa.Column( + sa.String(length=255), + nullable=True, + doc=""" + Name of the manufacturer, if applicable. + """, + ) + + model = sa.Column( + sa.String(length=255), + nullable=True, + doc=""" + Model name for the equipment, if applicable. + """, + ) + + serial_number = sa.Column( + sa.String(length=255), + nullable=True, + doc=""" + Serial number for the equipment, if applicable. + """, + ) + + _equipment_types = orm.relationship( + "EquipmentAssetEquipmentType", + cascade="all, delete-orphan", + cascade_backrefs=False, + back_populates="equipment_asset", + ) + + equipment_types = association_proxy( + "_equipment_types", + "equipment_type", + creator=lambda pt: EquipmentAssetEquipmentType(equipment_type=pt), + ) + + +add_asset_proxies(EquipmentAsset) + + +class EquipmentAssetEquipmentType(model.Base): + """ + Associates one or more equipment types with an equipment asset. + """ + + __tablename__ = "asset_equipment_equipment_type" + __versioned__ = {} + + uuid = model.uuid_column() + + equipment_asset_uuid = model.uuid_fk_column("asset_equipment.uuid", nullable=False) + equipment_asset = orm.relationship( + EquipmentAsset, + foreign_keys=equipment_asset_uuid, + back_populates="_equipment_types", + ) + + equipment_type_uuid = model.uuid_fk_column("equipment_type.uuid", nullable=False) + equipment_type = orm.relationship( + EquipmentType, + doc=""" + Reference to the equipment type. + """, + back_populates="_equipment_assets", + ) diff --git a/src/wuttafarm/farmos/importing/model.py b/src/wuttafarm/farmos/importing/model.py index 26e040c..bb1b41c 100644 --- a/src/wuttafarm/farmos/importing/model.py +++ b/src/wuttafarm/farmos/importing/model.py @@ -325,6 +325,68 @@ class EquipmentTypeImporter(ToFarmOSTaxonomy): farmos_taxonomy_type = "equipment_type" +class EquipmentAssetImporter(ToFarmOSAsset): + + model_title = "EquipmentAsset" + farmos_asset_type = "equipment" + + supported_fields = [ + "uuid", + "asset_name", + "manufacturer", + "model", + "serial_number", + "equipment_type_uuids", + "is_location", + "is_fixed", + "notes", + "archived", + ] + + def normalize_target_object(self, equipment): + data = super().normalize_target_object(equipment) + data.update( + { + "manufacturer": equipment["attributes"]["manufacturer"], + "model": equipment["attributes"]["model"], + "serial_number": equipment["attributes"]["serial_number"], + "equipment_type_uuids": [ + UUID(etype["id"]) + for etype in equipment["relationships"]["equipment_type"]["data"] + ], + } + ) + return data + + def get_asset_payload(self, source_data): + payload = super().get_asset_payload(source_data) + + attrs = {} + if "manufacturer" in self.fields: + attrs["manufacturer"] = source_data["manufacturer"] + if "model" in self.fields: + attrs["model"] = source_data["model"] + if "serial_number" in self.fields: + attrs["serial_number"] = source_data["serial_number"] + + rels = {} + if "equipment_type_uuids" in self.fields: + rels["equipment_type"] = {"data": []} + for uuid in source_data["equipment_type_uuids"]: + rels["equipment_type"]["data"].append( + { + "id": str(uuid), + "type": "taxonomy_term--equipment_type", + } + ) + + payload["attributes"].update(attrs) + if rels: + payload.setdefault("relationships", {}).update(rels) + + return payload + + class MaterialTypeImporter(ToFarmOSTaxonomy): model_title = "MaterialType" diff --git a/src/wuttafarm/farmos/importing/wuttafarm.py b/src/wuttafarm/farmos/importing/wuttafarm.py index f6e7a39..bd42e86 100644 --- a/src/wuttafarm/farmos/importing/wuttafarm.py +++ b/src/wuttafarm/farmos/importing/wuttafarm.py @@ -100,6 +100,7 @@ class FromWuttaFarmToFarmOS(FromWuttaFarmHandler, ToFarmOSHandler): importers["StructureAsset"] = StructureAssetImporter importers["WaterAsset"] = WaterAssetImporter importers["EquipmentType"] = EquipmentTypeImporter + importers["EquipmentAsset"] = EquipmentAssetImporter importers["AnimalType"] = AnimalTypeImporter importers["AnimalAsset"] = AnimalAssetImporter importers["GroupAsset"] = GroupAssetImporter @@ -235,6 +236,44 @@ class EquipmentTypeImporter( source_model_class = model.EquipmentType +class EquipmentAssetImporter( + FromWuttaFarmAsset, farmos_importing.model.EquipmentAssetImporter +): + """ + WuttaFarm → farmOS API exporter for Equipment Assets + """ + + source_model_class = model.EquipmentAsset + + def get_supported_fields(self): + fields = list(super().get_supported_fields()) + + print(fields) + fields.extend( + [ + "manufacturer", + "model", + "serial_number", + "equipment_type_uuids", + ] + ) + return fields + + def normalize_source_object(self, equipment): + data = super().normalize_source_object(equipment) + data.update( + { + "manufacturer": equipment.manufacturer, + "model": equipment.model, + "serial_number": equipment.serial_number, + "equipment_type_uuids": [ + etype.farmos_uuid for etype in equipment.equipment_types + ], + } + ) + return data + + class AnimalTypeImporter( FromWuttaFarmTaxonomy, farmos_importing.model.AnimalTypeImporter ): diff --git a/src/wuttafarm/importing/farmos.py b/src/wuttafarm/importing/farmos.py index dda4974..93e8d30 100644 --- a/src/wuttafarm/importing/farmos.py +++ b/src/wuttafarm/importing/farmos.py @@ -108,6 +108,7 @@ class FromFarmOSToWuttaFarm(FromFarmOSHandler, ToWuttaFarmHandler): importers["StructureAsset"] = StructureAssetImporter importers["WaterAsset"] = WaterAssetImporter importers["EquipmentType"] = EquipmentTypeImporter + importers["EquipmentAsset"] = EquipmentAssetImporter importers["AnimalType"] = AnimalTypeImporter importers["AnimalAsset"] = AnimalAssetImporter importers["GroupAsset"] = GroupAssetImporter @@ -489,6 +490,111 @@ class AssetTypeImporter(FromFarmOS, ToWutta): } +class EquipmentAssetImporter(AssetImporterBase): + """ + farmOS API → WuttaFarm importer for Equipment Assets + """ + + model_class = model.EquipmentAsset + + def get_supported_fields(self): + fields = list(super().get_supported_fields()) + fields.extend( + [ + "equipment_types", + ] + ) + return fields + + def setup(self): + super().setup() + model = self.app.model + + self.equipment_types_by_farmos_uuid = {} + for equipment_type in self.target_session.query(model.EquipmentType): + if equipment_type.farmos_uuid: + self.equipment_types_by_farmos_uuid[equipment_type.farmos_uuid] = ( + equipment_type + ) + + def normalize_source_object(self, equipment): + """ """ + data = super().normalize_source_object(equipment) + + equipment_types = [] + if relationships := equipment.get("relationships"): + + if equipment_type := relationships.get("equipment_type"): + equipment_types = [] + for equipment_type in equipment_type["data"]: + if wf_equipment_type := self.equipment_types_by_farmos_uuid.get( + UUID(equipment_type["id"]) + ): + equipment_types.append(wf_equipment_type.uuid) + else: + log.warning( + "equipment type not found: %s", equipment_type["id"] + ) + + data.update( + { + "manufacturer": equipment["attributes"]["manufacturer"], + "model": equipment["attributes"]["model"], + "serial_number": equipment["attributes"]["serial_number"], + "equipment_types": set(equipment_types), + } + ) + return data + + def normalize_target_object(self, equipment): + data = super().normalize_target_object(equipment) + + if "equipment_types" in self.fields: + data["equipment_types"] = set( + [etype.uuid for etype in equipment.equipment_types] + ) + + return data + + def update_target_object(self, equipment, source_data, target_data=None): + model = self.app.model + equipment = super().update_target_object(equipment, source_data, target_data) + + if "equipment_types" in self.fields: + if ( + not target_data + or target_data["equipment_types"] != source_data["equipment_types"] + ): + + for uuid in source_data["equipment_types"]: + if not target_data or uuid not in target_data["equipment_types"]: + self.target_session.flush() + equipment._equipment_types.append( + model.EquipmentAssetEquipmentType(equipment_type_uuid=uuid) + ) + + if target_data: + for uuid in target_data["equipment_types"]: + if uuid not in source_data["equipment_types"]: + equipment_type = ( + self.target_session.query( + model.EquipmentAssetEquipmentType + ) + .filter( + model.EquipmentAssetEquipmentType.equipment_asset + == equipment + ) + .filter( + model.EquipmentAssetEquipmentType.equipment_type_uuid + == uuid + ) + .one() + ) + self.target_session.delete(equipment_type) + + return equipment + + class GroupAssetImporter(AssetImporterBase): """ farmOS API → WuttaFarm importer for Group Assets diff --git a/src/wuttafarm/web/forms/schema.py b/src/wuttafarm/web/forms/schema.py index 9543c51..e24f0cf 100644 --- a/src/wuttafarm/web/forms/schema.py +++ b/src/wuttafarm/web/forms/schema.py @@ -28,7 +28,7 @@ import json import colander from wuttaweb.db import Session -from wuttaweb.forms.schema import ObjectRef, WuttaSet +from wuttaweb.forms.schema import ObjectRef, WuttaSet, WuttaList from wuttaweb.forms.widgets import NotesWidget @@ -216,6 +216,35 @@ class FarmOSQuantityRefs(WuttaSet): return FarmOSQuantityRefsWidget(**kwargs) +class FarmOSTaxonomyTerms(colander.SchemaType): + """ + Schema type which can represent multiple taxonomy terms. + """ + + route_prefix = None + + def __init__(self, request, route_prefix=None, *args, **kwargs): + super().__init__(*args, **kwargs) + self.request = request + if route_prefix: + self.route_prefix = route_prefix + + def serialize(self, node, appstruct): + if not appstruct: + return colander.null + return appstruct + + def widget_maker(self, **kwargs): + from wuttafarm.web.forms.widgets import FarmOSTaxonomyTermsWidget + + return FarmOSTaxonomyTermsWidget(self.request, self.route_prefix, **kwargs) + + +class FarmOSEquipmentTypeRefs(FarmOSTaxonomyTerms): + + route_prefix = "farmos_equipment_types" + + class FarmOSPlantTypes(colander.SchemaType): def __init__(self, request, *args, **kwargs): @@ -260,6 +289,35 @@ class LandTypeRef(ObjectRef): return self.request.route_url("land_types.view", uuid=land_type.uuid) +class TaxonomyTermRefs(WuttaList): + """ + Generic schema type for a field which can reference multiple + taxonomy terms. + """ + + def serialize(self, node, appstruct): + if not appstruct: + return colander.null + + terms = [] + for term in appstruct: + terms.append( + { + "uuid": str(term.uuid), + "name": term.name, + } + ) + return terms + + +class EquipmentTypeRefs(TaxonomyTermRefs): + + def widget_maker(self, **kwargs): + from wuttafarm.web.forms.widgets import EquipmentTypeRefsWidget + + return EquipmentTypeRefsWidget(self.request, **kwargs) + + class PlantTypeRefs(WuttaSet): """ Schema type for Plant Types field (on a Plant Asset). diff --git a/src/wuttafarm/web/forms/widgets.py b/src/wuttafarm/web/forms/widgets.py index 355adcc..db79eae 100644 --- a/src/wuttafarm/web/forms/widgets.py +++ b/src/wuttafarm/web/forms/widgets.py @@ -33,6 +33,7 @@ from wuttaweb.forms.widgets import WuttaCheckboxChoiceWidget, ObjectRefWidget from wuttaweb.db import Session from wuttafarm.web.util import render_quantity_objects +from wuttafarm.db.model import EquipmentType class ImageWidget(Widget): @@ -228,6 +229,38 @@ class FarmOSUnitRefWidget(Widget): return super().serialize(field, cstruct, **kw) +class FarmOSTaxonomyTermsWidget(Widget): + """ + Widget to display a field which can reference multiple taxonomy + terms. + """ + + def __init__(self, request, route_prefix, *args, **kwargs): + super().__init__(*args, **kwargs) + self.request = request + self.route_prefix = route_prefix + + def serialize(self, field, cstruct, **kw): + """ """ + readonly = kw.get("readonly", self.readonly) + if readonly: + if cstruct in (colander.null, None): + return HTML.tag("span") + + links = [] + for term in cstruct: + link = tags.link_to( + term["name"], + self.request.route_url( + f"{self.route_prefix}.view", uuid=term["uuid"] + ), + ) + links.append(HTML.tag("li", c=link)) + return HTML.tag("ul", c=links) + + return super().serialize(field, cstruct, **kw) + + class FarmOSPlantTypesWidget(Widget): """ Widget to display a farmOS "plant types" field. @@ -258,6 +291,88 @@ class FarmOSPlantTypesWidget(Widget): return super().serialize(field, cstruct, **kw) +class TaxonomyTermRefsWidget(Widget): + """ + Generic (incomplete) widget for fields which can reference + multiple taxonomy terms. + + This widget can handle typical read-only scenarios but the + editable mode is not implemented. + """ + + route_prefix = None + + 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() + + @classmethod + def get_route_prefix(cls): + return cls.route_prefix + + @classmethod + def get_permission_prefix(cls): + return cls.route_prefix + + def serialize(self, field, cstruct, **kw): + """ """ + if not cstruct: + cstruct = [] + + if readonly := kw.get("readonly", self.readonly): + items = [] + route_prefix = self.get_route_prefix() + for term in cstruct: + url = self.request.route_url(f"{route_prefix}.view", uuid=term["uuid"]) + link = tags.link_to(term["name"], url) + items.append(HTML.tag("li", c=link)) + 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) + model = self.app.model + session = Session() + + terms = [] + query = session.query(self.model_class).order_by(self.model_class.name) + for term in query: + terms.append( + { + "uuid": str(term.uuid), + "name": term.name, + } + ) + values["terms"] = terms + + permission_prefix = self.get_permission_prefix() + if self.request.has_perm(f"{permission_prefix}.create"): + values["can_create"] = True + + return values + + def deserialize(self, field, pstruct): + """ """ + if not pstruct: + return colander.null + + return json.loads(pstruct) + + +class EquipmentTypeRefsWidget(TaxonomyTermRefsWidget): + """ + Widget for Equipment Types field. + """ + + model_class = EquipmentType + route_prefix = "equipment_types" + template = "equipmenttyperefs" + + class PlantTypeRefsWidget(Widget): """ Widget for Plant Types field (on a Plant Asset). diff --git a/src/wuttafarm/web/menus.py b/src/wuttafarm/web/menus.py index ed7a11a..a44e5a9 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": "Equipment", + "route": "equipment_assets", + "perm": "equipment_assets.list", + }, { "title": "Group", "route": "group_assets", @@ -249,6 +254,11 @@ class WuttaFarmMenuHandler(base.MenuHandler): "route": "farmos_animal_assets", "perm": "farmos_animal_assets.list", }, + { + "title": "Equipment Assets", + "route": "farmos_equipment_assets", + "perm": "farmos_equipment_assets.list", + }, { "title": "Group Assets", "route": "farmos_group_assets", @@ -383,6 +393,11 @@ class WuttaFarmMenuHandler(base.MenuHandler): "route": "farmos_animal_assets", "perm": "farmos_animal_assets.list", }, + { + "title": "Equipment", + "route": "farmos_equipment_assets", + "perm": "farmos_equipment_assets.list", + }, { "title": "Group", "route": "farmos_group_assets", diff --git a/src/wuttafarm/web/templates/deform/equipmenttyperefs.pt b/src/wuttafarm/web/templates/deform/equipmenttyperefs.pt new file mode 100644 index 0000000..4d48fd7 --- /dev/null +++ b/src/wuttafarm/web/templates/deform/equipmenttyperefs.pt @@ -0,0 +1,13 @@ +
+ + + +
diff --git a/src/wuttafarm/web/templates/wuttafarm-components.mako b/src/wuttafarm/web/templates/wuttafarm-components.mako index d8f94b3..890568f 100644 --- a/src/wuttafarm/web/templates/wuttafarm-components.mako +++ b/src/wuttafarm/web/templates/wuttafarm-components.mako @@ -1,5 +1,7 @@ <%def name="make_wuttafarm_components()"> + ${self.make_taxonomy_terms_picker_component()} + ${self.make_equipment_types_picker_component()} ${self.make_assets_picker_component()} ${self.make_animal_type_picker_component()} ${self.make_material_types_picker_component()} @@ -9,6 +11,216 @@ ${self.make_seasons_picker_component()} +<%def name="make_taxonomy_terms_picker_component()"> + + + + +<%def name="make_equipment_types_picker_component()"> + + + + <%def name="make_assets_picker_component()">