diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8cf9487..d096239 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,19 +5,6 @@ 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.5.0 (2026-02-18)
-
-### Feat
-
-- add `produces_eggs` flag for animal, group assets
-- add more assets (plant) and logs (harvest, medical, observation)
-- refactor log models, views to use generic/common base
-
-### Fix
-
-- rename db model modules, for better convention
-- add override for requests cert validation
-
## v0.4.1 (2026-02-17)
### Fix
diff --git a/pyproject.toml b/pyproject.toml
index a474302..44bea43 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -5,7 +5,7 @@ build-backend = "hatchling.build"
[project]
name = "WuttaFarm"
-version = "0.5.0"
+version = "0.4.1"
description = "Web app to integrate with and extend farmOS"
readme = "README.md"
authors = [
diff --git a/src/wuttafarm/db/alembic/versions/11e0e46f48a6_add_plant_assets_and_more_logs.py b/src/wuttafarm/db/alembic/versions/11e0e46f48a6_add_plant_assets_and_more_logs.py
deleted file mode 100644
index 7df55c4..0000000
--- a/src/wuttafarm/db/alembic/versions/11e0e46f48a6_add_plant_assets_and_more_logs.py
+++ /dev/null
@@ -1,596 +0,0 @@
-"""add Plant Assets and more Logs
-
-Revision ID: 11e0e46f48a6
-Revises: dd6351e69233
-Create Date: 2026-02-18 18:11:46.536930
-
-"""
-
-from typing import Sequence, Union
-
-from alembic import op
-import sqlalchemy as sa
-import wuttjamaican.db.util
-
-
-# revision identifiers, used by Alembic.
-revision: str = "11e0e46f48a6"
-down_revision: Union[str, None] = "dd6351e69233"
-branch_labels: Union[str, Sequence[str], None] = None
-depends_on: Union[str, Sequence[str], None] = None
-
-
-def upgrade() -> None:
-
- # plant_type
- op.create_table(
- "plant_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("farmos_uuid", wuttjamaican.db.util.UUID(), nullable=True),
- sa.Column("drupal_id", sa.Integer(), nullable=True),
- sa.PrimaryKeyConstraint("uuid", name=op.f("pk_plant_type")),
- sa.UniqueConstraint("drupal_id", name=op.f("uq_plant_type_drupal_id")),
- sa.UniqueConstraint("farmos_uuid", name=op.f("uq_plant_type_farmos_uuid")),
- sa.UniqueConstraint("name", name=op.f("uq_plant_type_name")),
- )
- op.create_table(
- "plant_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(
- "farmos_uuid",
- wuttjamaican.db.util.UUID(),
- autoincrement=False,
- nullable=True,
- ),
- sa.Column("drupal_id", sa.Integer(), 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_plant_type_version")
- ),
- )
- op.create_index(
- op.f("ix_plant_type_version_end_transaction_id"),
- "plant_type_version",
- ["end_transaction_id"],
- unique=False,
- )
- op.create_index(
- op.f("ix_plant_type_version_operation_type"),
- "plant_type_version",
- ["operation_type"],
- unique=False,
- )
- op.create_index(
- "ix_plant_type_version_pk_transaction_id",
- "plant_type_version",
- ["uuid", sa.literal_column("transaction_id DESC")],
- unique=False,
- )
- op.create_index(
- "ix_plant_type_version_pk_validity",
- "plant_type_version",
- ["uuid", "transaction_id", "end_transaction_id"],
- unique=False,
- )
- op.create_index(
- op.f("ix_plant_type_version_transaction_id"),
- "plant_type_version",
- ["transaction_id"],
- unique=False,
- )
-
- # asset_plant
- op.create_table(
- "asset_plant",
- sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
- sa.ForeignKeyConstraint(
- ["uuid"], ["asset.uuid"], name=op.f("fk_asset_plant_uuid_asset")
- ),
- sa.PrimaryKeyConstraint("uuid", name=op.f("pk_asset_plant")),
- )
- op.create_table(
- "asset_plant_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_plant_version")
- ),
- )
- op.create_index(
- op.f("ix_asset_plant_version_end_transaction_id"),
- "asset_plant_version",
- ["end_transaction_id"],
- unique=False,
- )
- op.create_index(
- op.f("ix_asset_plant_version_operation_type"),
- "asset_plant_version",
- ["operation_type"],
- unique=False,
- )
- op.create_index(
- "ix_asset_plant_version_pk_transaction_id",
- "asset_plant_version",
- ["uuid", sa.literal_column("transaction_id DESC")],
- unique=False,
- )
- op.create_index(
- "ix_asset_plant_version_pk_validity",
- "asset_plant_version",
- ["uuid", "transaction_id", "end_transaction_id"],
- unique=False,
- )
- op.create_index(
- op.f("ix_asset_plant_version_transaction_id"),
- "asset_plant_version",
- ["transaction_id"],
- unique=False,
- )
-
- # asset_plant_plant_type
- op.create_table(
- "asset_plant_plant_type",
- sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
- sa.Column("plant_asset_uuid", wuttjamaican.db.util.UUID(), nullable=False),
- sa.Column("plant_type_uuid", wuttjamaican.db.util.UUID(), nullable=False),
- sa.ForeignKeyConstraint(
- ["plant_asset_uuid"],
- ["asset_plant.uuid"],
- name=op.f("fk_asset_plant_plant_type_plant_asset_uuid_asset_plant"),
- ),
- sa.ForeignKeyConstraint(
- ["plant_type_uuid"],
- ["plant_type.uuid"],
- name=op.f("fk_asset_plant_plant_type_plant_type_uuid_plant_type"),
- ),
- sa.PrimaryKeyConstraint("uuid", name=op.f("pk_asset_plant_plant_type")),
- )
- op.create_table(
- "asset_plant_plant_type_version",
- sa.Column(
- "uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
- ),
- sa.Column(
- "plant_asset_uuid",
- wuttjamaican.db.util.UUID(),
- autoincrement=False,
- nullable=True,
- ),
- sa.Column(
- "plant_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_plant_plant_type_version")
- ),
- )
- op.create_index(
- op.f("ix_asset_plant_plant_type_version_end_transaction_id"),
- "asset_plant_plant_type_version",
- ["end_transaction_id"],
- unique=False,
- )
- op.create_index(
- op.f("ix_asset_plant_plant_type_version_operation_type"),
- "asset_plant_plant_type_version",
- ["operation_type"],
- unique=False,
- )
- op.create_index(
- "ix_asset_plant_plant_type_version_pk_transaction_id",
- "asset_plant_plant_type_version",
- ["uuid", sa.literal_column("transaction_id DESC")],
- unique=False,
- )
- op.create_index(
- "ix_asset_plant_plant_type_version_pk_validity",
- "asset_plant_plant_type_version",
- ["uuid", "transaction_id", "end_transaction_id"],
- unique=False,
- )
- op.create_index(
- op.f("ix_asset_plant_plant_type_version_transaction_id"),
- "asset_plant_plant_type_version",
- ["transaction_id"],
- unique=False,
- )
-
- # log_asset
- op.create_table(
- "log_asset",
- sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
- sa.Column("log_uuid", wuttjamaican.db.util.UUID(), nullable=False),
- sa.Column("asset_uuid", wuttjamaican.db.util.UUID(), nullable=False),
- sa.ForeignKeyConstraint(
- ["asset_uuid"], ["asset.uuid"], name=op.f("fk_log_asset_asset_uuid_asset")
- ),
- sa.ForeignKeyConstraint(
- ["log_uuid"], ["log.uuid"], name=op.f("fk_log_asset_log_uuid_log")
- ),
- sa.PrimaryKeyConstraint("uuid", name=op.f("pk_log_asset")),
- )
- op.create_table(
- "log_asset_version",
- sa.Column(
- "uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
- ),
- sa.Column(
- "log_uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=True
- ),
- sa.Column(
- "asset_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_log_asset_version")
- ),
- )
- op.create_index(
- op.f("ix_log_asset_version_end_transaction_id"),
- "log_asset_version",
- ["end_transaction_id"],
- unique=False,
- )
- op.create_index(
- op.f("ix_log_asset_version_operation_type"),
- "log_asset_version",
- ["operation_type"],
- unique=False,
- )
- op.create_index(
- "ix_log_asset_version_pk_transaction_id",
- "log_asset_version",
- ["uuid", sa.literal_column("transaction_id DESC")],
- unique=False,
- )
- op.create_index(
- "ix_log_asset_version_pk_validity",
- "log_asset_version",
- ["uuid", "transaction_id", "end_transaction_id"],
- unique=False,
- )
- op.create_index(
- op.f("ix_log_asset_version_transaction_id"),
- "log_asset_version",
- ["transaction_id"],
- unique=False,
- )
-
- # log_harvest
- op.create_table(
- "log_harvest",
- sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
- sa.ForeignKeyConstraint(
- ["uuid"], ["log.uuid"], name=op.f("fk_log_harvest_uuid_log")
- ),
- sa.PrimaryKeyConstraint("uuid", name=op.f("pk_log_harvest")),
- )
- op.create_table(
- "log_harvest_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_log_harvest_version")
- ),
- )
- op.create_index(
- op.f("ix_log_harvest_version_end_transaction_id"),
- "log_harvest_version",
- ["end_transaction_id"],
- unique=False,
- )
- op.create_index(
- op.f("ix_log_harvest_version_operation_type"),
- "log_harvest_version",
- ["operation_type"],
- unique=False,
- )
- op.create_index(
- "ix_log_harvest_version_pk_transaction_id",
- "log_harvest_version",
- ["uuid", sa.literal_column("transaction_id DESC")],
- unique=False,
- )
- op.create_index(
- "ix_log_harvest_version_pk_validity",
- "log_harvest_version",
- ["uuid", "transaction_id", "end_transaction_id"],
- unique=False,
- )
- op.create_index(
- op.f("ix_log_harvest_version_transaction_id"),
- "log_harvest_version",
- ["transaction_id"],
- unique=False,
- )
-
- # log_medical
- op.create_table(
- "log_medical",
- sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
- sa.ForeignKeyConstraint(
- ["uuid"], ["log.uuid"], name=op.f("fk_log_medical_uuid_log")
- ),
- sa.PrimaryKeyConstraint("uuid", name=op.f("pk_log_medical")),
- )
- op.create_table(
- "log_medical_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_log_medical_version")
- ),
- )
- op.create_index(
- op.f("ix_log_medical_version_end_transaction_id"),
- "log_medical_version",
- ["end_transaction_id"],
- unique=False,
- )
- op.create_index(
- op.f("ix_log_medical_version_operation_type"),
- "log_medical_version",
- ["operation_type"],
- unique=False,
- )
- op.create_index(
- "ix_log_medical_version_pk_transaction_id",
- "log_medical_version",
- ["uuid", sa.literal_column("transaction_id DESC")],
- unique=False,
- )
- op.create_index(
- "ix_log_medical_version_pk_validity",
- "log_medical_version",
- ["uuid", "transaction_id", "end_transaction_id"],
- unique=False,
- )
- op.create_index(
- op.f("ix_log_medical_version_transaction_id"),
- "log_medical_version",
- ["transaction_id"],
- unique=False,
- )
-
- # log_observation
- op.create_table(
- "log_observation",
- sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
- sa.ForeignKeyConstraint(
- ["uuid"], ["log.uuid"], name=op.f("fk_log_observation_uuid_log")
- ),
- sa.PrimaryKeyConstraint("uuid", name=op.f("pk_log_observation")),
- )
- op.create_table(
- "log_observation_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_log_observation_version")
- ),
- )
- op.create_index(
- op.f("ix_log_observation_version_end_transaction_id"),
- "log_observation_version",
- ["end_transaction_id"],
- unique=False,
- )
- op.create_index(
- op.f("ix_log_observation_version_operation_type"),
- "log_observation_version",
- ["operation_type"],
- unique=False,
- )
- op.create_index(
- "ix_log_observation_version_pk_transaction_id",
- "log_observation_version",
- ["uuid", sa.literal_column("transaction_id DESC")],
- unique=False,
- )
- op.create_index(
- "ix_log_observation_version_pk_validity",
- "log_observation_version",
- ["uuid", "transaction_id", "end_transaction_id"],
- unique=False,
- )
- op.create_index(
- op.f("ix_log_observation_version_transaction_id"),
- "log_observation_version",
- ["transaction_id"],
- unique=False,
- )
-
-
-def downgrade() -> None:
-
- # log_observation
- op.drop_index(
- op.f("ix_log_observation_version_transaction_id"),
- table_name="log_observation_version",
- )
- op.drop_index(
- "ix_log_observation_version_pk_validity", table_name="log_observation_version"
- )
- op.drop_index(
- "ix_log_observation_version_pk_transaction_id",
- table_name="log_observation_version",
- )
- op.drop_index(
- op.f("ix_log_observation_version_operation_type"),
- table_name="log_observation_version",
- )
- op.drop_index(
- op.f("ix_log_observation_version_end_transaction_id"),
- table_name="log_observation_version",
- )
- op.drop_table("log_observation_version")
- op.drop_table("log_observation")
-
- # log_medical
- op.drop_index(
- op.f("ix_log_medical_version_transaction_id"), table_name="log_medical_version"
- )
- op.drop_index(
- "ix_log_medical_version_pk_validity", table_name="log_medical_version"
- )
- op.drop_index(
- "ix_log_medical_version_pk_transaction_id", table_name="log_medical_version"
- )
- op.drop_index(
- op.f("ix_log_medical_version_operation_type"), table_name="log_medical_version"
- )
- op.drop_index(
- op.f("ix_log_medical_version_end_transaction_id"),
- table_name="log_medical_version",
- )
- op.drop_table("log_medical_version")
- op.drop_table("log_medical")
-
- # log_harvest
- op.drop_index(
- op.f("ix_log_harvest_version_transaction_id"), table_name="log_harvest_version"
- )
- op.drop_index(
- "ix_log_harvest_version_pk_validity", table_name="log_harvest_version"
- )
- op.drop_index(
- "ix_log_harvest_version_pk_transaction_id", table_name="log_harvest_version"
- )
- op.drop_index(
- op.f("ix_log_harvest_version_operation_type"), table_name="log_harvest_version"
- )
- op.drop_index(
- op.f("ix_log_harvest_version_end_transaction_id"),
- table_name="log_harvest_version",
- )
- op.drop_table("log_harvest_version")
- op.drop_table("log_harvest")
-
- # log_asset
- op.drop_index(
- op.f("ix_log_asset_version_transaction_id"), table_name="log_asset_version"
- )
- op.drop_index("ix_log_asset_version_pk_validity", table_name="log_asset_version")
- op.drop_index(
- "ix_log_asset_version_pk_transaction_id", table_name="log_asset_version"
- )
- op.drop_index(
- op.f("ix_log_asset_version_operation_type"), table_name="log_asset_version"
- )
- op.drop_index(
- op.f("ix_log_asset_version_end_transaction_id"), table_name="log_asset_version"
- )
- op.drop_table("log_asset_version")
- op.drop_table("log_asset")
-
- # asset_plant_plant_type
- op.drop_index(
- op.f("ix_asset_plant_plant_type_version_transaction_id"),
- table_name="asset_plant_plant_type_version",
- )
- op.drop_index(
- "ix_asset_plant_plant_type_version_pk_validity",
- table_name="asset_plant_plant_type_version",
- )
- op.drop_index(
- "ix_asset_plant_plant_type_version_pk_transaction_id",
- table_name="asset_plant_plant_type_version",
- )
- op.drop_index(
- op.f("ix_asset_plant_plant_type_version_operation_type"),
- table_name="asset_plant_plant_type_version",
- )
- op.drop_index(
- op.f("ix_asset_plant_plant_type_version_end_transaction_id"),
- table_name="asset_plant_plant_type_version",
- )
- op.drop_table("asset_plant_plant_type_version")
- op.drop_table("asset_plant_plant_type")
-
- # asset_plant
- op.drop_index(
- op.f("ix_asset_plant_version_transaction_id"), table_name="asset_plant_version"
- )
- op.drop_index(
- "ix_asset_plant_version_pk_validity", table_name="asset_plant_version"
- )
- op.drop_index(
- "ix_asset_plant_version_pk_transaction_id", table_name="asset_plant_version"
- )
- op.drop_index(
- op.f("ix_asset_plant_version_operation_type"), table_name="asset_plant_version"
- )
- op.drop_index(
- op.f("ix_asset_plant_version_end_transaction_id"),
- table_name="asset_plant_version",
- )
- op.drop_table("asset_plant_version")
- op.drop_table("asset_plant")
-
- # plant_type
- op.drop_index(
- op.f("ix_plant_type_version_transaction_id"), table_name="plant_type_version"
- )
- op.drop_index("ix_plant_type_version_pk_validity", table_name="plant_type_version")
- op.drop_index(
- "ix_plant_type_version_pk_transaction_id", table_name="plant_type_version"
- )
- op.drop_index(
- op.f("ix_plant_type_version_operation_type"), table_name="plant_type_version"
- )
- op.drop_index(
- op.f("ix_plant_type_version_end_transaction_id"),
- table_name="plant_type_version",
- )
- op.drop_table("plant_type_version")
- op.drop_table("plant_type")
diff --git a/src/wuttafarm/db/alembic/versions/82a03f4ef1a4_add_produces_eggs_via_eggmixin.py b/src/wuttafarm/db/alembic/versions/82a03f4ef1a4_add_produces_eggs_via_eggmixin.py
deleted file mode 100644
index 9bed92c..0000000
--- a/src/wuttafarm/db/alembic/versions/82a03f4ef1a4_add_produces_eggs_via_eggmixin.py
+++ /dev/null
@@ -1,52 +0,0 @@
-"""add produces_eggs via EggMixin
-
-Revision ID: 82a03f4ef1a4
-Revises: 11e0e46f48a6
-Create Date: 2026-02-18 18:45:36.015144
-
-"""
-
-from typing import Sequence, Union
-
-from alembic import op
-import sqlalchemy as sa
-import wuttjamaican.db.util
-
-
-# revision identifiers, used by Alembic.
-revision: str = "82a03f4ef1a4"
-down_revision: Union[str, None] = "11e0e46f48a6"
-branch_labels: Union[str, Sequence[str], None] = None
-depends_on: Union[str, Sequence[str], None] = None
-
-
-def upgrade() -> None:
-
- # asset_animal
- op.add_column(
- "asset_animal", sa.Column("produces_eggs", sa.Boolean(), nullable=True)
- )
- op.add_column(
- "asset_animal_version",
- sa.Column("produces_eggs", sa.Boolean(), autoincrement=False, nullable=True),
- )
-
- # asset_group
- op.add_column(
- "asset_group", sa.Column("produces_eggs", sa.Boolean(), nullable=True)
- )
- op.add_column(
- "asset_group_version",
- sa.Column("produces_eggs", sa.Boolean(), autoincrement=False, nullable=True),
- )
-
-
-def downgrade() -> None:
-
- # asset_group
- op.drop_column("asset_group_version", "produces_eggs")
- op.drop_column("asset_group", "produces_eggs")
-
- # asset_animal
- op.drop_column("asset_animal_version", "produces_eggs")
- op.drop_column("asset_animal", "produces_eggs")
diff --git a/src/wuttafarm/db/alembic/versions/dd6351e69233_add_generic_log_base.py b/src/wuttafarm/db/alembic/versions/dd6351e69233_add_generic_log_base.py
deleted file mode 100644
index 0b82da9..0000000
--- a/src/wuttafarm/db/alembic/versions/dd6351e69233_add_generic_log_base.py
+++ /dev/null
@@ -1,206 +0,0 @@
-"""add generic log base
-
-Revision ID: dd6351e69233
-Revises: b8cd4a8f981f
-Create Date: 2026-02-18 12:09:05.200134
-
-"""
-
-from typing import Sequence, Union
-
-from alembic import op
-import sqlalchemy as sa
-import wuttjamaican.db.util
-from sqlalchemy.dialects import postgresql
-
-# revision identifiers, used by Alembic.
-revision: str = "dd6351e69233"
-down_revision: Union[str, None] = "b8cd4a8f981f"
-branch_labels: Union[str, Sequence[str], None] = None
-depends_on: Union[str, Sequence[str], None] = None
-
-
-def upgrade() -> None:
-
- # log
- op.create_table(
- "log",
- sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
- sa.Column("log_type", sa.String(length=100), nullable=False),
- sa.Column("message", sa.String(length=255), nullable=False),
- sa.Column("timestamp", sa.DateTime(), nullable=False),
- sa.Column("status", sa.String(length=20), nullable=False),
- sa.Column("notes", sa.Text(), nullable=True),
- sa.Column("farmos_uuid", wuttjamaican.db.util.UUID(), nullable=True),
- sa.Column("drupal_id", sa.Integer(), nullable=True),
- sa.ForeignKeyConstraint(
- ["log_type"], ["log_type.drupal_id"], name=op.f("fk_log_log_type_log_type")
- ),
- sa.PrimaryKeyConstraint("uuid", name=op.f("pk_log")),
- sa.UniqueConstraint("drupal_id", name=op.f("uq_log_drupal_id")),
- sa.UniqueConstraint("farmos_uuid", name=op.f("uq_log_farmos_uuid")),
- )
- op.create_table(
- "log_version",
- sa.Column(
- "uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
- ),
- sa.Column(
- "log_type", sa.String(length=100), autoincrement=False, nullable=True
- ),
- sa.Column("message", sa.String(length=255), autoincrement=False, nullable=True),
- sa.Column("timestamp", sa.DateTime(), autoincrement=False, nullable=True),
- sa.Column("status", sa.String(length=20), autoincrement=False, nullable=True),
- sa.Column("notes", sa.Text(), autoincrement=False, nullable=True),
- sa.Column(
- "farmos_uuid",
- wuttjamaican.db.util.UUID(),
- autoincrement=False,
- nullable=True,
- ),
- sa.Column("drupal_id", sa.Integer(), 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_log_version")),
- )
- op.create_index(
- op.f("ix_log_version_end_transaction_id"),
- "log_version",
- ["end_transaction_id"],
- unique=False,
- )
- op.create_index(
- op.f("ix_log_version_operation_type"),
- "log_version",
- ["operation_type"],
- unique=False,
- )
- op.create_index(
- "ix_log_version_pk_transaction_id",
- "log_version",
- ["uuid", sa.literal_column("transaction_id DESC")],
- unique=False,
- )
- op.create_index(
- "ix_log_version_pk_validity",
- "log_version",
- ["uuid", "transaction_id", "end_transaction_id"],
- unique=False,
- )
- op.create_index(
- op.f("ix_log_version_transaction_id"),
- "log_version",
- ["transaction_id"],
- unique=False,
- )
-
- # log_activity
- op.drop_column("log_activity_version", "status")
- op.drop_column("log_activity_version", "farmos_uuid")
- op.drop_column("log_activity_version", "timestamp")
- op.drop_column("log_activity_version", "message")
- op.drop_column("log_activity_version", "drupal_id")
- op.drop_column("log_activity_version", "notes")
- op.drop_constraint(
- op.f("uq_log_activity_drupal_id"), "log_activity", type_="unique"
- )
- op.drop_constraint(
- op.f("uq_log_activity_farmos_uuid"), "log_activity", type_="unique"
- )
- op.create_foreign_key(
- op.f("fk_log_activity_uuid_log"), "log_activity", "log", ["uuid"], ["uuid"]
- )
- op.drop_column("log_activity", "status")
- op.drop_column("log_activity", "farmos_uuid")
- op.drop_column("log_activity", "timestamp")
- op.drop_column("log_activity", "message")
- op.drop_column("log_activity", "drupal_id")
- op.drop_column("log_activity", "notes")
-
-
-def downgrade() -> None:
-
- # log_activity
- op.add_column(
- "log_activity",
- sa.Column("notes", sa.TEXT(), autoincrement=False, nullable=True),
- )
- op.add_column(
- "log_activity",
- sa.Column("drupal_id", sa.INTEGER(), autoincrement=False, nullable=True),
- )
- op.add_column(
- "log_activity",
- sa.Column(
- "message", sa.VARCHAR(length=255), autoincrement=False, nullable=False
- ),
- )
- op.add_column(
- "log_activity",
- sa.Column(
- "timestamp", postgresql.TIMESTAMP(), autoincrement=False, nullable=False
- ),
- )
- op.add_column(
- "log_activity",
- sa.Column("farmos_uuid", sa.UUID(), autoincrement=False, nullable=True),
- )
- op.add_column(
- "log_activity",
- sa.Column("status", sa.VARCHAR(length=20), autoincrement=False, nullable=False),
- )
- op.drop_constraint(
- op.f("fk_log_activity_uuid_log"), "log_activity", type_="foreignkey"
- )
- op.create_unique_constraint(
- op.f("uq_log_activity_farmos_uuid"),
- "log_activity",
- ["farmos_uuid"],
- postgresql_nulls_not_distinct=False,
- )
- op.create_unique_constraint(
- op.f("uq_log_activity_drupal_id"),
- "log_activity",
- ["drupal_id"],
- postgresql_nulls_not_distinct=False,
- )
- op.add_column(
- "log_activity_version",
- sa.Column("notes", sa.TEXT(), autoincrement=False, nullable=True),
- )
- op.add_column(
- "log_activity_version",
- sa.Column("drupal_id", sa.INTEGER(), autoincrement=False, nullable=True),
- )
- op.add_column(
- "log_activity_version",
- sa.Column(
- "message", sa.VARCHAR(length=255), autoincrement=False, nullable=True
- ),
- )
- op.add_column(
- "log_activity_version",
- sa.Column(
- "timestamp", postgresql.TIMESTAMP(), autoincrement=False, nullable=True
- ),
- )
- op.add_column(
- "log_activity_version",
- sa.Column("farmos_uuid", sa.UUID(), autoincrement=False, nullable=True),
- )
- op.add_column(
- "log_activity_version",
- sa.Column("status", sa.VARCHAR(length=20), autoincrement=False, nullable=True),
- )
-
- # log
- op.drop_index(op.f("ix_log_version_transaction_id"), table_name="log_version")
- op.drop_index("ix_log_version_pk_validity", table_name="log_version")
- op.drop_index("ix_log_version_pk_transaction_id", table_name="log_version")
- op.drop_index(op.f("ix_log_version_operation_type"), table_name="log_version")
- op.drop_index(op.f("ix_log_version_end_transaction_id"), table_name="log_version")
- op.drop_table("log_version")
- op.drop_table("log")
diff --git a/src/wuttafarm/db/model/__init__.py b/src/wuttafarm/db/model/__init__.py
index f9eb790..367bc1c 100644
--- a/src/wuttafarm/db/model/__init__.py
+++ b/src/wuttafarm/db/model/__init__.py
@@ -30,14 +30,9 @@ from wuttjamaican.db.model import *
from .users import WuttaFarmUser
# wuttafarm proper models
-from .asset import AssetType, Asset, AssetParent
-from .asset_land import LandType, LandAsset
-from .asset_structure import StructureType, StructureAsset
-from .asset_animal import AnimalType, AnimalAsset
-from .asset_group import GroupAsset
-from .asset_plant import PlantType, PlantAsset, PlantAssetPlantType
-from .log import LogType, Log, LogAsset
-from .log_activity import ActivityLog
-from .log_harvest import HarvestLog
-from .log_medical import MedicalLog
-from .log_observation import ObservationLog
+from .assets import AssetType, Asset, AssetParent
+from .land import LandType, LandAsset
+from .structures import StructureType, StructureAsset
+from .animals import AnimalType, AnimalAsset
+from .groups import GroupAsset
+from .logs import LogType, ActivityLog
diff --git a/src/wuttafarm/db/model/asset_animal.py b/src/wuttafarm/db/model/animals.py
similarity index 96%
rename from src/wuttafarm/db/model/asset_animal.py
rename to src/wuttafarm/db/model/animals.py
index 768b0f9..8c0df35 100644
--- a/src/wuttafarm/db/model/asset_animal.py
+++ b/src/wuttafarm/db/model/animals.py
@@ -28,7 +28,7 @@ from sqlalchemy import orm
from wuttjamaican.db import model
-from wuttafarm.db.model.asset import AssetMixin, add_asset_proxies, EggMixin
+from wuttafarm.db.model.assets import AssetMixin, add_asset_proxies
class AnimalType(model.Base):
@@ -84,7 +84,7 @@ class AnimalType(model.Base):
return self.name or ""
-class AnimalAsset(AssetMixin, EggMixin, model.Base):
+class AnimalAsset(AssetMixin, model.Base):
"""
Represents an animal asset from farmOS
"""
diff --git a/src/wuttafarm/db/model/asset_plant.py b/src/wuttafarm/db/model/asset_plant.py
deleted file mode 100644
index 5f10e7c..0000000
--- a/src/wuttafarm/db/model/asset_plant.py
+++ /dev/null
@@ -1,132 +0,0 @@
-# -*- 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 Plant Assets
-"""
-
-import sqlalchemy as sa
-from sqlalchemy import orm
-
-from wuttjamaican.db import model
-
-from wuttafarm.db.model.asset import AssetMixin, add_asset_proxies
-
-
-class PlantType(model.Base):
- """
- Represents a "plant type" (taxonomy term) from farmOS
- """
-
- __tablename__ = "plant_type"
- __versioned__ = {}
- __wutta_hint__ = {
- "model_title": "Plant Type",
- "model_title_plural": "Plant Types",
- }
-
- uuid = model.uuid_column()
-
- name = sa.Column(
- sa.String(length=100),
- nullable=False,
- unique=True,
- doc="""
- Name of the plant type.
- """,
- )
-
- description = sa.Column(
- sa.String(length=255),
- nullable=True,
- doc="""
- Optional description for the plant type.
- """,
- )
-
- farmos_uuid = sa.Column(
- model.UUID(),
- nullable=True,
- unique=True,
- doc="""
- UUID for the plant type within farmOS.
- """,
- )
-
- drupal_id = sa.Column(
- sa.Integer(),
- nullable=True,
- unique=True,
- doc="""
- Drupal internal ID for the plant type.
- """,
- )
-
- def __str__(self):
- return self.name or ""
-
-
-class PlantAsset(AssetMixin, model.Base):
- """
- Represents a plant asset from farmOS
- """
-
- __tablename__ = "asset_plant"
- __versioned__ = {}
- __wutta_hint__ = {
- "model_title": "Plant Asset",
- "model_title_plural": "Plant Assets",
- "farmos_asset_type": "plant",
- }
-
- _plant_types = orm.relationship(
- "PlantAssetPlantType",
- back_populates="plant_asset",
- )
-
-
-add_asset_proxies(PlantAsset)
-
-
-class PlantAssetPlantType(model.Base):
- """
- Associates one or more plant types with a plant asset.
- """
-
- __tablename__ = "asset_plant_plant_type"
- __versioned__ = {}
-
- uuid = model.uuid_column()
-
- plant_asset_uuid = model.uuid_fk_column("asset_plant.uuid", nullable=False)
- plant_asset = orm.relationship(
- PlantAsset,
- foreign_keys=plant_asset_uuid,
- back_populates="_plant_types",
- )
-
- plant_type_uuid = model.uuid_fk_column("plant_type.uuid", nullable=False)
- plant_type = orm.relationship(
- PlantType,
- doc="""
- Reference to the plant type.
- """,
- )
diff --git a/src/wuttafarm/db/model/asset.py b/src/wuttafarm/db/model/assets.py
similarity index 95%
rename from src/wuttafarm/db/model/asset.py
rename to src/wuttafarm/db/model/assets.py
index 90372e2..531fd62 100644
--- a/src/wuttafarm/db/model/asset.py
+++ b/src/wuttafarm/db/model/assets.py
@@ -215,18 +215,6 @@ def add_asset_proxies(subclass):
Asset.make_proxy(subclass, "asset", "archived")
-class EggMixin:
-
- produces_eggs = sa.Column(
- sa.Boolean(),
- nullable=True,
- doc="""
- Whether the group asset produces eggs (i.e. it should be
- available in the egg harvest form).
- """,
- )
-
-
class AssetParent(model.Base):
"""
Represents an "asset's parent relationship" from farmOS.
diff --git a/src/wuttafarm/db/model/asset_group.py b/src/wuttafarm/db/model/groups.py
similarity index 91%
rename from src/wuttafarm/db/model/asset_group.py
rename to src/wuttafarm/db/model/groups.py
index ad4d184..84453a7 100644
--- a/src/wuttafarm/db/model/asset_group.py
+++ b/src/wuttafarm/db/model/groups.py
@@ -25,10 +25,10 @@ Model definition for Groups
from wuttjamaican.db import model
-from wuttafarm.db.model.asset import AssetMixin, add_asset_proxies, EggMixin
+from wuttafarm.db.model.assets import AssetMixin, add_asset_proxies
-class GroupAsset(AssetMixin, EggMixin, model.Base):
+class GroupAsset(AssetMixin, model.Base):
"""
Represents a group asset from farmOS
"""
diff --git a/src/wuttafarm/db/model/asset_land.py b/src/wuttafarm/db/model/land.py
similarity index 97%
rename from src/wuttafarm/db/model/asset_land.py
rename to src/wuttafarm/db/model/land.py
index bbd7bf0..1221c63 100644
--- a/src/wuttafarm/db/model/asset_land.py
+++ b/src/wuttafarm/db/model/land.py
@@ -28,7 +28,7 @@ from sqlalchemy import orm
from wuttjamaican.db import model
-from wuttafarm.db.model.asset import AssetMixin, add_asset_proxies
+from wuttafarm.db.model.assets import AssetMixin, add_asset_proxies
class LandType(model.Base):
diff --git a/src/wuttafarm/db/model/log_activity.py b/src/wuttafarm/db/model/log_activity.py
deleted file mode 100644
index 2f5f6e5..0000000
--- a/src/wuttafarm/db/model/log_activity.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# -*- 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 Activity Logs
-"""
-
-from wuttjamaican.db import model
-
-from wuttafarm.db.model.log import LogMixin, add_log_proxies
-
-
-class ActivityLog(LogMixin, model.Base):
- """
- Represents an Activity Log from farmOS
- """
-
- __tablename__ = "log_activity"
- __versioned__ = {}
- __wutta_hint__ = {
- "model_title": "Activity Log",
- "model_title_plural": "Activity Logs",
- "farmos_log_type": "activity",
- }
-
-
-add_log_proxies(ActivityLog)
diff --git a/src/wuttafarm/db/model/log_harvest.py b/src/wuttafarm/db/model/log_harvest.py
deleted file mode 100644
index 35c3105..0000000
--- a/src/wuttafarm/db/model/log_harvest.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# -*- 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 Harvest Logs
-"""
-
-from wuttjamaican.db import model
-
-from wuttafarm.db.model.log import LogMixin, add_log_proxies
-
-
-class HarvestLog(LogMixin, model.Base):
- """
- Represents a Harvest Log from farmOS
- """
-
- __tablename__ = "log_harvest"
- __versioned__ = {}
- __wutta_hint__ = {
- "model_title": "Harvest Log",
- "model_title_plural": "Harvest Logs",
- "farmos_log_type": "harvest",
- }
-
-
-add_log_proxies(HarvestLog)
diff --git a/src/wuttafarm/db/model/log_medical.py b/src/wuttafarm/db/model/log_medical.py
deleted file mode 100644
index 439ee3b..0000000
--- a/src/wuttafarm/db/model/log_medical.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# -*- 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 Medical Logs
-"""
-
-from wuttjamaican.db import model
-
-from wuttafarm.db.model.log import LogMixin, add_log_proxies
-
-
-class MedicalLog(LogMixin, model.Base):
- """
- Represents a Medical Log from farmOS
- """
-
- __tablename__ = "log_medical"
- __versioned__ = {}
- __wutta_hint__ = {
- "model_title": "Medical Log",
- "model_title_plural": "Medical Logs",
- "farmos_log_type": "medical",
- }
-
-
-add_log_proxies(MedicalLog)
diff --git a/src/wuttafarm/db/model/log_observation.py b/src/wuttafarm/db/model/log_observation.py
deleted file mode 100644
index ab89c3f..0000000
--- a/src/wuttafarm/db/model/log_observation.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# -*- 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 Observation Logs
-"""
-
-from wuttjamaican.db import model
-
-from wuttafarm.db.model.log import LogMixin, add_log_proxies
-
-
-class ObservationLog(LogMixin, model.Base):
- """
- Represents a Observation Log from farmOS
- """
-
- __tablename__ = "log_observation"
- __versioned__ = {}
- __wutta_hint__ = {
- "model_title": "Observation Log",
- "model_title_plural": "Observation Logs",
- "farmos_log_type": "observation",
- }
-
-
-add_log_proxies(ObservationLog)
diff --git a/src/wuttafarm/db/model/log.py b/src/wuttafarm/db/model/logs.py
similarity index 67%
rename from src/wuttafarm/db/model/log.py
rename to src/wuttafarm/db/model/logs.py
index a86c447..76f7715 100644
--- a/src/wuttafarm/db/model/log.py
+++ b/src/wuttafarm/db/model/logs.py
@@ -20,12 +20,11 @@
#
################################################################################
"""
-Model definition for Logs
+Model definition for Log Types
"""
import sqlalchemy as sa
from sqlalchemy import orm
-from sqlalchemy.ext.declarative import declared_attr
from wuttjamaican.db import model
@@ -83,26 +82,20 @@ class LogType(model.Base):
return self.name or ""
-class Log(model.Base):
+class ActivityLog(model.Base):
"""
- Represents a base log record from farmOS
+ Represents an activity log from farmOS
"""
- __tablename__ = "log"
+ __tablename__ = "log_activity"
__versioned__ = {}
__wutta_hint__ = {
- "model_title": "Log",
- "model_title_plural": "All Logs",
+ "model_title": "Activity Log",
+ "model_title_plural": "Activity Logs",
}
uuid = model.uuid_column()
- log_type = sa.Column(
- sa.String(length=100),
- sa.ForeignKey("log_type.drupal_id"),
- nullable=False,
- )
-
message = sa.Column(
sa.String(length=255),
nullable=False,
@@ -153,53 +146,5 @@ class Log(model.Base):
""",
)
- _assets = orm.relationship("LogAsset", back_populates="log")
-
def __str__(self):
return self.message or ""
-
-
-class LogMixin:
-
- uuid = model.uuid_fk_column("log.uuid", nullable=False, primary_key=True)
-
- @declared_attr
- def log(cls):
- return orm.relationship(Log)
-
- def __str__(self):
- return self.message or ""
-
-
-def add_log_proxies(subclass):
- Log.make_proxy(subclass, "log", "farmos_uuid")
- Log.make_proxy(subclass, "log", "drupal_id")
- Log.make_proxy(subclass, "log", "log_type")
- Log.make_proxy(subclass, "log", "message")
- Log.make_proxy(subclass, "log", "timestamp")
- Log.make_proxy(subclass, "log", "status")
- Log.make_proxy(subclass, "log", "notes")
-
-
-class LogAsset(model.Base):
- """
- Represents a "log's asset relationship" from farmOS.
- """
-
- __tablename__ = "log_asset"
- __versioned__ = {}
-
- uuid = model.uuid_column()
-
- log_uuid = model.uuid_fk_column("log.uuid", nullable=False)
- log = orm.relationship(
- Log,
- foreign_keys=log_uuid,
- back_populates="_assets",
- )
-
- asset_uuid = model.uuid_fk_column("asset.uuid", nullable=False)
- asset = orm.relationship(
- "Asset",
- foreign_keys=asset_uuid,
- )
diff --git a/src/wuttafarm/db/model/asset_structure.py b/src/wuttafarm/db/model/structures.py
similarity index 97%
rename from src/wuttafarm/db/model/asset_structure.py
rename to src/wuttafarm/db/model/structures.py
index 7f4fc23..8c5371c 100644
--- a/src/wuttafarm/db/model/asset_structure.py
+++ b/src/wuttafarm/db/model/structures.py
@@ -28,7 +28,7 @@ from sqlalchemy import orm
from wuttjamaican.db import model
-from wuttafarm.db.model.asset import AssetMixin, add_asset_proxies
+from wuttafarm.db.model.assets import AssetMixin, add_asset_proxies
class StructureType(model.Base):
diff --git a/src/wuttafarm/enum.py b/src/wuttafarm/enum.py
index 03181b9..41bf597 100644
--- a/src/wuttafarm/enum.py
+++ b/src/wuttafarm/enum.py
@@ -34,12 +34,3 @@ ANIMAL_SEX = OrderedDict(
("F", "Female"),
]
)
-
-
-LOG_STATUS = OrderedDict(
- [
- ("pending", "Pending"),
- ("done", "Done"),
- ("abandoned", "Abandoned"),
- ]
-)
diff --git a/src/wuttafarm/farmos/importing/model.py b/src/wuttafarm/farmos/importing/model.py
index d20c068..6c3f5a0 100644
--- a/src/wuttafarm/farmos/importing/model.py
+++ b/src/wuttafarm/farmos/importing/model.py
@@ -125,7 +125,6 @@ class ToFarmOSAsset(ToFarmOS):
"asset_name": asset["attributes"]["name"],
"is_location": asset["attributes"]["is_location"],
"is_fixed": asset["attributes"]["is_fixed"],
- "produces_eggs": asset["attributes"].get("produces_eggs"),
"notes": notes,
"archived": asset["attributes"]["archived"],
}
@@ -139,8 +138,6 @@ class ToFarmOSAsset(ToFarmOS):
attrs["is_location"] = source_data["is_location"]
if "is_fixed" in self.fields:
attrs["is_fixed"] = source_data["is_fixed"]
- if "produces_eggs" in self.fields:
- attrs["produces_eggs"] = source_data["produces_eggs"]
if "notes" in self.fields:
attrs["notes"] = {"value": source_data["notes"]}
if "archived" in self.fields:
@@ -162,7 +159,6 @@ class AnimalAssetImporter(ToFarmOSAsset):
"animal_type_uuid",
"sex",
"is_sterile",
- "produces_eggs",
"birthdate",
"notes",
"archived",
@@ -290,7 +286,6 @@ class GroupAssetImporter(ToFarmOSAsset):
supported_fields = [
"uuid",
"asset_name",
- "produces_eggs",
"notes",
"archived",
]
@@ -368,113 +363,3 @@ class StructureAssetImporter(ToFarmOSAsset):
payload["attributes"].update(attrs)
return payload
-
-
-##############################
-# log importers
-##############################
-
-
-class ToFarmOSLog(ToFarmOS):
- """
- Base class for log data importer targeting the farmOS API.
- """
-
- farmos_log_type = None
-
- supported_fields = [
- "uuid",
- "name",
- "notes",
- ]
-
- def get_target_objects(self, **kwargs):
- result = self.farmos_client.log.get(self.farmos_log_type)
- return result["data"]
-
- def get_target_object(self, key):
-
- # fetch from cache, if applicable
- if self.caches_target:
- return super().get_target_object(key)
-
- # okay now must fetch via API
- if self.get_keys() != ["uuid"]:
- raise ValueError("must use uuid key for this to work")
- uuid = key[0]
-
- try:
- log = self.farmos_client.log.get_id(self.farmos_log_type, str(uuid))
- except requests.HTTPError as exc:
- if exc.response.status_code == 404:
- return None
- raise
- return log["data"]
-
- def create_target_object(self, key, source_data):
- if source_data.get("__ignoreme__"):
- return None
- if self.dry_run:
- return source_data
-
- payload = self.get_log_payload(source_data)
- result = self.farmos_client.log.send(self.farmos_log_type, payload)
- normal = self.normalize_target_object(result["data"])
- normal["_new_object"] = result["data"]
- return normal
-
- def update_target_object(self, asset, source_data, target_data=None):
- if self.dry_run:
- return asset
-
- payload = self.get_log_payload(source_data)
- payload["id"] = str(source_data["uuid"])
- result = self.farmos_client.log.send(self.farmos_log_type, payload)
- return self.normalize_target_object(result["data"])
-
- def normalize_target_object(self, log):
-
- if notes := log["attributes"]["notes"]:
- notes = notes["value"]
-
- return {
- "uuid": UUID(log["id"]),
- "name": log["attributes"]["name"],
- "notes": notes,
- }
-
- def get_log_payload(self, source_data):
-
- attrs = {}
- if "name" in self.fields:
- attrs["name"] = source_data["name"]
- if "notes" in self.fields:
- attrs["notes"] = {"value": source_data["notes"]}
-
- payload = {"attributes": attrs}
-
- return payload
-
-
-class ActivityLogImporter(ToFarmOSLog):
-
- model_title = "ActivityLog"
- farmos_log_type = "activity"
-
-
-class HarvestLogImporter(ToFarmOSLog):
-
- model_title = "HarvestLog"
- farmos_log_type = "harvest"
-
-
-class MedicalLogImporter(ToFarmOSLog):
-
- model_title = "MedicalLog"
- farmos_log_type = "medical"
-
-
-class ObservationLogImporter(ToFarmOSLog):
-
- model_title = "ObservationLog"
- farmos_log_type = "observation"
diff --git a/src/wuttafarm/farmos/importing/wuttafarm.py b/src/wuttafarm/farmos/importing/wuttafarm.py
index ffd78b7..8ef8a77 100644
--- a/src/wuttafarm/farmos/importing/wuttafarm.py
+++ b/src/wuttafarm/farmos/importing/wuttafarm.py
@@ -98,10 +98,6 @@ class FromWuttaFarmToFarmOS(FromWuttaFarmHandler, ToFarmOSHandler):
importers["AnimalType"] = AnimalTypeImporter
importers["AnimalAsset"] = AnimalAssetImporter
importers["GroupAsset"] = GroupAssetImporter
- importers["ActivityLog"] = ActivityLogImporter
- importers["HarvestLog"] = HarvestLogImporter
- importers["MedicalLog"] = MedicalLogImporter
- importers["ObservationLog"] = ObservationLogImporter
return importers
@@ -140,7 +136,6 @@ class AnimalAssetImporter(FromWuttaFarm, farmos_importing.model.AnimalAssetImpor
"animal_type_uuid",
"sex",
"is_sterile",
- "produces_eggs",
"birthdate",
"notes",
"archived",
@@ -153,7 +148,6 @@ class AnimalAssetImporter(FromWuttaFarm, farmos_importing.model.AnimalAssetImpor
"animal_type_uuid": animal.animal_type.farmos_uuid,
"sex": animal.sex,
"is_sterile": animal.is_sterile,
- "produces_eggs": animal.produces_eggs,
"birthdate": animal.birthdate,
"notes": animal.notes,
"archived": animal.archived,
@@ -193,7 +187,6 @@ class GroupAssetImporter(FromWuttaFarm, farmos_importing.model.GroupAssetImporte
supported_fields = [
"uuid",
"asset_name",
- "produces_eggs",
"notes",
"archived",
]
@@ -202,7 +195,6 @@ class GroupAssetImporter(FromWuttaFarm, farmos_importing.model.GroupAssetImporte
return {
"uuid": group.farmos_uuid or self.app.make_true_uuid(),
"asset_name": group.asset_name,
- "produces_eggs": group.produces_eggs,
"notes": group.notes,
"archived": group.archived,
"_src_object": group,
@@ -269,62 +261,3 @@ class StructureAssetImporter(
"archived": structure.archived,
"_src_object": structure,
}
-
-
-##############################
-# log importers
-##############################
-
-
-class FromWuttaFarmLog(FromWuttaFarm):
- """
- Base class for WuttaFarm -> farmOS log importers
- """
-
- supported_fields = [
- "uuid",
- "name",
- "notes",
- ]
-
- def normalize_source_object(self, log):
- return {
- "uuid": log.farmos_uuid or self.app.make_true_uuid(),
- "name": log.message,
- "notes": log.notes,
- "_src_object": log,
- }
-
-
-class ActivityLogImporter(FromWuttaFarmLog, farmos_importing.model.ActivityLogImporter):
- """
- WuttaFarm → farmOS API exporter for Activity Logs
- """
-
- source_model_class = model.ActivityLog
-
-
-class HarvestLogImporter(FromWuttaFarmLog, farmos_importing.model.HarvestLogImporter):
- """
- WuttaFarm → farmOS API exporter for Harvest Logs
- """
-
- source_model_class = model.HarvestLog
-
-
-class MedicalLogImporter(FromWuttaFarmLog, farmos_importing.model.MedicalLogImporter):
- """
- WuttaFarm → farmOS API exporter for Medical Logs
- """
-
- source_model_class = model.MedicalLog
-
-
-class ObservationLogImporter(
- FromWuttaFarmLog, farmos_importing.model.ObservationLogImporter
-):
- """
- WuttaFarm → farmOS API exporter for Observation Logs
- """
-
- source_model_class = model.ObservationLog
diff --git a/src/wuttafarm/importing/farmos.py b/src/wuttafarm/importing/farmos.py
index d1cac19..b07d06d 100644
--- a/src/wuttafarm/importing/farmos.py
+++ b/src/wuttafarm/importing/farmos.py
@@ -104,13 +104,8 @@ class FromFarmOSToWuttaFarm(FromFarmOSHandler, ToWuttaFarmHandler):
importers["AnimalType"] = AnimalTypeImporter
importers["AnimalAsset"] = AnimalAssetImporter
importers["GroupAsset"] = GroupAssetImporter
- importers["PlantType"] = PlantTypeImporter
- importers["PlantAsset"] = PlantAssetImporter
importers["LogType"] = LogTypeImporter
importers["ActivityLog"] = ActivityLogImporter
- importers["HarvestLog"] = HarvestLogImporter
- importers["MedicalLog"] = MedicalLogImporter
- importers["ObservationLog"] = ObservationLogImporter
return importers
@@ -144,14 +139,48 @@ class FromFarmOS(Importer):
return self.app.make_utc(dt)
+class ActivityLogImporter(FromFarmOS, ToWutta):
+ """
+ farmOS API → WuttaFarm importer for Activity Logs
+ """
+
+ model_class = model.ActivityLog
+
+ supported_fields = [
+ "farmos_uuid",
+ "drupal_id",
+ "message",
+ "timestamp",
+ "notes",
+ "status",
+ ]
+
+ def get_source_objects(self):
+ """ """
+ logs = self.farmos_client.log.get("activity")
+ return logs["data"]
+
+ def normalize_source_object(self, log):
+ """ """
+
+ if notes := log["attributes"]["notes"]:
+ notes = notes["value"]
+
+ return {
+ "farmos_uuid": UUID(log["id"]),
+ "drupal_id": log["attributes"]["drupal_internal__id"],
+ "message": log["attributes"]["name"],
+ "timestamp": self.normalize_datetime(log["attributes"]["timestamp"]),
+ "notes": notes,
+ "status": log["attributes"]["status"],
+ }
+
+
class AssetImporterBase(FromFarmOS, ToWutta):
"""
Base class for farmOS API → WuttaFarm asset importers
"""
- def get_farmos_asset_type(self):
- return self.model_class.__wutta_hint__["farmos_asset_type"]
-
def get_simple_fields(self):
""" """
fields = list(super().get_simple_fields())
@@ -182,12 +211,6 @@ class AssetImporterBase(FromFarmOS, ToWutta):
)
return fields
- def get_source_objects(self):
- """ """
- asset_type = self.get_farmos_asset_type()
- result = self.farmos_client.asset.get(asset_type)
- return result["data"]
-
def normalize_source_data(self, **kwargs):
""" """
data = super().normalize_source_data(**kwargs)
@@ -312,7 +335,6 @@ class AnimalAssetImporter(AssetImporterBase):
"animal_type_uuid",
"sex",
"is_sterile",
- "produces_eggs",
"birthdate",
"notes",
"archived",
@@ -372,7 +394,6 @@ class AnimalAssetImporter(AssetImporterBase):
"animal_type_uuid": animal_type_uuid,
"sex": animal["attributes"]["sex"],
"is_sterile": sterile,
- "produces_eggs": animal["attributes"]["produces_eggs"],
"birthdate": birthdate,
}
)
@@ -451,7 +472,6 @@ class GroupAssetImporter(AssetImporterBase):
"asset_name",
"is_location",
"is_fixed",
- "produces_eggs",
"notes",
"archived",
"image_url",
@@ -470,7 +490,6 @@ class GroupAssetImporter(AssetImporterBase):
data.update(
{
"asset_type": "group",
- "produces_eggs": group["attributes"]["produces_eggs"],
}
)
return data
@@ -557,12 +576,12 @@ class LandTypeImporter(FromFarmOS, ToWutta):
}
-class PlantTypeImporter(FromFarmOS, ToWutta):
+class LogTypeImporter(FromFarmOS, ToWutta):
"""
- farmOS API → WuttaFarm importer for Plant Types
+ farmOS API → WuttaFarm importer for Log Types
"""
- model_class = model.PlantType
+ model_class = model.LogType
supported_fields = [
"farmos_uuid",
@@ -573,112 +592,19 @@ class PlantTypeImporter(FromFarmOS, ToWutta):
def get_source_objects(self):
""" """
- result = self.farmos_client.resource.get("taxonomy_term", "plant_type")
- return result["data"]
+ log_types = self.farmos_client.resource.get("log_type")
+ return log_types["data"]
- def normalize_source_object(self, plant_type):
+ def normalize_source_object(self, log_type):
""" """
return {
- "farmos_uuid": UUID(plant_type["id"]),
- "drupal_id": plant_type["attributes"]["drupal_internal__tid"],
- "name": plant_type["attributes"]["name"],
- "description": plant_type["attributes"]["description"],
+ "farmos_uuid": UUID(log_type["id"]),
+ "drupal_id": log_type["attributes"]["drupal_internal__id"],
+ "name": log_type["attributes"]["label"],
+ "description": log_type["attributes"]["description"],
}
-class PlantAssetImporter(AssetImporterBase):
- """
- farmOS API → WuttaFarm importer for Plant Assets
- """
-
- model_class = model.PlantAsset
-
- supported_fields = [
- "farmos_uuid",
- "drupal_id",
- "asset_type",
- "asset_name",
- "plant_types",
- "notes",
- "archived",
- "image_url",
- "thumbnail_url",
- ]
-
- def setup(self):
- super().setup()
- model = self.app.model
-
- self.plant_types_by_farmos_uuid = {}
- for plant_type in self.target_session.query(model.PlantType):
- if plant_type.farmos_uuid:
- self.plant_types_by_farmos_uuid[plant_type.farmos_uuid] = plant_type
-
- def normalize_source_object(self, plant):
- """ """
- plant_types = None
- if relationships := plant.get("relationships"):
-
- if plant_type := relationships.get("plant_type"):
- plant_types = []
- for plant_type in plant_type["data"]:
- if wf_plant_type := self.plant_types_by_farmos_uuid.get(
- UUID(plant_type["id"])
- ):
- plant_types.append(wf_plant_type.uuid)
- else:
- log.warning("plant type not found: %s", plant_type["id"])
-
- data = self.normalize_asset(plant)
- data.update(
- {
- "asset_type": "plant",
- "plant_types": plant_types,
- }
- )
- return data
-
- def normalize_target_object(self, plant):
- data = super().normalize_target_object(plant)
-
- if "plant_types" in self.fields:
- data["plant_types"] = [t.plant_type_uuid for t in plant._plant_types]
-
- return data
-
- def update_target_object(self, plant, source_data, target_data=None):
- model = self.app.model
- plant = super().update_target_object(plant, source_data, target_data)
-
- if "plant_types" in self.fields:
- if (
- not target_data
- or target_data["plant_types"] != source_data["plant_types"]
- ):
-
- for uuid in source_data["plant_types"]:
- if not target_data or uuid not in target_data["plant_types"]:
- self.target_session.flush()
- plant._plant_types.append(
- model.PlantAssetPlantType(plant_type_uuid=uuid)
- )
-
- if target_data:
- for uuid in target_data["plant_types"]:
- if uuid not in source_data["plant_types"]:
- plant_type = (
- self.target_session.query(model.PlantAssetPlantType)
- .filter(model.PlantAssetPlantType.plant_asset == plant)
- .filter(
- model.PlantAssetPlantType.plant_type_uuid == uuid
- )
- .one()
- )
- self.target_session.delete(plant_type)
-
- return plant
-
-
class StructureAssetImporter(AssetImporterBase):
"""
farmOS API → WuttaFarm importer for Structure Assets
@@ -814,229 +740,3 @@ class UserImporter(FromFarmOS, ToWutta):
if not user.farmos_uuid:
return False
return True
-
-
-##############################
-# log importers
-##############################
-
-
-class LogTypeImporter(FromFarmOS, ToWutta):
- """
- farmOS API → WuttaFarm importer for Log Types
- """
-
- model_class = model.LogType
-
- supported_fields = [
- "farmos_uuid",
- "drupal_id",
- "name",
- "description",
- ]
-
- def get_source_objects(self):
- """ """
- log_types = self.farmos_client.resource.get("log_type")
- return log_types["data"]
-
- def normalize_source_object(self, log_type):
- """ """
- return {
- "farmos_uuid": UUID(log_type["id"]),
- "drupal_id": log_type["attributes"]["drupal_internal__id"],
- "name": log_type["attributes"]["label"],
- "description": log_type["attributes"]["description"],
- }
-
-
-class LogImporterBase(FromFarmOS, ToWutta):
- """
- Base class for farmOS API → WuttaFarm log importers
- """
-
- def get_farmos_log_type(self):
- return self.model_class.__wutta_hint__["farmos_log_type"]
-
- def get_simple_fields(self):
- """ """
- fields = list(super().get_simple_fields())
- # nb. must explicitly declare proxy fields
- fields.extend(
- [
- "farmos_uuid",
- "drupal_id",
- "log_type",
- "message",
- "timestamp",
- "notes",
- "status",
- ]
- )
- return fields
-
- def get_supported_fields(self):
- """ """
- fields = list(super().get_supported_fields())
- fields.extend(
- [
- "assets",
- ]
- )
- return fields
-
- def get_source_objects(self):
- """ """
- log_type = self.get_farmos_log_type()
- result = self.farmos_client.log.get(log_type)
- return result["data"]
-
- def get_asset_type(self, asset):
- return asset["type"].split("--")[1]
-
- def normalize_source_object(self, log):
- """ """
- if notes := log["attributes"]["notes"]:
- notes = notes["value"]
-
- assets = None
- if "assets" in self.fields:
- assets = []
- for asset in log["relationships"]["asset"]["data"]:
- assets.append((self.get_asset_type(asset), UUID(asset["id"])))
-
- return {
- "farmos_uuid": UUID(log["id"]),
- "drupal_id": log["attributes"]["drupal_internal__id"],
- "log_type": self.get_farmos_log_type(),
- "message": log["attributes"]["name"],
- "timestamp": self.normalize_datetime(log["attributes"]["timestamp"]),
- "notes": notes,
- "status": log["attributes"]["status"],
- "assets": assets,
- }
-
- def normalize_target_object(self, log):
- data = super().normalize_target_object(log)
-
- if "assets" in self.fields:
- data["assets"] = [
- (a.asset.asset_type, a.asset.farmos_uuid) for a in log.log._assets
- ]
-
- return data
-
- def update_target_object(self, log, source_data, target_data=None):
- model = self.app.model
- log = super().update_target_object(log, source_data, target_data)
-
- if "assets" in self.fields:
- if not target_data or target_data["assets"] != source_data["assets"]:
-
- for key in source_data["assets"]:
- asset_type, farmos_uuid = key
- if not target_data or key not in target_data["assets"]:
- self.target_session.flush()
- asset = (
- self.target_session.query(model.Asset)
- .filter(model.Asset.asset_type == asset_type)
- .filter(model.Asset.farmos_uuid == farmos_uuid)
- .one()
- )
- log.log._assets.append(model.LogAsset(asset=asset))
-
- if target_data:
- for key in target_data["assets"]:
- asset_type, farmos_uuid = key
- if key not in source_data["assets"]:
- asset = (
- self.target_session.query(model.Asset)
- .filter(model.Asset.asset_type == asset_type)
- .filter(model.Asset.farmos_uuid == farmos_uuid)
- .one()
- )
- asset = (
- self.target_session.query(model.LogAsset)
- .filter(model.LogAsset.log == log)
- .filter(model.LogAsset.asset == asset)
- .one()
- )
- self.target_session.delete(asset)
-
- return log
-
-
-class ActivityLogImporter(LogImporterBase):
- """
- farmOS API → WuttaFarm importer for Activity Logs
- """
-
- model_class = model.ActivityLog
-
- supported_fields = [
- "farmos_uuid",
- "drupal_id",
- "log_type",
- "message",
- "timestamp",
- "notes",
- "status",
- "assets",
- ]
-
-
-class HarvestLogImporter(LogImporterBase):
- """
- farmOS API → WuttaFarm importer for Harvest Logs
- """
-
- model_class = model.HarvestLog
-
- supported_fields = [
- "farmos_uuid",
- "drupal_id",
- "log_type",
- "message",
- "timestamp",
- "notes",
- "status",
- "assets",
- ]
-
-
-class MedicalLogImporter(LogImporterBase):
- """
- farmOS API → WuttaFarm importer for Medical Logs
- """
-
- model_class = model.MedicalLog
-
- supported_fields = [
- "farmos_uuid",
- "drupal_id",
- "log_type",
- "message",
- "timestamp",
- "notes",
- "status",
- "assets",
- ]
-
-
-class ObservationLogImporter(LogImporterBase):
- """
- farmOS API → WuttaFarm importer for Observation Logs
- """
-
- model_class = model.ObservationLog
-
- supported_fields = [
- "farmos_uuid",
- "drupal_id",
- "log_type",
- "message",
- "timestamp",
- "notes",
- "status",
- "assets",
- ]
diff --git a/src/wuttafarm/web/forms/schema.py b/src/wuttafarm/web/forms/schema.py
index 123f662..95b3e9d 100644
--- a/src/wuttafarm/web/forms/schema.py
+++ b/src/wuttafarm/web/forms/schema.py
@@ -74,25 +74,6 @@ class AnimalTypeType(colander.SchemaType):
return AnimalTypeWidget(self.request, **kwargs)
-class FarmOSPlantTypes(colander.SchemaType):
-
- def __init__(self, request, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.request = request
-
- def serialize(self, node, appstruct):
- if appstruct is colander.null:
- return colander.null
-
- return json.dumps(appstruct)
-
- def widget_maker(self, **kwargs): # pylint: disable=empty-docstring
- """ """
- from wuttafarm.web.forms.widgets import FarmOSPlantTypesWidget
-
- return FarmOSPlantTypesWidget(self.request, **kwargs)
-
-
class LandTypeRef(ObjectRef):
"""
Custom schema type for a
@@ -118,23 +99,6 @@ class LandTypeRef(ObjectRef):
return self.request.route_url("land_types.view", uuid=land_type.uuid)
-class PlantTypeRefs(WuttaSet):
- """
- Schema type for Plant Types field (on a Plant Asset).
- """
-
- def serialize(self, node, appstruct):
- if not appstruct:
- appstruct = []
- uuids = [u.hex for u in appstruct]
- return json.dumps(uuids)
-
- def widget_maker(self, **kwargs):
- from wuttafarm.web.forms.widgets import PlantTypeRefsWidget
-
- return PlantTypeRefsWidget(self.request, **kwargs)
-
-
class StructureType(colander.SchemaType):
def __init__(self, request, *args, **kwargs):
@@ -213,20 +177,3 @@ class AssetParentRefs(WuttaSet):
from wuttafarm.web.forms.widgets import AssetParentRefsWidget
return AssetParentRefsWidget(self.request, **kwargs)
-
-
-class LogAssetRefs(WuttaSet):
- """
- Schema type for Assets field (on a Log record)
- """
-
- def serialize(self, node, appstruct):
- if not appstruct:
- appstruct = []
- uuids = [u.hex for u in appstruct]
- return json.dumps(uuids)
-
- def widget_maker(self, **kwargs):
- from wuttafarm.web.forms.widgets import LogAssetRefsWidget
-
- return LogAssetRefsWidget(self.request, **kwargs)
diff --git a/src/wuttafarm/web/forms/widgets.py b/src/wuttafarm/web/forms/widgets.py
index d5bf5c2..f812ccf 100644
--- a/src/wuttafarm/web/forms/widgets.py
+++ b/src/wuttafarm/web/forms/widgets.py
@@ -81,67 +81,6 @@ class AnimalTypeWidget(Widget):
return super().serialize(field, cstruct, **kw)
-class FarmOSPlantTypesWidget(Widget):
- """
- Widget to display a farmOS "plant types" field.
- """
-
- def __init__(self, request, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.request = request
-
- 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 plant_type in json.loads(cstruct):
- link = tags.link_to(
- plant_type["name"],
- self.request.route_url(
- "farmos_plant_types.view", uuid=plant_type["uuid"]
- ),
- )
- links.append(HTML.tag("li", c=link))
- return HTML.tag("ul", c=links)
-
- return super().serialize(field, cstruct, **kw)
-
-
-class PlantTypeRefsWidget(WuttaCheckboxChoiceWidget):
- """
- Widget for Plant Types field (on a Plant Asset).
- """
-
- def serialize(self, field, cstruct, **kw):
- """ """
- model = self.app.model
- session = Session()
-
- readonly = kw.get("readonly", self.readonly)
- if readonly:
- plant_types = []
- for uuid in json.loads(cstruct):
- plant_type = session.get(model.PlantType, uuid)
- plant_types.append(
- HTML.tag(
- "li",
- c=tags.link_to(
- str(plant_type),
- self.request.route_url(
- "plant_types.view", uuid=plant_type.uuid
- ),
- ),
- )
- )
- return HTML.tag("ul", c=plant_types)
-
- return super().serialize(field, cstruct, **kw)
-
-
class StructureWidget(Widget):
"""
Widget to display a "structure" field.
@@ -227,34 +166,3 @@ class AssetParentRefsWidget(WuttaCheckboxChoiceWidget):
return HTML.tag("ul", c=parents)
return super().serialize(field, cstruct, **kw)
-
-
-class LogAssetRefsWidget(WuttaCheckboxChoiceWidget):
- """
- Widget for Assets field (on a Log record)
- """
-
- def serialize(self, field, cstruct, **kw):
- """ """
- model = self.app.model
- session = Session()
-
- readonly = kw.get("readonly", self.readonly)
- if readonly:
- assets = []
- for uuid in json.loads(cstruct):
- asset = session.get(model.Asset, uuid)
- assets.append(
- HTML.tag(
- "li",
- c=tags.link_to(
- str(asset),
- self.request.route_url(
- f"{asset.asset_type}_assets.view", uuid=asset.uuid
- ),
- ),
- )
- )
- return HTML.tag("ul", c=assets)
-
- return super().serialize(field, cstruct, **kw)
diff --git a/src/wuttafarm/web/menus.py b/src/wuttafarm/web/menus.py
index d52a6ca..bdd2fbf 100644
--- a/src/wuttafarm/web/menus.py
+++ b/src/wuttafarm/web/menus.py
@@ -64,11 +64,6 @@ class WuttaFarmMenuHandler(base.MenuHandler):
"route": "land_assets",
"perm": "land_assets.list",
},
- {
- "title": "Plant",
- "route": "plant_assets",
- "perm": "plant_assets.list",
- },
{
"title": "Structure",
"route": "structure_assets",
@@ -85,11 +80,6 @@ class WuttaFarmMenuHandler(base.MenuHandler):
"route": "land_types",
"perm": "land_types.list",
},
- {
- "title": "Plant Types",
- "route": "plant_types",
- "perm": "plant_types.list",
- },
{
"title": "Structure Types",
"route": "structure_types",
@@ -109,29 +99,9 @@ class WuttaFarmMenuHandler(base.MenuHandler):
"type": "menu",
"items": [
{
- "title": "All Logs",
- "route": "log",
- "perm": "log.list",
- },
- {
- "title": "Activity",
- "route": "logs_activity",
- "perm": "logs_activity.list",
- },
- {
- "title": "Harvest",
- "route": "logs_harvest",
- "perm": "logs_harvest.list",
- },
- {
- "title": "Medical",
- "route": "logs_medical",
- "perm": "logs_medical.list",
- },
- {
- "title": "Observation",
- "route": "logs_observation",
- "perm": "logs_observation.list",
+ "title": "Activity Logs",
+ "route": "activity_logs",
+ "perm": "activity_logs.list",
},
{"type": "sep"},
{
@@ -165,11 +135,6 @@ class WuttaFarmMenuHandler(base.MenuHandler):
"route": "farmos_groups",
"perm": "farmos_groups.list",
},
- {
- "title": "Plants",
- "route": "farmos_asset_plant",
- "perm": "farmos_asset_plant.list",
- },
{
"title": "Structures",
"route": "farmos_structures",
@@ -186,32 +151,12 @@ class WuttaFarmMenuHandler(base.MenuHandler):
"route": "farmos_logs_activity",
"perm": "farmos_logs_activity.list",
},
- {
- "title": "Harvest Logs",
- "route": "farmos_logs_harvest",
- "perm": "farmos_logs_harvest.list",
- },
- {
- "title": "Medical Logs",
- "route": "farmos_logs_medical",
- "perm": "farmos_logs_medical.list",
- },
- {
- "title": "Observation Logs",
- "route": "farmos_logs_observation",
- "perm": "farmos_logs_observation.list",
- },
{"type": "sep"},
{
"title": "Animal Types",
"route": "farmos_animal_types",
"perm": "farmos_animal_types.list",
},
- {
- "title": "Plant Types",
- "route": "farmos_plant_types",
- "perm": "farmos_plant_types.list",
- },
{
"title": "Structure Types",
"route": "farmos_structure_types",
diff --git a/src/wuttafarm/web/views/__init__.py b/src/wuttafarm/web/views/__init__.py
index bb710a2..e44c16e 100644
--- a/src/wuttafarm/web/views/__init__.py
+++ b/src/wuttafarm/web/views/__init__.py
@@ -47,12 +47,8 @@ def includeme(config):
config.include("wuttafarm.web.views.structures")
config.include("wuttafarm.web.views.animals")
config.include("wuttafarm.web.views.groups")
- config.include("wuttafarm.web.views.plants")
- config.include("wuttafarm.web.views.logs")
+ config.include("wuttafarm.web.views.log_types")
config.include("wuttafarm.web.views.logs_activity")
- config.include("wuttafarm.web.views.logs_harvest")
- config.include("wuttafarm.web.views.logs_medical")
- config.include("wuttafarm.web.views.logs_observation")
# views for farmOS
config.include("wuttafarm.web.views.farmos")
diff --git a/src/wuttafarm/web/views/animals.py b/src/wuttafarm/web/views/animals.py
index 72a05ee..7016e36 100644
--- a/src/wuttafarm/web/views/animals.py
+++ b/src/wuttafarm/web/views/animals.py
@@ -157,7 +157,6 @@ class AnimalAssetView(AssetMasterView):
"birthdate",
"is_sterile",
"sex",
- "produces_eggs",
"archived",
]
@@ -167,7 +166,6 @@ class AnimalAssetView(AssetMasterView):
"birthdate",
"sex",
"is_sterile",
- "produces_eggs",
"notes",
"asset_type",
"archived",
diff --git a/src/wuttafarm/web/views/asset_types.py b/src/wuttafarm/web/views/asset_types.py
index b9f560a..775fa3a 100644
--- a/src/wuttafarm/web/views/asset_types.py
+++ b/src/wuttafarm/web/views/asset_types.py
@@ -23,7 +23,7 @@
Master view for Asset Types
"""
-from wuttafarm.db.model import AssetType
+from wuttafarm.db.model.assets import AssetType
from wuttafarm.web.views import WuttaFarmMasterView
diff --git a/src/wuttafarm/web/views/assets.py b/src/wuttafarm/web/views/assets.py
index b918839..dffaae7 100644
--- a/src/wuttafarm/web/views/assets.py
+++ b/src/wuttafarm/web/views/assets.py
@@ -29,7 +29,7 @@ from wuttaweb.forms.schema import WuttaDictEnum
from wuttaweb.db import Session
from wuttafarm.web.views import WuttaFarmMasterView
-from wuttafarm.db.model import Asset, Log
+from wuttafarm.db.model import Asset
from wuttafarm.web.forms.schema import AssetParentRefs
from wuttafarm.web.forms.widgets import ImageWidget
@@ -140,28 +140,6 @@ class AssetMasterView(WuttaFarmMasterView):
"archived": {"active": True, "verb": "is_false"},
}
- has_rows = True
- row_model_class = Log
- rows_viewable = True
-
- row_labels = {
- "message": "Log Name",
- }
-
- row_grid_columns = [
- "status",
- "drupal_id",
- "timestamp",
- "message",
- "log_type",
- "assets",
- "location",
- "quantity",
- "is_group_assignment",
- ]
-
- rows_sort_defaults = ("timestamp", "desc")
-
def get_fallback_templates(self, template):
templates = super().get_fallback_templates(template)
@@ -287,8 +265,6 @@ class AssetMasterView(WuttaFarmMasterView):
route = "farmos_groups.view"
elif asset.asset_type == "land":
route = "farmos_land_assets.view"
- elif asset.asset_type == "plant":
- route = "farmos_asset_plant.view"
elif asset.asset_type == "structure":
route = "farmos_structures.view"
@@ -304,39 +280,6 @@ class AssetMasterView(WuttaFarmMasterView):
return buttons
- def get_row_grid_data(self, asset):
- model = self.app.model
- session = self.Session()
- return (
- session.query(model.Log)
- .outerjoin(model.LogAsset)
- .filter(model.LogAsset.asset_uuid == asset.uuid)
- )
-
- def configure_row_grid(self, grid):
- g = grid
- super().configure_row_grid(g)
- model = self.app.model
-
- # drupal_id
- g.set_label("drupal_id", "ID", column_only=True)
-
- # message
- g.set_link("message")
- g.set_sorter("message", model.Log.message)
- g.set_filter("message", model.Log.message)
-
- # timestamp
- g.set_sorter("timestamp", model.Log.timestamp)
- g.set_filter("timestamp", model.Log.timestamp)
-
- # log_type
- g.set_sorter("log_type", model.Log.log_type)
- g.set_filter("log_type", model.Log.log_type)
-
- def get_row_action_url_view(self, log, i):
- return self.request.route_url(f"logs_{log.log_type}.view", uuid=log.uuid)
-
def defaults(config, **kwargs):
base = globals()
diff --git a/src/wuttafarm/web/views/farmos/__init__.py b/src/wuttafarm/web/views/farmos/__init__.py
index bda5d03..deacd7d 100644
--- a/src/wuttafarm/web/views/farmos/__init__.py
+++ b/src/wuttafarm/web/views/farmos/__init__.py
@@ -36,9 +36,5 @@ def includeme(config):
config.include("wuttafarm.web.views.farmos.animal_types")
config.include("wuttafarm.web.views.farmos.animals")
config.include("wuttafarm.web.views.farmos.groups")
- config.include("wuttafarm.web.views.farmos.plants")
config.include("wuttafarm.web.views.farmos.log_types")
config.include("wuttafarm.web.views.farmos.logs_activity")
- config.include("wuttafarm.web.views.farmos.logs_harvest")
- config.include("wuttafarm.web.views.farmos.logs_medical")
- config.include("wuttafarm.web.views.farmos.logs_observation")
diff --git a/src/wuttafarm/web/views/farmos/groups.py b/src/wuttafarm/web/views/farmos/groups.py
index ddb7278..c6748c4 100644
--- a/src/wuttafarm/web/views/farmos/groups.py
+++ b/src/wuttafarm/web/views/farmos/groups.py
@@ -115,9 +115,6 @@ class GroupView(FarmOSMasterView):
else:
archived = group["attributes"]["status"] == "archived"
- if notes := group["attributes"]["notes"]:
- notes = notes["value"]
-
return {
"uuid": group["id"],
"drupal_id": group["attributes"]["drupal_internal__id"],
@@ -127,7 +124,7 @@ class GroupView(FarmOSMasterView):
"is_fixed": group["attributes"]["is_fixed"],
"is_location": group["attributes"]["is_location"],
"archived": archived,
- "notes": notes or colander.null,
+ "notes": group["attributes"]["notes"]["value"],
}
def configure_form(self, form):
diff --git a/src/wuttafarm/web/views/farmos/logs.py b/src/wuttafarm/web/views/farmos/logs.py
deleted file mode 100644
index a3e804f..0000000
--- a/src/wuttafarm/web/views/farmos/logs.py
+++ /dev/null
@@ -1,142 +0,0 @@
-# -*- 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 .
-#
-################################################################################
-"""
-View for farmOS Harvest Logs
-"""
-
-import datetime
-
-import colander
-
-from wuttaweb.forms.schema import WuttaDateTime
-from wuttaweb.forms.widgets import WuttaDateTimeWidget
-
-from wuttafarm.web.views.farmos import FarmOSMasterView
-
-
-class LogMasterView(FarmOSMasterView):
- """
- Base class for farmOS Log master views
- """
-
- farmos_log_type = None
-
- grid_columns = [
- "name",
- "timestamp",
- "status",
- ]
-
- sort_defaults = ("timestamp", "desc")
-
- form_fields = [
- "name",
- "timestamp",
- "status",
- "notes",
- ]
-
- def get_grid_data(self, columns=None, session=None):
- result = self.farmos_client.log.get(self.farmos_log_type)
- return [self.normalize_log(l) for l in result["data"]]
-
- def configure_grid(self, grid):
- g = grid
- super().configure_grid(g)
-
- # name
- g.set_link("name")
- g.set_searchable("name")
-
- # timestamp
- g.set_renderer("timestamp", "datetime")
-
- def get_instance(self):
- log = self.farmos_client.log.get_id(
- self.farmos_log_type, self.request.matchdict["uuid"]
- )
- self.raw_json = log
- return self.normalize_log(log["data"])
-
- def get_instance_title(self, log):
- return log["name"]
-
- def normalize_log(self, log):
-
- if timestamp := log["attributes"]["timestamp"]:
- timestamp = datetime.datetime.fromisoformat(timestamp)
- timestamp = self.app.localtime(timestamp)
-
- if notes := log["attributes"]["notes"]:
- notes = notes["value"]
-
- return {
- "uuid": log["id"],
- "drupal_id": log["attributes"]["drupal_internal__id"],
- "name": log["attributes"]["name"],
- "timestamp": timestamp,
- "status": log["attributes"]["status"],
- "notes": notes or colander.null,
- }
-
- def configure_form(self, form):
- f = form
- super().configure_form(f)
-
- # timestamp
- f.set_node("timestamp", WuttaDateTime())
- f.set_widget("timestamp", WuttaDateTimeWidget(self.request))
-
- # notes
- f.set_widget("notes", "notes")
-
- def get_xref_buttons(self, log):
- model = self.app.model
- session = self.Session()
-
- buttons = [
- self.make_button(
- "View in farmOS",
- primary=True,
- url=self.app.get_farmos_url(f"/log/{log['drupal_id']}"),
- target="_blank",
- icon_left="external-link-alt",
- ),
- ]
-
- if wf_log := (
- session.query(model.Log)
- .filter(model.Log.farmos_uuid == log["uuid"])
- .first()
- ):
- buttons.append(
- self.make_button(
- f"View {self.app.get_title()} record",
- primary=True,
- url=self.request.route_url(
- f"logs_{self.farmos_log_type}.view", uuid=wf_log.uuid
- ),
- icon_left="eye",
- )
- )
-
- return buttons
diff --git a/src/wuttafarm/web/views/farmos/logs_activity.py b/src/wuttafarm/web/views/farmos/logs_activity.py
index 972ca31..e966810 100644
--- a/src/wuttafarm/web/views/farmos/logs_activity.py
+++ b/src/wuttafarm/web/views/farmos/logs_activity.py
@@ -20,13 +20,20 @@
#
################################################################################
"""
-View for farmOS Activity Logs
+View for farmOS activity logs
"""
-from wuttafarm.web.views.farmos.logs import LogMasterView
+import datetime
+
+import colander
+
+from wuttaweb.forms.schema import WuttaDateTime
+from wuttaweb.forms.widgets import WuttaDateTimeWidget
+
+from wuttafarm.web.views.farmos import FarmOSMasterView
-class ActivityLogView(LogMasterView):
+class ActivityLogView(FarmOSMasterView):
"""
View for farmOS activity logs
"""
@@ -38,9 +45,105 @@ class ActivityLogView(LogMasterView):
route_prefix = "farmos_logs_activity"
url_prefix = "/farmOS/logs/activity"
- farmos_log_type = "activity"
farmos_refurl_path = "/logs/activity"
+ grid_columns = [
+ "name",
+ "timestamp",
+ "status",
+ ]
+
+ sort_defaults = ("timestamp", "desc")
+
+ form_fields = [
+ "name",
+ "timestamp",
+ "status",
+ "notes",
+ ]
+
+ def get_grid_data(self, columns=None, session=None):
+ logs = self.farmos_client.log.get("activity")
+ return [self.normalize_log(t) for t in logs["data"]]
+
+ def configure_grid(self, grid):
+ g = grid
+ super().configure_grid(g)
+
+ # name
+ g.set_link("name")
+ g.set_searchable("name")
+
+ # timestamp
+ g.set_renderer("timestamp", "datetime")
+
+ def get_instance(self):
+ log = self.farmos_client.log.get_id("activity", self.request.matchdict["uuid"])
+ self.raw_json = log
+ return self.normalize_log(log["data"])
+
+ def get_instance_title(self, log):
+ return log["name"]
+
+ def normalize_log(self, log):
+
+ if timestamp := log["attributes"]["timestamp"]:
+ timestamp = datetime.datetime.fromisoformat(timestamp)
+ timestamp = self.app.localtime(timestamp)
+
+ if notes := log["attributes"]["notes"]:
+ notes = notes["value"]
+
+ return {
+ "uuid": log["id"],
+ "drupal_id": log["attributes"]["drupal_internal__id"],
+ "name": log["attributes"]["name"],
+ "timestamp": timestamp,
+ "status": log["attributes"]["status"],
+ "notes": notes or colander.null,
+ }
+
+ def configure_form(self, form):
+ f = form
+ super().configure_form(f)
+
+ # timestamp
+ f.set_node("timestamp", WuttaDateTime())
+ f.set_widget("timestamp", WuttaDateTimeWidget(self.request))
+
+ # notes
+ f.set_widget("notes", "notes")
+
+ def get_xref_buttons(self, log):
+ model = self.app.model
+ session = self.Session()
+
+ buttons = [
+ self.make_button(
+ "View in farmOS",
+ primary=True,
+ url=self.app.get_farmos_url(f"/log/{log['drupal_id']}"),
+ target="_blank",
+ icon_left="external-link-alt",
+ ),
+ ]
+
+ if wf_log := (
+ session.query(model.ActivityLog)
+ .filter(model.ActivityLog.farmos_uuid == log["uuid"])
+ .first()
+ ):
+ buttons.append(
+ self.make_button(
+ f"View {self.app.get_title()} record",
+ primary=True,
+ url=self.request.route_url("activity_logs.view", uuid=wf_log.uuid),
+ icon_left="eye",
+ )
+ )
+
+ return buttons
+
def defaults(config, **kwargs):
base = globals()
diff --git a/src/wuttafarm/web/views/farmos/logs_harvest.py b/src/wuttafarm/web/views/farmos/logs_harvest.py
deleted file mode 100644
index 0f39a5a..0000000
--- a/src/wuttafarm/web/views/farmos/logs_harvest.py
+++ /dev/null
@@ -1,53 +0,0 @@
-# -*- 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 .
-#
-################################################################################
-"""
-View for farmOS Harvest Logs
-"""
-
-from wuttafarm.web.views.farmos.logs import LogMasterView
-
-
-class HarvestLogView(LogMasterView):
- """
- View for farmOS harvest logs
- """
-
- model_name = "farmos_harvest_log"
- model_title = "farmOS Harvest Log"
- model_title_plural = "farmOS Harvest Logs"
-
- route_prefix = "farmos_logs_harvest"
- url_prefix = "/farmOS/logs/harvest"
-
- farmos_log_type = "harvest"
- farmos_refurl_path = "/logs/harvest"
-
-
-def defaults(config, **kwargs):
- base = globals()
-
- HarvestLogView = kwargs.get("HarvestLogView", base["HarvestLogView"])
- HarvestLogView.defaults(config)
-
-
-def includeme(config):
- defaults(config)
diff --git a/src/wuttafarm/web/views/farmos/logs_medical.py b/src/wuttafarm/web/views/farmos/logs_medical.py
deleted file mode 100644
index 95a88c5..0000000
--- a/src/wuttafarm/web/views/farmos/logs_medical.py
+++ /dev/null
@@ -1,53 +0,0 @@
-# -*- 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 .
-#
-################################################################################
-"""
-View for farmOS Medical Logs
-"""
-
-from wuttafarm.web.views.farmos.logs import LogMasterView
-
-
-class MedicalLogView(LogMasterView):
- """
- View for farmOS medical logs
- """
-
- model_name = "farmos_medical_log"
- model_title = "farmOS Medical Log"
- model_title_plural = "farmOS Medical Logs"
-
- route_prefix = "farmos_logs_medical"
- url_prefix = "/farmOS/logs/medical"
-
- farmos_log_type = "medical"
- farmos_refurl_path = "/logs/medical"
-
-
-def defaults(config, **kwargs):
- base = globals()
-
- MedicalLogView = kwargs.get("MedicalLogView", base["MedicalLogView"])
- MedicalLogView.defaults(config)
-
-
-def includeme(config):
- defaults(config)
diff --git a/src/wuttafarm/web/views/farmos/logs_observation.py b/src/wuttafarm/web/views/farmos/logs_observation.py
deleted file mode 100644
index ab27b5a..0000000
--- a/src/wuttafarm/web/views/farmos/logs_observation.py
+++ /dev/null
@@ -1,53 +0,0 @@
-# -*- 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 .
-#
-################################################################################
-"""
-View for farmOS Observation Logs
-"""
-
-from wuttafarm.web.views.farmos.logs import LogMasterView
-
-
-class ObservationLogView(LogMasterView):
- """
- View for farmOS observation logs
- """
-
- model_name = "farmos_observation_log"
- model_title = "farmOS Observation Log"
- model_title_plural = "farmOS Observation Logs"
-
- route_prefix = "farmos_logs_observation"
- url_prefix = "/farmOS/logs/observation"
-
- farmos_log_type = "observation"
- farmos_refurl_path = "/logs/observation"
-
-
-def defaults(config, **kwargs):
- base = globals()
-
- ObservationLogView = kwargs.get("ObservationLogView", base["ObservationLogView"])
- ObservationLogView.defaults(config)
-
-
-def includeme(config):
- defaults(config)
diff --git a/src/wuttafarm/web/views/farmos/plants.py b/src/wuttafarm/web/views/farmos/plants.py
deleted file mode 100644
index f02801f..0000000
--- a/src/wuttafarm/web/views/farmos/plants.py
+++ /dev/null
@@ -1,362 +0,0 @@
-# -*- 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 Farm Plants
-"""
-
-import datetime
-
-import colander
-
-from wuttaweb.forms.schema import WuttaDateTime
-from wuttaweb.forms.widgets import WuttaDateTimeWidget
-
-from wuttafarm.web.views.farmos import FarmOSMasterView
-from wuttafarm.web.forms.schema import UsersType, StructureType, FarmOSPlantTypes
-from wuttafarm.web.forms.widgets import ImageWidget
-
-
-class PlantTypeView(FarmOSMasterView):
- """
- Master view for Plant Types in farmOS.
- """
-
- model_name = "farmos_plant_type"
- model_title = "farmOS Plant Type"
- model_title_plural = "farmOS Plant Types"
-
- route_prefix = "farmos_plant_types"
- url_prefix = "/farmOS/plant-types"
-
- farmos_refurl_path = "/admin/structure/taxonomy/manage/plant_type/overview"
-
- grid_columns = [
- "name",
- "description",
- "changed",
- ]
-
- sort_defaults = "name"
-
- form_fields = [
- "name",
- "description",
- "changed",
- ]
-
- def get_grid_data(self, columns=None, session=None):
- result = self.farmos_client.resource.get("taxonomy_term", "plant_type")
- return [self.normalize_plant_type(t) for t in result["data"]]
-
- def configure_grid(self, grid):
- g = grid
- super().configure_grid(g)
-
- # name
- g.set_link("name")
- g.set_searchable("name")
-
- # changed
- g.set_renderer("changed", "datetime")
-
- def get_instance(self):
- plant_type = self.farmos_client.resource.get_id(
- "taxonomy_term", "plant_type", self.request.matchdict["uuid"]
- )
- self.raw_json = plant_type
- return self.normalize_plant_type(plant_type["data"])
-
- def get_instance_title(self, plant_type):
- return plant_type["name"]
-
- def normalize_plant_type(self, plant_type):
-
- if changed := plant_type["attributes"]["changed"]:
- changed = datetime.datetime.fromisoformat(changed)
- changed = self.app.localtime(changed)
-
- if description := plant_type["attributes"]["description"]:
- description = description["value"]
-
- return {
- "uuid": plant_type["id"],
- "drupal_id": plant_type["attributes"]["drupal_internal__tid"],
- "name": plant_type["attributes"]["name"],
- "description": description or colander.null,
- "changed": changed,
- }
-
- def configure_form(self, form):
- f = form
- super().configure_form(f)
-
- # description
- f.set_widget("description", "notes")
-
- # changed
- f.set_node("changed", WuttaDateTime())
-
- def get_xref_buttons(self, plant_type):
- model = self.app.model
- session = self.Session()
-
- buttons = [
- self.make_button(
- "View in farmOS",
- primary=True,
- url=self.app.get_farmos_url(
- f"/taxonomy/term/{plant_type['drupal_id']}"
- ),
- target="_blank",
- icon_left="external-link-alt",
- )
- ]
-
- if wf_plant_type := (
- session.query(model.PlantType)
- .filter(model.PlantType.farmos_uuid == plant_type["uuid"])
- .first()
- ):
- buttons.append(
- self.make_button(
- f"View {self.app.get_title()} record",
- primary=True,
- url=self.request.route_url(
- "plant_types.view", uuid=wf_plant_type.uuid
- ),
- icon_left="eye",
- )
- )
-
- return buttons
-
-
-class PlantAssetView(FarmOSMasterView):
- """
- Master view for farmOS Plant Assets
- """
-
- model_name = "farmos_asset_plant"
- model_title = "farmOS Plant Asset"
- model_title_plural = "farmOS Plant Assets"
-
- route_prefix = "farmos_asset_plant"
- url_prefix = "/farmOS/assets/plant"
-
- farmos_refurl_path = "/assets/plant"
-
- grid_columns = [
- "name",
- "archived",
- ]
-
- sort_defaults = "name"
-
- form_fields = [
- "name",
- "plant_types",
- "archived",
- "owners",
- "location",
- "notes",
- "raw_image_url",
- "large_image_url",
- "thumbnail_image_url",
- "image",
- ]
-
- def get_grid_data(self, columns=None, session=None):
- result = self.farmos_client.asset.get("plant")
- return [self.normalize_plant(a) for a in result["data"]]
-
- def configure_grid(self, grid):
- g = grid
- super().configure_grid(g)
-
- # name
- g.set_link("name")
- g.set_searchable("name")
-
- # archived
- g.set_renderer("archived", "boolean")
-
- def get_instance(self):
-
- plant = self.farmos_client.resource.get_id(
- "asset", "plant", self.request.matchdict["uuid"]
- )
- self.raw_json = plant
-
- # instance data
- data = self.normalize_plant(plant["data"])
-
- if relationships := plant["data"].get("relationships"):
-
- # add plant types
- if plant_type := relationships.get("plant_type"):
- if plant_type["data"]:
- data["plant_types"] = []
- for plant_type in plant_type["data"]:
- plant_type = self.farmos_client.resource.get_id(
- "taxonomy_term", "plant_type", plant_type["id"]
- )
- data["plant_types"].append(
- {
- "uuid": plant_type["data"]["id"],
- "name": plant_type["data"]["attributes"]["name"],
- }
- )
-
- # add location
- if location := relationships.get("location"):
- if location["data"]:
- location = self.farmos_client.resource.get_id(
- "asset", "structure", location["data"][0]["id"]
- )
- data["location"] = {
- "uuid": location["data"]["id"],
- "name": location["data"]["attributes"]["name"],
- }
-
- # add owners
- if owner := relationships.get("owner"):
- data["owners"] = []
- for owner_data in owner["data"]:
- owner = self.farmos_client.resource.get_id(
- "user", "user", owner_data["id"]
- )
- data["owners"].append(
- {
- "uuid": owner["data"]["id"],
- "display_name": owner["data"]["attributes"]["display_name"],
- }
- )
-
- # add image urls
- if image := relationships.get("image"):
- if image["data"]:
- image = self.farmos_client.resource.get_id(
- "file", "file", image["data"][0]["id"]
- )
- data["raw_image_url"] = self.app.get_farmos_url(
- image["data"]["attributes"]["uri"]["url"]
- )
- # nb. other styles available: medium, wide
- data["large_image_url"] = image["data"]["attributes"][
- "image_style_uri"
- ]["large"]
- data["thumbnail_image_url"] = image["data"]["attributes"][
- "image_style_uri"
- ]["thumbnail"]
-
- return data
-
- def get_instance_title(self, plant):
- return plant["name"]
-
- def normalize_plant(self, plant):
-
- if notes := plant["attributes"]["notes"]:
- notes = notes["value"]
-
- if self.farmos_4x:
- archived = plant["attributes"]["archived"]
- else:
- archived = plant["attributes"]["status"] == "archived"
-
- return {
- "uuid": plant["id"],
- "drupal_id": plant["attributes"]["drupal_internal__id"],
- "name": plant["attributes"]["name"],
- "location": colander.null, # TODO
- "archived": archived,
- "notes": notes or colander.null,
- }
-
- def configure_form(self, form):
- f = form
- super().configure_form(f)
- plant = f.model_instance
-
- # plant_types
- f.set_node("plant_types", FarmOSPlantTypes(self.request))
-
- # location
- f.set_node("location", StructureType(self.request))
-
- # owners
- f.set_node("owners", UsersType(self.request))
-
- # notes
- f.set_widget("notes", "notes")
-
- # archived
- f.set_node("archived", colander.Boolean())
-
- # image
- if url := plant.get("large_image_url"):
- f.set_widget("image", ImageWidget("plant image"))
- f.set_default("image", url)
-
- def get_xref_buttons(self, plant):
- model = self.app.model
- session = self.Session()
-
- buttons = [
- self.make_button(
- "View in farmOS",
- primary=True,
- url=self.app.get_farmos_url(f"/asset/{plant['drupal_id']}"),
- target="_blank",
- icon_left="external-link-alt",
- ),
- ]
-
- if wf_plant := (
- session.query(model.Asset)
- .filter(model.Asset.farmos_uuid == plant["uuid"])
- .first()
- ):
- buttons.append(
- self.make_button(
- f"View {self.app.get_title()} record",
- primary=True,
- url=self.request.route_url("plant_assets.view", uuid=wf_plant.uuid),
- icon_left="eye",
- )
- )
-
- return buttons
-
-
-def defaults(config, **kwargs):
- base = globals()
-
- PlantTypeView = kwargs.get("PlantTypeView", base["PlantTypeView"])
- PlantTypeView.defaults(config)
-
- PlantAssetView = kwargs.get("PlantAssetView", base["PlantAssetView"])
- PlantAssetView.defaults(config)
-
-
-def includeme(config):
- defaults(config)
diff --git a/src/wuttafarm/web/views/groups.py b/src/wuttafarm/web/views/groups.py
index 4b26463..21d7ed4 100644
--- a/src/wuttafarm/web/views/groups.py
+++ b/src/wuttafarm/web/views/groups.py
@@ -24,7 +24,7 @@ Master view for Groups
"""
from wuttafarm.web.views.assets import AssetMasterView
-from wuttafarm.db.model import GroupAsset
+from wuttafarm.db.model.groups import GroupAsset
class GroupView(AssetMasterView):
@@ -42,7 +42,6 @@ class GroupView(AssetMasterView):
"thumbnail",
"drupal_id",
"asset_name",
- "produces_eggs",
"archived",
]
@@ -50,7 +49,6 @@ class GroupView(AssetMasterView):
"asset_name",
"notes",
"asset_type",
- "produces_eggs",
"archived",
"farmos_uuid",
"drupal_id",
diff --git a/src/wuttafarm/web/views/land.py b/src/wuttafarm/web/views/land.py
index 22827a0..aad15e7 100644
--- a/src/wuttafarm/web/views/land.py
+++ b/src/wuttafarm/web/views/land.py
@@ -25,7 +25,7 @@ Master view for Land Types
from webhelpers2.html import HTML, tags
-from wuttafarm.db.model import LandType, LandAsset
+from wuttafarm.db.model.land import LandType, LandAsset
from wuttafarm.web.views.assets import AssetTypeMasterView, AssetMasterView
from wuttafarm.web.forms.schema import LandTypeRef
diff --git a/src/wuttafarm/web/views/log_types.py b/src/wuttafarm/web/views/log_types.py
new file mode 100644
index 0000000..13ea35f
--- /dev/null
+++ b/src/wuttafarm/web/views/log_types.py
@@ -0,0 +1,90 @@
+# -*- 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 Log Types
+"""
+
+from wuttafarm.db.model.logs import LogType
+from wuttafarm.web.views import WuttaFarmMasterView
+
+
+class LogTypeView(WuttaFarmMasterView):
+ """
+ Master view for Log Types
+ """
+
+ model_class = LogType
+ route_prefix = "log_types"
+ url_prefix = "/log-types"
+
+ grid_columns = [
+ "name",
+ "description",
+ ]
+
+ sort_defaults = "name"
+
+ filter_defaults = {
+ "name": {"active": True, "verb": "contains"},
+ }
+
+ form_fields = [
+ "name",
+ "description",
+ "farmos_uuid",
+ "drupal_id",
+ ]
+
+ def configure_grid(self, grid):
+ g = grid
+ super().configure_grid(g)
+
+ # name
+ g.set_link("name")
+
+ def get_xref_buttons(self, log_type):
+ buttons = super().get_xref_buttons(log_type)
+
+ if log_type.farmos_uuid:
+ buttons.append(
+ self.make_button(
+ "View farmOS record",
+ primary=True,
+ url=self.request.route_url(
+ "farmos_log_types.view", uuid=log_type.farmos_uuid
+ ),
+ icon_left="eye",
+ )
+ )
+
+ return buttons
+
+
+def defaults(config, **kwargs):
+ base = globals()
+
+ LogTypeView = kwargs.get("LogTypeView", base["LogTypeView"])
+ LogTypeView.defaults(config)
+
+
+def includeme(config):
+ defaults(config)
diff --git a/src/wuttafarm/web/views/logs.py b/src/wuttafarm/web/views/logs.py
deleted file mode 100644
index cf77967..0000000
--- a/src/wuttafarm/web/views/logs.py
+++ /dev/null
@@ -1,342 +0,0 @@
-# -*- 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 views for Logs
-"""
-
-from collections import OrderedDict
-
-import colander
-
-from wuttaweb.forms.schema import WuttaDictEnum
-from wuttaweb.db import Session
-from wuttaweb.forms.widgets import WuttaDateTimeWidget
-
-from wuttafarm.web.views import WuttaFarmMasterView
-from wuttafarm.db.model import LogType, Log
-from wuttafarm.web.forms.schema import LogAssetRefs
-
-
-def get_log_type_enum(config):
- app = config.get_app()
- model = app.model
- session = Session()
- log_types = OrderedDict()
- query = session.query(model.LogType).order_by(model.LogType.name)
- for log_type in query:
- log_types[log_type.drupal_id] = log_type.name
- return log_types
-
-
-class LogTypeView(WuttaFarmMasterView):
- """
- Master view for Log Types
- """
-
- model_class = LogType
- route_prefix = "log_types"
- url_prefix = "/log-types"
-
- grid_columns = [
- "name",
- "description",
- ]
-
- sort_defaults = "name"
-
- filter_defaults = {
- "name": {"active": True, "verb": "contains"},
- }
-
- form_fields = [
- "name",
- "description",
- "farmos_uuid",
- "drupal_id",
- ]
-
- def configure_grid(self, grid):
- g = grid
- super().configure_grid(g)
-
- # name
- g.set_link("name")
-
- def get_xref_buttons(self, log_type):
- buttons = super().get_xref_buttons(log_type)
-
- if log_type.farmos_uuid:
- buttons.append(
- self.make_button(
- "View farmOS record",
- primary=True,
- url=self.request.route_url(
- "farmos_log_types.view", uuid=log_type.farmos_uuid
- ),
- icon_left="eye",
- )
- )
-
- return buttons
-
-
-class LogView(WuttaFarmMasterView):
- """
- Master view for All Logs
- """
-
- model_class = Log
- route_prefix = "log"
- url_prefix = "/logs"
-
- farmos_refurl_path = "/logs"
-
- viewable = False
- creatable = False
- editable = False
- deletable = False
- model_is_versioned = False
-
- labels = {
- "message": "Log Name",
- }
-
- grid_columns = [
- "status",
- "drupal_id",
- "timestamp",
- "message",
- "log_type",
- "assets",
- "location",
- "quantity",
- "groups",
- "is_group_assignment",
- ]
-
- sort_defaults = ("timestamp", "desc")
-
- filter_defaults = {
- "message": {"active": True, "verb": "contains"},
- }
-
- def configure_grid(self, grid):
- g = grid
- super().configure_grid(g)
-
- # drupal_id
- g.set_label("drupal_id", "ID", column_only=True)
-
- # timestamp
- g.set_renderer("timestamp", "date")
- g.set_link("timestamp")
-
- # message
- g.set_link("message")
-
- # log_type
- g.set_enum("log_type", get_log_type_enum(self.config))
-
- # assets
- g.set_renderer("assets", self.render_assets_for_grid)
-
- # view action links to final log record
- def log_url(log, i):
- return self.request.route_url(f"logs_{log.log_type}.view", uuid=log.uuid)
-
- g.add_action("view", icon="eye", url=log_url)
-
- def render_assets_for_grid(self, log, field, value):
- assets = [str(a.asset) for a in log._assets]
- return ", ".join(assets)
-
-
-class LogMasterView(WuttaFarmMasterView):
- """
- Base class for Asset master views
- """
-
- grid_columns = [
- "status",
- "drupal_id",
- "timestamp",
- "message",
- "assets",
- "location",
- "quantity",
- "is_group_assignment",
- ]
-
- sort_defaults = ("timestamp", "desc")
-
- filter_defaults = {
- "message": {"active": True, "verb": "contains"},
- }
-
- form_fields = [
- "message",
- "timestamp",
- "assets",
- "location",
- "quantity",
- "notes",
- "status",
- "log_type",
- "owners",
- "is_group_assignment",
- "farmos_uuid",
- "drupal_id",
- ]
-
- def get_query(self, session=None):
- """ """
- model = self.app.model
- model_class = self.get_model_class()
- session = session or self.Session()
- return session.query(model_class).join(model.Log)
-
- def configure_grid(self, grid):
- g = grid
- super().configure_grid(g)
- model = self.app.model
-
- # status
- g.set_sorter("status", model.Log.status)
- g.set_filter("status", model.Log.status)
-
- # drupal_id
- g.set_label("drupal_id", "ID", column_only=True)
- g.set_sorter("drupal_id", model.Log.drupal_id)
- g.set_filter("drupal_id", model.Log.drupal_id)
-
- # timestamp
- g.set_renderer("timestamp", "date")
- g.set_link("timestamp")
- g.set_sorter("timestamp", model.Log.timestamp)
- g.set_filter("timestamp", model.Log.timestamp)
-
- # message
- g.set_link("message")
- g.set_sorter("message", model.Log.message)
- g.set_filter("message", model.Log.message)
-
- # assets
- g.set_renderer("assets", self.render_assets_for_grid)
-
- def render_assets_for_grid(self, log, field, value):
- return ", ".join([a.asset.asset_name for a in log.log._assets])
-
- def configure_form(self, form):
- f = form
- super().configure_form(f)
- enum = self.app.enum
- log = f.model_instance
-
- # timestamp
- # TODO: the widget should be automatic (assn proxy field)
- f.set_widget("timestamp", WuttaDateTimeWidget(self.request))
- if self.creating:
- f.set_default("timestamp", self.app.make_utc())
-
- # assets
- if self.creating or self.editing:
- f.remove("assets") # TODO: need to support this
- else:
- f.set_node("assets", LogAssetRefs(self.request))
- f.set_default("assets", [a.asset_uuid for a in log.log._assets])
-
- # location
- if self.creating or self.editing:
- f.remove("location") # TODO: need to support this
-
- # log_type
- if self.creating:
- f.remove("log_type")
- else:
- f.set_node(
- "log_type",
- WuttaDictEnum(self.request, get_log_type_enum(self.config)),
- )
- f.set_readonly("log_type")
-
- # quantity
- if self.creating or self.editing:
- f.remove("quantity") # TODO: need to support this
-
- # notes
- f.set_widget("notes", "notes")
-
- # owners
- if self.creating or self.editing:
- f.remove("owners") # TODO: need to support this
-
- # status
- f.set_node("status", WuttaDictEnum(self.request, enum.LOG_STATUS))
-
- # is_group_assignment
- f.set_node("is_group_assignment", colander.Boolean())
-
- def objectify(self, form):
- log = super().objectify(form)
-
- if self.creating:
- model_class = self.get_model_class()
- log.log_type = self.get_farmos_log_type()
-
- return log
-
- def get_farmos_url(self, log):
- return self.app.get_farmos_url(f"/log/{log.drupal_id}")
-
- def get_farmos_log_type(self):
- return self.model_class.__wutta_hint__["farmos_log_type"]
-
- def get_xref_buttons(self, log):
- buttons = super().get_xref_buttons(log)
-
- if log.farmos_uuid:
- log_type = self.get_farmos_log_type()
- route = f"farmos_logs_{log_type}.view"
- buttons.append(
- self.make_button(
- "View farmOS record",
- primary=True,
- url=self.request.route_url(route, uuid=log.farmos_uuid),
- icon_left="eye",
- )
- )
-
- return buttons
-
-
-def defaults(config, **kwargs):
- base = globals()
-
- LogTypeView = kwargs.get("LogTypeView", base["LogTypeView"])
- LogTypeView.defaults(config)
-
- LogView = kwargs.get("LogView", base["LogView"])
- LogView.defaults(config)
-
-
-def includeme(config):
- defaults(config)
diff --git a/src/wuttafarm/web/views/logs_activity.py b/src/wuttafarm/web/views/logs_activity.py
index dda3ca7..a2b2154 100644
--- a/src/wuttafarm/web/views/logs_activity.py
+++ b/src/wuttafarm/web/views/logs_activity.py
@@ -23,21 +23,76 @@
Master view for Activity Logs
"""
-from wuttafarm.web.views.logs import LogMasterView
-from wuttafarm.db.model import ActivityLog
+from wuttafarm.db.model.logs import ActivityLog
+from wuttafarm.web.views import WuttaFarmMasterView
-class ActivityLogView(LogMasterView):
+class ActivityLogView(WuttaFarmMasterView):
"""
Master view for Activity Logs
"""
model_class = ActivityLog
- route_prefix = "logs_activity"
+ route_prefix = "activity_logs"
url_prefix = "/logs/activity"
farmos_refurl_path = "/logs/activity"
+ grid_columns = [
+ "message",
+ "timestamp",
+ "status",
+ ]
+
+ sort_defaults = ("timestamp", "desc")
+
+ filter_defaults = {
+ "message": {"active": True, "verb": "contains"},
+ }
+
+ form_fields = [
+ "message",
+ "timestamp",
+ "status",
+ "notes",
+ "farmos_uuid",
+ "drupal_id",
+ ]
+
+ def configure_grid(self, grid):
+ g = grid
+ super().configure_grid(g)
+
+ # message
+ g.set_link("message")
+
+ def configure_form(self, form):
+ f = form
+ super().configure_form(f)
+
+ # notes
+ f.set_widget("notes", "notes")
+
+ def get_farmos_url(self, log):
+ return self.app.get_farmos_url(f"/log/{log.drupal_id}")
+
+ def get_xref_buttons(self, log):
+ buttons = super().get_xref_buttons(log)
+
+ if log.farmos_uuid:
+ buttons.append(
+ self.make_button(
+ "View farmOS record",
+ primary=True,
+ url=self.request.route_url(
+ "farmos_logs_activity.view", uuid=log.farmos_uuid
+ ),
+ icon_left="eye",
+ )
+ )
+
+ return buttons
+
def defaults(config, **kwargs):
base = globals()
diff --git a/src/wuttafarm/web/views/logs_harvest.py b/src/wuttafarm/web/views/logs_harvest.py
deleted file mode 100644
index 825c864..0000000
--- a/src/wuttafarm/web/views/logs_harvest.py
+++ /dev/null
@@ -1,50 +0,0 @@
-# -*- 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 Harvest Logs
-"""
-
-from wuttafarm.web.views.logs import LogMasterView
-from wuttafarm.db.model import HarvestLog
-
-
-class HarvestLogView(LogMasterView):
- """
- Master view for Harvest Logs
- """
-
- model_class = HarvestLog
- route_prefix = "logs_harvest"
- url_prefix = "/logs/harvest"
-
- farmos_refurl_path = "/logs/harvest"
-
-
-def defaults(config, **kwargs):
- base = globals()
-
- HarvestLogView = kwargs.get("HarvestLogView", base["HarvestLogView"])
- HarvestLogView.defaults(config)
-
-
-def includeme(config):
- defaults(config)
diff --git a/src/wuttafarm/web/views/logs_medical.py b/src/wuttafarm/web/views/logs_medical.py
deleted file mode 100644
index d582db9..0000000
--- a/src/wuttafarm/web/views/logs_medical.py
+++ /dev/null
@@ -1,50 +0,0 @@
-# -*- 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 Medical Logs
-"""
-
-from wuttafarm.web.views.logs import LogMasterView
-from wuttafarm.db.model import MedicalLog
-
-
-class MedicalLogView(LogMasterView):
- """
- Master view for Medical Logs
- """
-
- model_class = MedicalLog
- route_prefix = "logs_medical"
- url_prefix = "/logs/medical"
-
- farmos_refurl_path = "/logs/medical"
-
-
-def defaults(config, **kwargs):
- base = globals()
-
- MedicalLogView = kwargs.get("MedicalLogView", base["MedicalLogView"])
- MedicalLogView.defaults(config)
-
-
-def includeme(config):
- defaults(config)
diff --git a/src/wuttafarm/web/views/logs_observation.py b/src/wuttafarm/web/views/logs_observation.py
deleted file mode 100644
index a4b9e8e..0000000
--- a/src/wuttafarm/web/views/logs_observation.py
+++ /dev/null
@@ -1,50 +0,0 @@
-# -*- 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 Observation Logs
-"""
-
-from wuttafarm.web.views.logs import LogMasterView
-from wuttafarm.db.model import ObservationLog
-
-
-class ObservationLogView(LogMasterView):
- """
- Master view for Observation Logs
- """
-
- model_class = ObservationLog
- route_prefix = "logs_observation"
- url_prefix = "/logs/observation"
-
- farmos_refurl_path = "/logs/observation"
-
-
-def defaults(config, **kwargs):
- base = globals()
-
- ObservationLogView = kwargs.get("ObservationLogView", base["ObservationLogView"])
- ObservationLogView.defaults(config)
-
-
-def includeme(config):
- defaults(config)
diff --git a/src/wuttafarm/web/views/plants.py b/src/wuttafarm/web/views/plants.py
deleted file mode 100644
index d92949a..0000000
--- a/src/wuttafarm/web/views/plants.py
+++ /dev/null
@@ -1,201 +0,0 @@
-# -*- 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 wuttaweb.forms.schema import WuttaDictEnum
-
-from wuttafarm.db.model import PlantType, PlantAsset
-from wuttafarm.web.views.assets import AssetTypeMasterView, AssetMasterView
-from wuttafarm.web.forms.schema import PlantTypeRefs
-from wuttafarm.web.forms.widgets import ImageWidget
-
-
-class PlantTypeView(AssetTypeMasterView):
- """
- Master view for Plant Types
- """
-
- model_class = PlantType
- route_prefix = "plant_types"
- url_prefix = "/plant-types"
-
- farmos_refurl_path = "/admin/structure/taxonomy/manage/plant_type/overview"
-
- grid_columns = [
- "name",
- "description",
- ]
-
- sort_defaults = "name"
-
- filter_defaults = {
- "name": {"active": True, "verb": "contains"},
- }
-
- form_fields = [
- "name",
- "description",
- "farmos_uuid",
- "drupal_id",
- ]
-
- has_rows = True
- row_model_class = PlantAsset
- rows_viewable = True
-
- row_grid_columns = [
- "asset_name",
- "archived",
- ]
-
- rows_sort_defaults = "asset_name"
-
- def configure_grid(self, grid):
- g = grid
- super().configure_grid(g)
-
- # name
- g.set_link("name")
-
- def get_farmos_url(self, plant_type):
- return self.app.get_farmos_url(f"/taxonomy/term/{plant_type.drupal_id}")
-
- def get_xref_buttons(self, plant_type):
- buttons = super().get_xref_buttons(plant_type)
-
- if plant_type.farmos_uuid:
- buttons.append(
- self.make_button(
- "View farmOS record",
- primary=True,
- url=self.request.route_url(
- "farmos_plant_types.view", uuid=plant_type.farmos_uuid
- ),
- icon_left="eye",
- )
- )
-
- return buttons
-
- def get_row_grid_data(self, plant_type):
- model = self.app.model
- session = self.Session()
- return (
- session.query(model.PlantAsset)
- .join(model.Asset)
- .outerjoin(model.PlantAssetPlantType)
- .filter(model.PlantAssetPlantType.plant_type == plant_type)
- )
-
- def configure_row_grid(self, grid):
- g = grid
- super().configure_row_grid(g)
- model = self.app.model
-
- # asset_name
- g.set_link("asset_name")
- g.set_sorter("asset_name", model.Asset.asset_name)
- g.set_filter("asset_name", model.Asset.asset_name)
-
- # archived
- g.set_renderer("archived", "boolean")
- g.set_sorter("archived", model.Asset.archived)
- g.set_filter("archived", model.Asset.archived)
-
- def get_row_action_url_view(self, plant, i):
- return self.request.route_url("plant_assets.view", uuid=plant.uuid)
-
-
-class PlantAssetView(AssetMasterView):
- """
- Master view for Plant Assets
- """
-
- model_class = PlantAsset
- route_prefix = "plant_assets"
- url_prefix = "/assets/plant"
-
- farmos_refurl_path = "/assets/plant"
-
- labels = {
- "plant_types": "Crop/Variety",
- }
-
- grid_columns = [
- "thumbnail",
- "drupal_id",
- "asset_name",
- "plant_types",
- "season",
- "archived",
- ]
-
- form_fields = [
- "asset_name",
- "plant_types",
- "season",
- "notes",
- "asset_type",
- "archived",
- "farmos_uuid",
- "drupal_id",
- "thumbnail_url",
- "image_url",
- "thumbnail",
- "image",
- ]
-
- def configure_grid(self, grid):
- g = grid
- super().configure_grid(g)
-
- # plant_types
- g.set_renderer("plant_types", self.render_grid_plant_types)
-
- def render_grid_plant_types(self, plant, field, value):
- return ", ".join([t.plant_type.name for t in plant._plant_types])
-
- def configure_form(self, form):
- f = form
- super().configure_form(f)
- enum = self.app.enum
- plant = f.model_instance
-
- # plant_types
- f.set_node("plant_types", PlantTypeRefs(self.request))
- f.set_default("plant_types", [t.plant_type_uuid for t in plant._plant_types])
-
-
-def defaults(config, **kwargs):
- base = globals()
-
- PlantTypeView = kwargs.get("PlantTypeView", base["PlantTypeView"])
- PlantTypeView.defaults(config)
-
- PlantAssetView = kwargs.get("PlantAssetView", base["PlantAssetView"])
- PlantAssetView.defaults(config)
-
-
-def includeme(config):
- defaults(config)