diff --git a/src/wuttafarm/db/alembic/versions/e5b27eac471c_add_equipmenttype.py b/src/wuttafarm/db/alembic/versions/e5b27eac471c_add_equipmenttype.py new file mode 100644 index 0000000..a436725 --- /dev/null +++ b/src/wuttafarm/db/alembic/versions/e5b27eac471c_add_equipmenttype.py @@ -0,0 +1,118 @@ +"""add EquipmentType + +Revision ID: e5b27eac471c +Revises: de1197d24485 +Create Date: 2026-03-09 15:45:35.047694 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +import wuttjamaican.db.util + + +# revision identifiers, used by Alembic. +revision: str = "e5b27eac471c" +down_revision: Union[str, None] = "de1197d24485" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + + # equipment_type + op.create_table( + "equipment_type", + sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False), + sa.Column("name", sa.String(length=100), nullable=False), + sa.Column("description", sa.String(length=255), nullable=True), + sa.Column("drupal_id", sa.Integer(), nullable=True), + sa.Column("farmos_uuid", wuttjamaican.db.util.UUID(), nullable=True), + sa.PrimaryKeyConstraint("uuid", name=op.f("pk_equipment_type")), + sa.UniqueConstraint("drupal_id", name=op.f("uq_equipment_type_drupal_id")), + sa.UniqueConstraint("farmos_uuid", name=op.f("uq_equipment_type_farmos_uuid")), + sa.UniqueConstraint("name", name=op.f("uq_equipment_type_name")), + ) + op.create_table( + "equipment_type_version", + sa.Column( + "uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False + ), + sa.Column("name", sa.String(length=100), autoincrement=False, nullable=True), + sa.Column( + "description", sa.String(length=255), autoincrement=False, nullable=True + ), + sa.Column("drupal_id", sa.Integer(), autoincrement=False, nullable=True), + sa.Column( + "farmos_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_equipment_type_version") + ), + ) + op.create_index( + op.f("ix_equipment_type_version_end_transaction_id"), + "equipment_type_version", + ["end_transaction_id"], + unique=False, + ) + op.create_index( + op.f("ix_equipment_type_version_operation_type"), + "equipment_type_version", + ["operation_type"], + unique=False, + ) + op.create_index( + "ix_equipment_type_version_pk_transaction_id", + "equipment_type_version", + ["uuid", sa.literal_column("transaction_id DESC")], + unique=False, + ) + op.create_index( + "ix_equipment_type_version_pk_validity", + "equipment_type_version", + ["uuid", "transaction_id", "end_transaction_id"], + unique=False, + ) + op.create_index( + op.f("ix_equipment_type_version_transaction_id"), + "equipment_type_version", + ["transaction_id"], + unique=False, + ) + + +def downgrade() -> None: + + # equipment_type + op.drop_index( + op.f("ix_equipment_type_version_transaction_id"), + table_name="equipment_type_version", + ) + op.drop_index( + "ix_equipment_type_version_pk_validity", table_name="equipment_type_version" + ) + op.drop_index( + "ix_equipment_type_version_pk_transaction_id", + table_name="equipment_type_version", + ) + op.drop_index( + op.f("ix_equipment_type_version_operation_type"), + table_name="equipment_type_version", + ) + op.drop_index( + op.f("ix_equipment_type_version_end_transaction_id"), + table_name="equipment_type_version", + ) + op.drop_table("equipment_type_version") + op.drop_table("equipment_type") diff --git a/src/wuttafarm/db/model/__init__.py b/src/wuttafarm/db/model/__init__.py index e2b96c4..7475543 100644 --- a/src/wuttafarm/db/model/__init__.py +++ b/src/wuttafarm/db/model/__init__.py @@ -42,6 +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_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 new file mode 100644 index 0000000..6a589f1 --- /dev/null +++ b/src/wuttafarm/db/model/asset_equipment.py @@ -0,0 +1,41 @@ +# -*- 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 Equipment +""" + +from wuttjamaican.db import model + +from wuttafarm.db.model.taxonomy import TaxonomyMixin + + +class EquipmentType(TaxonomyMixin, model.Base): + """ + Represents a "equipment type" (taxonomy term) from farmOS + """ + + __tablename__ = "equipment_type" + __versioned__ = {} + __wutta_hint__ = { + "model_title": "Equipment Type", + "model_title_plural": "Equipment Types", + } diff --git a/src/wuttafarm/db/model/taxonomy.py b/src/wuttafarm/db/model/taxonomy.py new file mode 100644 index 0000000..3d84197 --- /dev/null +++ b/src/wuttafarm/db/model/taxonomy.py @@ -0,0 +1,74 @@ +# -*- 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 . +# +################################################################################ +""" +Base logic for taxonomy term models +""" + +import sqlalchemy as sa + +from wuttjamaican.db import model + + +class TaxonomyMixin: + """ + Mixin for taxonomy term models + """ + + uuid = model.uuid_column() + + name = sa.Column( + sa.String(length=100), + nullable=False, + unique=True, + doc=""" + Name for the taxonomy term. + """, + ) + + description = sa.Column( + sa.String(length=255), + nullable=True, + doc=""" + Optional description for the taxonomy term. + """, + ) + + drupal_id = sa.Column( + sa.Integer(), + nullable=True, + unique=True, + doc=""" + Drupal internal ID for the taxonomy term. + """, + ) + + farmos_uuid = sa.Column( + model.UUID(), + nullable=True, + unique=True, + doc=""" + UUID for the taxonomy term within farmOS. + """, + ) + + def __str__(self): + return self.name or "" diff --git a/src/wuttafarm/farmos/importing/model.py b/src/wuttafarm/farmos/importing/model.py index 5e6f01b..26e040c 100644 --- a/src/wuttafarm/farmos/importing/model.py +++ b/src/wuttafarm/farmos/importing/model.py @@ -319,6 +319,12 @@ class AnimalTypeImporter(ToFarmOSTaxonomy): farmos_taxonomy_type = "animal_type" +class EquipmentTypeImporter(ToFarmOSTaxonomy): + + model_title = "EquipmentType" + farmos_taxonomy_type = "equipment_type" + + class MaterialTypeImporter(ToFarmOSTaxonomy): model_title = "MaterialType" diff --git a/src/wuttafarm/farmos/importing/wuttafarm.py b/src/wuttafarm/farmos/importing/wuttafarm.py index 7f99b51..f6e7a39 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["EquipmentType"] = EquipmentTypeImporter importers["AnimalType"] = AnimalTypeImporter importers["AnimalAsset"] = AnimalAssetImporter importers["GroupAsset"] = GroupAssetImporter @@ -224,6 +225,16 @@ class FromWuttaFarmTaxonomy(FromWuttaFarm): } +class EquipmentTypeImporter( + FromWuttaFarmTaxonomy, farmos_importing.model.EquipmentTypeImporter +): + """ + WuttaFarm → farmOS API exporter for Equipment Types + """ + + source_model_class = model.EquipmentType + + class AnimalTypeImporter( FromWuttaFarmTaxonomy, farmos_importing.model.AnimalTypeImporter ): diff --git a/src/wuttafarm/importing/farmos.py b/src/wuttafarm/importing/farmos.py index a16393d..dda4974 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["EquipmentType"] = EquipmentTypeImporter importers["AnimalType"] = AnimalTypeImporter importers["AnimalAsset"] = AnimalAssetImporter importers["GroupAsset"] = GroupAssetImporter @@ -451,6 +452,15 @@ class MaterialTypeImporter(TaxonomyImporterBase): taxonomy_type = "material_type" +class EquipmentTypeImporter(TaxonomyImporterBase): + """ + farmOS API → WuttaFarm importer for Equipment Types + """ + + model_class = model.EquipmentType + taxonomy_type = "equipment_type" + + class AssetTypeImporter(FromFarmOS, ToWutta): """ farmOS API → WuttaFarm importer for Asset Types diff --git a/src/wuttafarm/web/menus.py b/src/wuttafarm/web/menus.py index e518361..ed7a11a 100644 --- a/src/wuttafarm/web/menus.py +++ b/src/wuttafarm/web/menus.py @@ -123,6 +123,11 @@ class WuttaFarmMenuHandler(base.MenuHandler): "route": "animal_types", "perm": "animal_types.list", }, + { + "title": "Equipment Types", + "route": "equipment_types", + "perm": "equipment_types.list", + }, { "title": "Land Types", "route": "land_types", @@ -296,6 +301,11 @@ class WuttaFarmMenuHandler(base.MenuHandler): "route": "farmos_animal_types", "perm": "farmos_animal_types.list", }, + { + "title": "Equipment Types", + "route": "farmos_equipment_types", + "perm": "farmos_equipment_types.list", + }, { "title": "Land Types", "route": "farmos_land_types", @@ -404,6 +414,11 @@ class WuttaFarmMenuHandler(base.MenuHandler): "route": "farmos_animal_types", "perm": "farmos_animal_types.list", }, + { + "title": "Equipment Types", + "route": "farmos_equipment_types", + "perm": "farmos_equipment_types.list", + }, { "title": "Land Types", "route": "farmos_land_types", diff --git a/src/wuttafarm/web/views/__init__.py b/src/wuttafarm/web/views/__init__.py index 94f8772..8e57f34 100644 --- a/src/wuttafarm/web/views/__init__.py +++ b/src/wuttafarm/web/views/__init__.py @@ -54,6 +54,7 @@ def includeme(config): config.include("wuttafarm.web.views.assets") config.include("wuttafarm.web.views.land") config.include("wuttafarm.web.views.structures") + config.include("wuttafarm.web.views.equipment") config.include("wuttafarm.web.views.animals") config.include("wuttafarm.web.views.groups") config.include("wuttafarm.web.views.plants") diff --git a/src/wuttafarm/web/views/equipment.py b/src/wuttafarm/web/views/equipment.py new file mode 100644 index 0000000..5e080ef --- /dev/null +++ b/src/wuttafarm/web/views/equipment.py @@ -0,0 +1,53 @@ +# -*- 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 Plants +""" + +from wuttafarm.db.model import EquipmentType +from wuttafarm.web.views import TaxonomyMasterView + + +class EquipmentTypeView(TaxonomyMasterView): + """ + Master view for Equipment Types + """ + + model_class = EquipmentType + route_prefix = "equipment_types" + url_prefix = "/equipment-types" + + farmos_route_prefix = "farmos_equipment_types" + farmos_entity_type = "taxonomy_term" + farmos_bundle = "equipment_type" + farmos_refurl_path = "/admin/structure/taxonomy/manage/equipment_type/overview" + + +def defaults(config, **kwargs): + base = globals() + + EquipmentTypeView = kwargs.get("EquipmentTypeView", base["EquipmentTypeView"]) + EquipmentTypeView.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 2de8ff9..c92a04c 100644 --- a/src/wuttafarm/web/views/farmos/__init__.py +++ b/src/wuttafarm/web/views/farmos/__init__.py @@ -36,6 +36,7 @@ def includeme(config): config.include("wuttafarm.web.views.farmos.land_assets") config.include("wuttafarm.web.views.farmos.structure_types") config.include("wuttafarm.web.views.farmos.structures") + config.include("wuttafarm.web.views.farmos.equipment") config.include("wuttafarm.web.views.farmos.animal_types") config.include("wuttafarm.web.views.farmos.animals") config.include("wuttafarm.web.views.farmos.groups") diff --git a/src/wuttafarm/web/views/farmos/equipment.py b/src/wuttafarm/web/views/farmos/equipment.py new file mode 100644 index 0000000..67987c3 --- /dev/null +++ b/src/wuttafarm/web/views/farmos/equipment.py @@ -0,0 +1,76 @@ +# -*- 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 Equipment +""" + +from wuttafarm.web.views.farmos.master import TaxonomyMasterView + + +class EquipmentTypeView(TaxonomyMasterView): + """ + Master view for Equipment Types in farmOS. + """ + + model_name = "farmos_equipment_type" + model_title = "farmOS Equipment Type" + model_title_plural = "farmOS Equipment Types" + + route_prefix = "farmos_equipment_types" + url_prefix = "/farmOS/equipment-types" + + farmos_taxonomy_type = "equipment_type" + farmos_refurl_path = "/admin/structure/taxonomy/manage/equipment_type/overview" + + def get_xref_buttons(self, equipment_type): + buttons = super().get_xref_buttons(equipment_type) + model = self.app.model + session = self.Session() + + if wf_equipment_type := ( + session.query(model.EquipmentType) + .filter(model.EquipmentType.farmos_uuid == equipment_type["uuid"]) + .first() + ): + buttons.append( + self.make_button( + f"View {self.app.get_title()} record", + primary=True, + url=self.request.route_url( + "equipment_types.view", uuid=wf_equipment_type.uuid + ), + icon_left="eye", + ) + ) + + return buttons + + +def defaults(config, **kwargs): + base = globals() + + EquipmentTypeView = kwargs.get("EquipmentTypeView", base["EquipmentTypeView"]) + EquipmentTypeView.defaults(config) + + +def includeme(config): + defaults(config)