diff --git a/src/wuttafarm/db/alembic/versions/9e875e5cbdc1_add_logquantity.py b/src/wuttafarm/db/alembic/versions/9e875e5cbdc1_add_logquantity.py new file mode 100644 index 0000000..3867b17 --- /dev/null +++ b/src/wuttafarm/db/alembic/versions/9e875e5cbdc1_add_logquantity.py @@ -0,0 +1,118 @@ +"""add LogQuantity + +Revision ID: 9e875e5cbdc1 +Revises: 74d32b4ec210 +Create Date: 2026-02-28 21:55:31.876087 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +import wuttjamaican.db.util + + +# revision identifiers, used by Alembic. +revision: str = "9e875e5cbdc1" +down_revision: Union[str, None] = "74d32b4ec210" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + + # log_quantity + op.create_table( + "log_quantity", + sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False), + sa.Column("log_uuid", wuttjamaican.db.util.UUID(), nullable=False), + sa.Column("quantity_uuid", wuttjamaican.db.util.UUID(), nullable=False), + sa.ForeignKeyConstraint( + ["log_uuid"], ["log.uuid"], name=op.f("fk_log_quantity_log_uuid_log") + ), + sa.ForeignKeyConstraint( + ["quantity_uuid"], + ["quantity.uuid"], + name=op.f("fk_log_quantity_quantity_uuid_quantity"), + ), + sa.PrimaryKeyConstraint("uuid", name=op.f("pk_log_quantity")), + ) + op.create_table( + "log_quantity_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( + "quantity_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_quantity_version") + ), + ) + op.create_index( + op.f("ix_log_quantity_version_end_transaction_id"), + "log_quantity_version", + ["end_transaction_id"], + unique=False, + ) + op.create_index( + op.f("ix_log_quantity_version_operation_type"), + "log_quantity_version", + ["operation_type"], + unique=False, + ) + op.create_index( + "ix_log_quantity_version_pk_transaction_id", + "log_quantity_version", + ["uuid", sa.literal_column("transaction_id DESC")], + unique=False, + ) + op.create_index( + "ix_log_quantity_version_pk_validity", + "log_quantity_version", + ["uuid", "transaction_id", "end_transaction_id"], + unique=False, + ) + op.create_index( + op.f("ix_log_quantity_version_transaction_id"), + "log_quantity_version", + ["transaction_id"], + unique=False, + ) + + +def downgrade() -> None: + + # log_quantity + op.drop_index( + op.f("ix_log_quantity_version_transaction_id"), + table_name="log_quantity_version", + ) + op.drop_index( + "ix_log_quantity_version_pk_validity", table_name="log_quantity_version" + ) + op.drop_index( + "ix_log_quantity_version_pk_transaction_id", table_name="log_quantity_version" + ) + op.drop_index( + op.f("ix_log_quantity_version_operation_type"), + table_name="log_quantity_version", + ) + op.drop_index( + op.f("ix_log_quantity_version_end_transaction_id"), + table_name="log_quantity_version", + ) + op.drop_table("log_quantity_version") + op.drop_table("log_quantity") diff --git a/src/wuttafarm/db/model/__init__.py b/src/wuttafarm/db/model/__init__.py index 68695e5..15514fb 100644 --- a/src/wuttafarm/db/model/__init__.py +++ b/src/wuttafarm/db/model/__init__.py @@ -38,7 +38,7 @@ 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 import LogType, Log, LogAsset, LogGroup, LogLocation, LogQuantity, LogOwner from .log_activity import ActivityLog from .log_harvest import HarvestLog from .log_medical import MedicalLog diff --git a/src/wuttafarm/db/model/log.py b/src/wuttafarm/db/model/log.py index afa637b..b77898f 100644 --- a/src/wuttafarm/db/model/log.py +++ b/src/wuttafarm/db/model/log.py @@ -201,6 +201,19 @@ class Log(model.Base): creator=lambda asset: LogLocation(asset=asset), ) + _quantities = orm.relationship( + "LogQuantity", + cascade="all, delete-orphan", + cascade_backrefs=False, + back_populates="log", + ) + + quantities = association_proxy( + "_quantities", + "quantity", + creator=lambda quantity: LogQuantity(quantity=quantity), + ) + _owners = orm.relationship( "LogOwner", cascade="all, delete-orphan", @@ -247,6 +260,7 @@ def add_log_proxies(subclass): Log.make_proxy(subclass, "log", "assets") Log.make_proxy(subclass, "log", "groups") Log.make_proxy(subclass, "log", "locations") + Log.make_proxy(subclass, "log", "quantities") Log.make_proxy(subclass, "log", "owners") @@ -322,6 +336,30 @@ class LogLocation(model.Base): ) +class LogQuantity(model.Base): + """ + Represents a "log's quantity relationship" from farmOS. + """ + + __tablename__ = "log_quantity" + __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="_quantities", + ) + + quantity_uuid = model.uuid_fk_column("quantity.uuid", nullable=False) + quantity = orm.relationship( + "Quantity", + foreign_keys=quantity_uuid, + ) + + class LogOwner(model.Base): """ Represents a "log's owner relationship" from farmOS. diff --git a/src/wuttafarm/importing/farmos.py b/src/wuttafarm/importing/farmos.py index a1b539f..a35b35d 100644 --- a/src/wuttafarm/importing/farmos.py +++ b/src/wuttafarm/importing/farmos.py @@ -982,6 +982,7 @@ class LogImporterBase(FromFarmOS, ToWutta): "assets", "groups", "locations", + "quantities", "owners", ] ) @@ -1019,6 +1020,9 @@ class LogImporterBase(FromFarmOS, ToWutta): for asset in data["locations"] ] + if "quantities" in self.fields: + data["quantities"] = [UUID(uuid) for uuid in data["quantity_uuids"]] + if "owners" in self.fields: data["owners"] = [UUID(uuid) for uuid in data["owner_uuids"]] @@ -1042,6 +1046,9 @@ class LogImporterBase(FromFarmOS, ToWutta): (asset.asset_type, asset.farmos_uuid) for asset in log.locations ] + if "quantities" in self.fields: + data["quantities"] = [qty.farmos_uuid for qty in log.quantities] + if "owners" in self.fields: data["owners"] = [user.farmos_uuid for user in log.owners] @@ -1129,6 +1136,31 @@ class LogImporterBase(FromFarmOS, ToWutta): ) log.locations.remove(asset) + if "quantities" in self.fields: + if ( + not target_data + or target_data["quantities"] != source_data["quantities"] + ): + + for farmos_uuid in source_data["quantities"]: + if not target_data or farmos_uuid not in target_data["quantities"]: + qty = ( + self.target_session.query(model.Quantity) + .filter(model.Quantity.farmos_uuid == farmos_uuid) + .one() + ) + log.quantities.append(qty) + + if target_data: + for farmos_uuid in target_data["quantities"]: + if farmos_uuid not in source_data["quantities"]: + qty = ( + self.target_session.query(model.Quantity) + .filter(model.Quantity.farmos_uuid == farmos_uuid) + .one() + ) + log.quantities.remove(qty) + if "owners" in self.fields: if not target_data or target_data["owners"] != source_data["owners"]: diff --git a/src/wuttafarm/web/views/farmos/logs_harvest.py b/src/wuttafarm/web/views/farmos/logs_harvest.py index 08b2629..bfe7121 100644 --- a/src/wuttafarm/web/views/farmos/logs_harvest.py +++ b/src/wuttafarm/web/views/farmos/logs_harvest.py @@ -48,7 +48,6 @@ class HarvestLogView(LogMasterView): "name", "assets", "quantities", - "is_group_assignment", "owners", ] diff --git a/src/wuttafarm/web/views/logs.py b/src/wuttafarm/web/views/logs.py index 2679c3f..0608573 100644 --- a/src/wuttafarm/web/views/logs.py +++ b/src/wuttafarm/web/views/logs.py @@ -26,7 +26,7 @@ Base views for Logs from collections import OrderedDict import colander -from webhelpers2.html import tags +from webhelpers2.html import tags, HTML from wuttaweb.forms.schema import WuttaDictEnum from wuttaweb.db import Session @@ -100,6 +100,7 @@ class LogMasterView(WuttaFarmMasterView): labels = { "message": "Log Name", "locations": "Location", + "quantities": "Quantity", } grid_columns = [ @@ -109,7 +110,7 @@ class LogMasterView(WuttaFarmMasterView): "message", "assets", "locations", - "quantity", + "quantities", "is_group_assignment", "owners", ] @@ -189,6 +190,9 @@ class LogMasterView(WuttaFarmMasterView): # locations g.set_renderer("locations", self.render_assets_for_grid) + # quantities + g.set_renderer("quantities", self.render_quantities_for_grid) + # is_group_assignment g.set_renderer("is_group_assignment", "boolean") g.set_sorter("is_group_assignment", model.Log.is_group_assignment) @@ -212,6 +216,13 @@ class LogMasterView(WuttaFarmMasterView): return ", ".join([str(a) for a in assets]) + def render_quantities_for_grid(self, log, field, value): + quantities = getattr(log, field) or [] + items = [] + for qty in quantities: + items.append(HTML.tag("li", c=qty.render_as_text(self.config))) + return HTML.tag("ul", c=items) + def render_owners_for_grid(self, log, field, value): if self.farmos_style_grid_links: @@ -377,7 +388,7 @@ class AllLogView(LogMasterView): "log_type", "assets", "locations", - "quantity", + "quantities", "groups", "is_group_assignment", "owners", diff --git a/src/wuttafarm/web/views/logs_harvest.py b/src/wuttafarm/web/views/logs_harvest.py index f2b29e0..e38c6d7 100644 --- a/src/wuttafarm/web/views/logs_harvest.py +++ b/src/wuttafarm/web/views/logs_harvest.py @@ -45,7 +45,7 @@ class HarvestLogView(LogMasterView): "timestamp", "message", "assets", - "quantity", + "quantities", "owners", ]