feat: add schema, import support for Log.quantities

This commit is contained in:
Lance Edgar 2026-02-28 22:06:41 -06:00
parent 1d877545ae
commit 7d2ae48067
7 changed files with 204 additions and 6 deletions

View file

@ -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")

View file

@ -38,7 +38,7 @@ from .asset_structure import StructureType, StructureAsset
from .asset_animal import AnimalType, AnimalAsset from .asset_animal import AnimalType, AnimalAsset
from .asset_group import GroupAsset from .asset_group import GroupAsset
from .asset_plant import PlantType, PlantAsset, PlantAssetPlantType 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_activity import ActivityLog
from .log_harvest import HarvestLog from .log_harvest import HarvestLog
from .log_medical import MedicalLog from .log_medical import MedicalLog

View file

@ -201,6 +201,19 @@ class Log(model.Base):
creator=lambda asset: LogLocation(asset=asset), 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( _owners = orm.relationship(
"LogOwner", "LogOwner",
cascade="all, delete-orphan", cascade="all, delete-orphan",
@ -247,6 +260,7 @@ def add_log_proxies(subclass):
Log.make_proxy(subclass, "log", "assets") Log.make_proxy(subclass, "log", "assets")
Log.make_proxy(subclass, "log", "groups") Log.make_proxy(subclass, "log", "groups")
Log.make_proxy(subclass, "log", "locations") Log.make_proxy(subclass, "log", "locations")
Log.make_proxy(subclass, "log", "quantities")
Log.make_proxy(subclass, "log", "owners") 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): class LogOwner(model.Base):
""" """
Represents a "log's owner relationship" from farmOS. Represents a "log's owner relationship" from farmOS.

View file

@ -982,6 +982,7 @@ class LogImporterBase(FromFarmOS, ToWutta):
"assets", "assets",
"groups", "groups",
"locations", "locations",
"quantities",
"owners", "owners",
] ]
) )
@ -1019,6 +1020,9 @@ class LogImporterBase(FromFarmOS, ToWutta):
for asset in data["locations"] 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: if "owners" in self.fields:
data["owners"] = [UUID(uuid) for uuid in data["owner_uuids"]] 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 (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: if "owners" in self.fields:
data["owners"] = [user.farmos_uuid for user in log.owners] data["owners"] = [user.farmos_uuid for user in log.owners]
@ -1129,6 +1136,31 @@ class LogImporterBase(FromFarmOS, ToWutta):
) )
log.locations.remove(asset) 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 "owners" in self.fields:
if not target_data or target_data["owners"] != source_data["owners"]: if not target_data or target_data["owners"] != source_data["owners"]:

View file

@ -48,7 +48,6 @@ class HarvestLogView(LogMasterView):
"name", "name",
"assets", "assets",
"quantities", "quantities",
"is_group_assignment",
"owners", "owners",
] ]

View file

@ -26,7 +26,7 @@ Base views for Logs
from collections import OrderedDict from collections import OrderedDict
import colander import colander
from webhelpers2.html import tags from webhelpers2.html import tags, HTML
from wuttaweb.forms.schema import WuttaDictEnum from wuttaweb.forms.schema import WuttaDictEnum
from wuttaweb.db import Session from wuttaweb.db import Session
@ -100,6 +100,7 @@ class LogMasterView(WuttaFarmMasterView):
labels = { labels = {
"message": "Log Name", "message": "Log Name",
"locations": "Location", "locations": "Location",
"quantities": "Quantity",
} }
grid_columns = [ grid_columns = [
@ -109,7 +110,7 @@ class LogMasterView(WuttaFarmMasterView):
"message", "message",
"assets", "assets",
"locations", "locations",
"quantity", "quantities",
"is_group_assignment", "is_group_assignment",
"owners", "owners",
] ]
@ -189,6 +190,9 @@ class LogMasterView(WuttaFarmMasterView):
# locations # locations
g.set_renderer("locations", self.render_assets_for_grid) g.set_renderer("locations", self.render_assets_for_grid)
# quantities
g.set_renderer("quantities", self.render_quantities_for_grid)
# is_group_assignment # is_group_assignment
g.set_renderer("is_group_assignment", "boolean") g.set_renderer("is_group_assignment", "boolean")
g.set_sorter("is_group_assignment", model.Log.is_group_assignment) 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]) 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): def render_owners_for_grid(self, log, field, value):
if self.farmos_style_grid_links: if self.farmos_style_grid_links:
@ -377,7 +388,7 @@ class AllLogView(LogMasterView):
"log_type", "log_type",
"assets", "assets",
"locations", "locations",
"quantity", "quantities",
"groups", "groups",
"is_group_assignment", "is_group_assignment",
"owners", "owners",

View file

@ -45,7 +45,7 @@ class HarvestLogView(LogMasterView):
"timestamp", "timestamp",
"message", "message",
"assets", "assets",
"quantity", "quantities",
"owners", "owners",
] ]