feat: add Standard Quantities table, views, import
This commit is contained in:
parent
c93660ec4a
commit
cfe2e4b7b4
13 changed files with 1225 additions and 131 deletions
|
|
@ -0,0 +1,293 @@
|
||||||
|
"""add Standard Quantities
|
||||||
|
|
||||||
|
Revision ID: 5b6c87d8cddf
|
||||||
|
Revises: 1f98d27cabeb
|
||||||
|
Create Date: 2026-02-19 15:42:19.691148
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
import wuttjamaican.db.util
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = "5b6c87d8cddf"
|
||||||
|
down_revision: Union[str, None] = "1f98d27cabeb"
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
|
||||||
|
# measure
|
||||||
|
op.create_table(
|
||||||
|
"measure",
|
||||||
|
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
|
||||||
|
sa.Column("name", sa.String(length=100), nullable=False),
|
||||||
|
sa.Column("drupal_id", sa.String(length=20), nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_measure")),
|
||||||
|
sa.UniqueConstraint("drupal_id", name=op.f("uq_measure_drupal_id")),
|
||||||
|
sa.UniqueConstraint("name", name=op.f("uq_measure_name")),
|
||||||
|
)
|
||||||
|
op.create_table(
|
||||||
|
"measure_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(
|
||||||
|
"drupal_id", sa.String(length=20), 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_measure_version")
|
||||||
|
),
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_measure_version_end_transaction_id"),
|
||||||
|
"measure_version",
|
||||||
|
["end_transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_measure_version_operation_type"),
|
||||||
|
"measure_version",
|
||||||
|
["operation_type"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
"ix_measure_version_pk_transaction_id",
|
||||||
|
"measure_version",
|
||||||
|
["uuid", sa.literal_column("transaction_id DESC")],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
"ix_measure_version_pk_validity",
|
||||||
|
"measure_version",
|
||||||
|
["uuid", "transaction_id", "end_transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_measure_version_transaction_id"),
|
||||||
|
"measure_version",
|
||||||
|
["transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
# quantity
|
||||||
|
op.create_table(
|
||||||
|
"quantity",
|
||||||
|
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
|
||||||
|
sa.Column("quantity_type_id", sa.String(length=50), nullable=False),
|
||||||
|
sa.Column("measure_id", sa.String(length=20), nullable=False),
|
||||||
|
sa.Column("value_numerator", sa.Integer(), nullable=False),
|
||||||
|
sa.Column("value_denominator", sa.Integer(), nullable=False),
|
||||||
|
sa.Column("units_uuid", wuttjamaican.db.util.UUID(), nullable=False),
|
||||||
|
sa.Column("label", 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.ForeignKeyConstraint(
|
||||||
|
["measure_id"],
|
||||||
|
["measure.drupal_id"],
|
||||||
|
name=op.f("fk_quantity_measure_id_measure"),
|
||||||
|
),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["quantity_type_id"],
|
||||||
|
["quantity_type.drupal_id"],
|
||||||
|
name=op.f("fk_quantity_quantity_type_id_quantity_type"),
|
||||||
|
),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["units_uuid"], ["unit.uuid"], name=op.f("fk_quantity_units_uuid_unit")
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_quantity")),
|
||||||
|
sa.UniqueConstraint("drupal_id", name=op.f("uq_quantity_drupal_id")),
|
||||||
|
sa.UniqueConstraint("farmos_uuid", name=op.f("uq_quantity_farmos_uuid")),
|
||||||
|
)
|
||||||
|
op.create_table(
|
||||||
|
"quantity_version",
|
||||||
|
sa.Column(
|
||||||
|
"uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
|
||||||
|
),
|
||||||
|
sa.Column(
|
||||||
|
"quantity_type_id", sa.String(length=50), autoincrement=False, nullable=True
|
||||||
|
),
|
||||||
|
sa.Column(
|
||||||
|
"measure_id", sa.String(length=20), autoincrement=False, nullable=True
|
||||||
|
),
|
||||||
|
sa.Column("value_numerator", sa.Integer(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column(
|
||||||
|
"value_denominator", sa.Integer(), autoincrement=False, nullable=True
|
||||||
|
),
|
||||||
|
sa.Column(
|
||||||
|
"units_uuid",
|
||||||
|
wuttjamaican.db.util.UUID(),
|
||||||
|
autoincrement=False,
|
||||||
|
nullable=True,
|
||||||
|
),
|
||||||
|
sa.Column("label", 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_quantity_version")
|
||||||
|
),
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_quantity_version_end_transaction_id"),
|
||||||
|
"quantity_version",
|
||||||
|
["end_transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_quantity_version_operation_type"),
|
||||||
|
"quantity_version",
|
||||||
|
["operation_type"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
"ix_quantity_version_pk_transaction_id",
|
||||||
|
"quantity_version",
|
||||||
|
["uuid", sa.literal_column("transaction_id DESC")],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
"ix_quantity_version_pk_validity",
|
||||||
|
"quantity_version",
|
||||||
|
["uuid", "transaction_id", "end_transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_quantity_version_transaction_id"),
|
||||||
|
"quantity_version",
|
||||||
|
["transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
# quantity_standard
|
||||||
|
op.create_table(
|
||||||
|
"quantity_standard",
|
||||||
|
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["uuid"], ["quantity.uuid"], name=op.f("fk_quantity_standard_uuid_quantity")
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_quantity_standard")),
|
||||||
|
)
|
||||||
|
op.create_table(
|
||||||
|
"quantity_standard_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_quantity_standard_version")
|
||||||
|
),
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_quantity_standard_version_end_transaction_id"),
|
||||||
|
"quantity_standard_version",
|
||||||
|
["end_transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_quantity_standard_version_operation_type"),
|
||||||
|
"quantity_standard_version",
|
||||||
|
["operation_type"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
"ix_quantity_standard_version_pk_transaction_id",
|
||||||
|
"quantity_standard_version",
|
||||||
|
["uuid", sa.literal_column("transaction_id DESC")],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
"ix_quantity_standard_version_pk_validity",
|
||||||
|
"quantity_standard_version",
|
||||||
|
["uuid", "transaction_id", "end_transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_quantity_standard_version_transaction_id"),
|
||||||
|
"quantity_standard_version",
|
||||||
|
["transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
|
||||||
|
# quantity_standard
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_quantity_standard_version_transaction_id"),
|
||||||
|
table_name="quantity_standard_version",
|
||||||
|
)
|
||||||
|
op.drop_index(
|
||||||
|
"ix_quantity_standard_version_pk_validity",
|
||||||
|
table_name="quantity_standard_version",
|
||||||
|
)
|
||||||
|
op.drop_index(
|
||||||
|
"ix_quantity_standard_version_pk_transaction_id",
|
||||||
|
table_name="quantity_standard_version",
|
||||||
|
)
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_quantity_standard_version_operation_type"),
|
||||||
|
table_name="quantity_standard_version",
|
||||||
|
)
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_quantity_standard_version_end_transaction_id"),
|
||||||
|
table_name="quantity_standard_version",
|
||||||
|
)
|
||||||
|
op.drop_table("quantity_standard_version")
|
||||||
|
op.drop_table("quantity_standard")
|
||||||
|
|
||||||
|
# quantity
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_quantity_version_transaction_id"), table_name="quantity_version"
|
||||||
|
)
|
||||||
|
op.drop_index("ix_quantity_version_pk_validity", table_name="quantity_version")
|
||||||
|
op.drop_index(
|
||||||
|
"ix_quantity_version_pk_transaction_id", table_name="quantity_version"
|
||||||
|
)
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_quantity_version_operation_type"), table_name="quantity_version"
|
||||||
|
)
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_quantity_version_end_transaction_id"), table_name="quantity_version"
|
||||||
|
)
|
||||||
|
op.drop_table("quantity_version")
|
||||||
|
op.drop_table("quantity")
|
||||||
|
|
||||||
|
# measure
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_measure_version_transaction_id"), table_name="measure_version"
|
||||||
|
)
|
||||||
|
op.drop_index("ix_measure_version_pk_validity", table_name="measure_version")
|
||||||
|
op.drop_index("ix_measure_version_pk_transaction_id", table_name="measure_version")
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_measure_version_operation_type"), table_name="measure_version"
|
||||||
|
)
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_measure_version_end_transaction_id"), table_name="measure_version"
|
||||||
|
)
|
||||||
|
op.drop_table("measure_version")
|
||||||
|
op.drop_table("measure")
|
||||||
|
|
@ -30,8 +30,8 @@ from wuttjamaican.db.model import *
|
||||||
from .users import WuttaFarmUser
|
from .users import WuttaFarmUser
|
||||||
|
|
||||||
# wuttafarm proper models
|
# wuttafarm proper models
|
||||||
from .unit import Unit
|
from .unit import Unit, Measure
|
||||||
from .quantities import QuantityType
|
from .quantities import QuantityType, Quantity, StandardQuantity
|
||||||
from .asset import AssetType, Asset, AssetParent
|
from .asset import AssetType, Asset, AssetParent
|
||||||
from .asset_land import LandType, LandAsset
|
from .asset_land import LandType, LandAsset
|
||||||
from .asset_structure import StructureType, StructureAsset
|
from .asset_structure import StructureType, StructureAsset
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ Model definition for Quantities
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import orm
|
||||||
|
from sqlalchemy.ext.declarative import declared_attr
|
||||||
|
|
||||||
from wuttjamaican.db import model
|
from wuttjamaican.db import model
|
||||||
|
|
||||||
|
|
@ -79,3 +81,141 @@ class QuantityType(model.Base):
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name or ""
|
return self.name or ""
|
||||||
|
|
||||||
|
|
||||||
|
class Quantity(model.Base):
|
||||||
|
"""
|
||||||
|
Represents a base quantity record from farmOS
|
||||||
|
"""
|
||||||
|
|
||||||
|
__tablename__ = "quantity"
|
||||||
|
__versioned__ = {}
|
||||||
|
__wutta_hint__ = {
|
||||||
|
"model_title": "Quantity",
|
||||||
|
"model_title_plural": "All Quantities",
|
||||||
|
}
|
||||||
|
|
||||||
|
uuid = model.uuid_column()
|
||||||
|
|
||||||
|
quantity_type_id = sa.Column(
|
||||||
|
sa.String(length=50),
|
||||||
|
sa.ForeignKey("quantity_type.drupal_id"),
|
||||||
|
nullable=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
quantity_type = orm.relationship(QuantityType)
|
||||||
|
|
||||||
|
measure_id = sa.Column(
|
||||||
|
sa.String(length=20),
|
||||||
|
sa.ForeignKey("measure.drupal_id"),
|
||||||
|
nullable=False,
|
||||||
|
doc="""
|
||||||
|
Measure for the quantity.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
measure = orm.relationship("Measure")
|
||||||
|
|
||||||
|
value_numerator = sa.Column(
|
||||||
|
sa.Integer(),
|
||||||
|
nullable=False,
|
||||||
|
doc="""
|
||||||
|
Numerator for the quantity value.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
value_denominator = sa.Column(
|
||||||
|
sa.Integer(),
|
||||||
|
nullable=False,
|
||||||
|
doc="""
|
||||||
|
Denominator for the quantity value.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
units_uuid = model.uuid_fk_column("unit.uuid", nullable=False)
|
||||||
|
units = orm.relationship("Unit")
|
||||||
|
|
||||||
|
label = sa.Column(
|
||||||
|
sa.String(length=255),
|
||||||
|
nullable=True,
|
||||||
|
doc="""
|
||||||
|
Optional label for the quantity.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
farmos_uuid = sa.Column(
|
||||||
|
model.UUID(),
|
||||||
|
nullable=True,
|
||||||
|
unique=True,
|
||||||
|
doc="""
|
||||||
|
UUID for the quantity within farmOS.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
drupal_id = sa.Column(
|
||||||
|
sa.Integer(),
|
||||||
|
nullable=True,
|
||||||
|
unique=True,
|
||||||
|
doc="""
|
||||||
|
Drupal internal ID for the quantity.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
def render_as_text(self, config=None):
|
||||||
|
measure = str(self.measure or self.measure_id or "")
|
||||||
|
value = self.value_numerator / self.value_denominator
|
||||||
|
if config:
|
||||||
|
app = config.get_app()
|
||||||
|
value = app.render_quantity(value)
|
||||||
|
units = str(self.units or "")
|
||||||
|
return f"( {measure} ) {value} {units}"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.render_as_text()
|
||||||
|
|
||||||
|
|
||||||
|
class QuantityMixin:
|
||||||
|
|
||||||
|
uuid = model.uuid_fk_column("quantity.uuid", nullable=False, primary_key=True)
|
||||||
|
|
||||||
|
@declared_attr
|
||||||
|
def quantity(cls):
|
||||||
|
return orm.relationship(Quantity)
|
||||||
|
|
||||||
|
def render_as_text(self, config=None):
|
||||||
|
return self.quantity.render_as_text(config)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.render_as_text()
|
||||||
|
|
||||||
|
|
||||||
|
def add_quantity_proxies(subclass):
|
||||||
|
Quantity.make_proxy(subclass, "quantity", "farmos_uuid")
|
||||||
|
Quantity.make_proxy(subclass, "quantity", "drupal_id")
|
||||||
|
Quantity.make_proxy(subclass, "quantity", "quantity_type")
|
||||||
|
Quantity.make_proxy(subclass, "quantity", "quantity_type_id")
|
||||||
|
Quantity.make_proxy(subclass, "quantity", "measure")
|
||||||
|
Quantity.make_proxy(subclass, "quantity", "measure_id")
|
||||||
|
Quantity.make_proxy(subclass, "quantity", "value_numerator")
|
||||||
|
Quantity.make_proxy(subclass, "quantity", "value_denominator")
|
||||||
|
Quantity.make_proxy(subclass, "quantity", "value_decimal")
|
||||||
|
Quantity.make_proxy(subclass, "quantity", "units_uuid")
|
||||||
|
Quantity.make_proxy(subclass, "quantity", "units")
|
||||||
|
Quantity.make_proxy(subclass, "quantity", "label")
|
||||||
|
|
||||||
|
|
||||||
|
class StandardQuantity(QuantityMixin, model.Base):
|
||||||
|
"""
|
||||||
|
Represents a Standard Quantity from farmOS
|
||||||
|
"""
|
||||||
|
|
||||||
|
__tablename__ = "quantity_standard"
|
||||||
|
__versioned__ = {}
|
||||||
|
__wutta_hint__ = {
|
||||||
|
"model_title": "Standard Quantity",
|
||||||
|
"model_title_plural": "Standard Quantities",
|
||||||
|
"farmos_quantity_type": "standard",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
add_quantity_proxies(StandardQuantity)
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,42 @@ import sqlalchemy as sa
|
||||||
from wuttjamaican.db import model
|
from wuttjamaican.db import model
|
||||||
|
|
||||||
|
|
||||||
|
class Measure(model.Base):
|
||||||
|
"""
|
||||||
|
Represents a "measure" option (for quantities) from farmOS
|
||||||
|
"""
|
||||||
|
|
||||||
|
__tablename__ = "measure"
|
||||||
|
__versioned__ = {}
|
||||||
|
__wutta_hint__ = {
|
||||||
|
"model_title": "Measure",
|
||||||
|
"model_title_plural": "Measures",
|
||||||
|
}
|
||||||
|
|
||||||
|
uuid = model.uuid_column()
|
||||||
|
|
||||||
|
name = sa.Column(
|
||||||
|
sa.String(length=100),
|
||||||
|
nullable=False,
|
||||||
|
unique=True,
|
||||||
|
doc="""
|
||||||
|
Name of the measure.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
drupal_id = sa.Column(
|
||||||
|
sa.String(length=20),
|
||||||
|
nullable=True,
|
||||||
|
unique=True,
|
||||||
|
doc="""
|
||||||
|
Drupal internal ID for the measure.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name or ""
|
||||||
|
|
||||||
|
|
||||||
class Unit(model.Base):
|
class Unit(model.Base):
|
||||||
"""
|
"""
|
||||||
Represents an "unit" (taxonomy term) from farmOS
|
Represents an "unit" (taxonomy term) from farmOS
|
||||||
|
|
|
||||||
|
|
@ -106,8 +106,10 @@ class FromFarmOSToWuttaFarm(FromFarmOSHandler, ToWuttaFarmHandler):
|
||||||
importers["GroupAsset"] = GroupAssetImporter
|
importers["GroupAsset"] = GroupAssetImporter
|
||||||
importers["PlantType"] = PlantTypeImporter
|
importers["PlantType"] = PlantTypeImporter
|
||||||
importers["PlantAsset"] = PlantAssetImporter
|
importers["PlantAsset"] = PlantAssetImporter
|
||||||
|
importers["Measure"] = MeasureImporter
|
||||||
importers["Unit"] = UnitImporter
|
importers["Unit"] = UnitImporter
|
||||||
importers["QuantityType"] = QuantityTypeImporter
|
importers["QuantityType"] = QuantityTypeImporter
|
||||||
|
importers["StandardQuantity"] = StandardQuantityImporter
|
||||||
importers["LogType"] = LogTypeImporter
|
importers["LogType"] = LogTypeImporter
|
||||||
importers["ActivityLog"] = ActivityLogImporter
|
importers["ActivityLog"] = ActivityLogImporter
|
||||||
importers["HarvestLog"] = HarvestLogImporter
|
importers["HarvestLog"] = HarvestLogImporter
|
||||||
|
|
@ -823,6 +825,37 @@ class UserImporter(FromFarmOS, ToWutta):
|
||||||
##############################
|
##############################
|
||||||
|
|
||||||
|
|
||||||
|
class MeasureImporter(FromFarmOS, ToWutta):
|
||||||
|
"""
|
||||||
|
farmOS API → WuttaFarm importer for Measures
|
||||||
|
"""
|
||||||
|
|
||||||
|
model_class = model.Measure
|
||||||
|
|
||||||
|
key = "drupal_id"
|
||||||
|
|
||||||
|
supported_fields = [
|
||||||
|
"drupal_id",
|
||||||
|
"name",
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_source_objects(self):
|
||||||
|
""" """
|
||||||
|
response = self.farmos_client.session.get(
|
||||||
|
self.app.get_farmos_url("/api/quantity/standard/resource/schema")
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
return data["definitions"]["attributes"]["properties"]["measure"]["oneOf"]
|
||||||
|
|
||||||
|
def normalize_source_object(self, measure):
|
||||||
|
""" """
|
||||||
|
return {
|
||||||
|
"drupal_id": measure["const"],
|
||||||
|
"name": measure["title"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class UnitImporter(FromFarmOS, ToWutta):
|
class UnitImporter(FromFarmOS, ToWutta):
|
||||||
"""
|
"""
|
||||||
farmOS API → WuttaFarm importer for Units
|
farmOS API → WuttaFarm importer for Units
|
||||||
|
|
@ -1100,3 +1133,114 @@ class ObservationLogImporter(LogImporterBase):
|
||||||
"status",
|
"status",
|
||||||
"assets",
|
"assets",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class QuantityImporterBase(FromFarmOS, ToWutta):
|
||||||
|
"""
|
||||||
|
Base class for farmOS API → WuttaFarm quantity importers
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_farmos_quantity_type(self):
|
||||||
|
return self.model_class.__wutta_hint__["farmos_quantity_type"]
|
||||||
|
|
||||||
|
def get_simple_fields(self):
|
||||||
|
""" """
|
||||||
|
fields = list(super().get_simple_fields())
|
||||||
|
# nb. must explicitly declare proxy fields
|
||||||
|
fields.extend(
|
||||||
|
[
|
||||||
|
"farmos_uuid",
|
||||||
|
"drupal_id",
|
||||||
|
"quantity_type_id",
|
||||||
|
"measure_id",
|
||||||
|
"value_numerator",
|
||||||
|
"value_denominator",
|
||||||
|
"units_uuid",
|
||||||
|
"label",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
return fields
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
super().setup()
|
||||||
|
model = self.app.model
|
||||||
|
|
||||||
|
self.quantity_types_by_farmos_uuid = {}
|
||||||
|
for quantity_type in self.target_session.query(model.QuantityType):
|
||||||
|
if quantity_type.farmos_uuid:
|
||||||
|
self.quantity_types_by_farmos_uuid[quantity_type.farmos_uuid] = (
|
||||||
|
quantity_type
|
||||||
|
)
|
||||||
|
|
||||||
|
self.units_by_farmos_uuid = {}
|
||||||
|
for unit in self.target_session.query(model.Unit):
|
||||||
|
if unit.farmos_uuid:
|
||||||
|
self.units_by_farmos_uuid[unit.farmos_uuid] = unit
|
||||||
|
|
||||||
|
def get_source_objects(self):
|
||||||
|
""" """
|
||||||
|
quantity_type = self.get_farmos_quantity_type()
|
||||||
|
result = self.farmos_client.resource.get("quantity", quantity_type)
|
||||||
|
return result["data"]
|
||||||
|
|
||||||
|
def normalize_source_object(self, quantity):
|
||||||
|
""" """
|
||||||
|
quantity_type_id = None
|
||||||
|
units_uuid = None
|
||||||
|
if relationships := quantity.get("relationships"):
|
||||||
|
|
||||||
|
if quantity_type := relationships.get("quantity_type"):
|
||||||
|
if quantity_type["data"]:
|
||||||
|
if wf_quantity_type := self.quantity_types_by_farmos_uuid.get(
|
||||||
|
UUID(quantity_type["data"]["id"])
|
||||||
|
):
|
||||||
|
quantity_type_id = wf_quantity_type.drupal_id
|
||||||
|
|
||||||
|
if units := relationships.get("units"):
|
||||||
|
if units["data"]:
|
||||||
|
if wf_unit := self.units_by_farmos_uuid.get(
|
||||||
|
UUID(units["data"]["id"])
|
||||||
|
):
|
||||||
|
units_uuid = wf_unit.uuid
|
||||||
|
|
||||||
|
if not quantity_type_id:
|
||||||
|
log.warning(
|
||||||
|
"missing/invalid quantity_type for farmOS Quantity: %s", quantity
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
if not units_uuid:
|
||||||
|
log.warning("missing/invalid units for farmOS Quantity: %s", quantity)
|
||||||
|
return None
|
||||||
|
|
||||||
|
value = quantity["attributes"]["value"]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"farmos_uuid": UUID(quantity["id"]),
|
||||||
|
"drupal_id": quantity["attributes"]["drupal_internal__id"],
|
||||||
|
"quantity_type_id": quantity_type_id,
|
||||||
|
"measure_id": quantity["attributes"]["measure"],
|
||||||
|
"value_numerator": value["numerator"],
|
||||||
|
"value_denominator": value["denominator"],
|
||||||
|
"units_uuid": units_uuid,
|
||||||
|
"label": quantity["attributes"]["label"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class StandardQuantityImporter(QuantityImporterBase):
|
||||||
|
"""
|
||||||
|
farmOS API → WuttaFarm importer for Standard Quantities
|
||||||
|
"""
|
||||||
|
|
||||||
|
model_class = model.StandardQuantity
|
||||||
|
|
||||||
|
supported_fields = [
|
||||||
|
"farmos_uuid",
|
||||||
|
"drupal_id",
|
||||||
|
"quantity_type_id",
|
||||||
|
"measure_id",
|
||||||
|
"value_numerator",
|
||||||
|
"value_denominator",
|
||||||
|
"units_uuid",
|
||||||
|
"label",
|
||||||
|
]
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,26 @@ class AnimalTypeRef(ObjectRef):
|
||||||
return self.request.route_url("animal_types.view", uuid=animal_type.uuid)
|
return self.request.route_url("animal_types.view", uuid=animal_type.uuid)
|
||||||
|
|
||||||
|
|
||||||
|
class FarmOSRef(colander.SchemaType):
|
||||||
|
|
||||||
|
def __init__(self, request, route_prefix, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.request = request
|
||||||
|
self.route_prefix = route_prefix
|
||||||
|
|
||||||
|
def serialize(self, node, appstruct):
|
||||||
|
if appstruct is colander.null:
|
||||||
|
return colander.null
|
||||||
|
|
||||||
|
return json.dumps(appstruct)
|
||||||
|
|
||||||
|
def widget_maker(self, **kwargs):
|
||||||
|
""" """
|
||||||
|
from wuttafarm.web.forms.widgets import FarmOSRefWidget
|
||||||
|
|
||||||
|
return FarmOSRefWidget(self.request, self.route_prefix, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class AnimalTypeType(colander.SchemaType):
|
class AnimalTypeType(colander.SchemaType):
|
||||||
|
|
||||||
def __init__(self, request, *args, **kwargs):
|
def __init__(self, request, *args, **kwargs):
|
||||||
|
|
@ -179,6 +199,27 @@ class StructureTypeRef(ObjectRef):
|
||||||
return self.request.route_url("structure_types.view", uuid=structure_type.uuid)
|
return self.request.route_url("structure_types.view", uuid=structure_type.uuid)
|
||||||
|
|
||||||
|
|
||||||
|
class UnitRef(ObjectRef):
|
||||||
|
"""
|
||||||
|
Custom schema type for a :class:`~wuttafarm.db.model.units.Unit`
|
||||||
|
reference field.
|
||||||
|
|
||||||
|
This is a subclass of
|
||||||
|
:class:`~wuttaweb:wuttaweb.forms.schema.ObjectRef`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def model_class(self):
|
||||||
|
model = self.app.model
|
||||||
|
return model.Unit
|
||||||
|
|
||||||
|
def sort_query(self, query):
|
||||||
|
return query.order_by(self.model_class.name)
|
||||||
|
|
||||||
|
def get_object_url(self, unit):
|
||||||
|
return self.request.route_url("units.view", uuid=unit.uuid)
|
||||||
|
|
||||||
|
|
||||||
class UsersType(colander.SchemaType):
|
class UsersType(colander.SchemaType):
|
||||||
|
|
||||||
def __init__(self, request, *args, **kwargs):
|
def __init__(self, request, *args, **kwargs):
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,33 @@ class ImageWidget(Widget):
|
||||||
return super().serialize(field, cstruct, **kw)
|
return super().serialize(field, cstruct, **kw)
|
||||||
|
|
||||||
|
|
||||||
|
class FarmOSRefWidget(Widget):
|
||||||
|
"""
|
||||||
|
Generic widget to display "any reference field" - as a link to
|
||||||
|
view the farmOS record it references. Only used by the farmOS
|
||||||
|
direct API views.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, request, route_prefix, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.request = request
|
||||||
|
self.route_prefix = route_prefix
|
||||||
|
|
||||||
|
def serialize(self, field, cstruct, **kw):
|
||||||
|
readonly = kw.get("readonly", self.readonly)
|
||||||
|
if readonly:
|
||||||
|
if cstruct in (colander.null, None):
|
||||||
|
return HTML.tag("span")
|
||||||
|
|
||||||
|
obj = json.loads(cstruct)
|
||||||
|
return tags.link_to(
|
||||||
|
obj["name"],
|
||||||
|
self.request.route_url(f"{self.route_prefix}.view", uuid=obj["uuid"]),
|
||||||
|
)
|
||||||
|
|
||||||
|
return super().serialize(field, cstruct, **kw)
|
||||||
|
|
||||||
|
|
||||||
class AnimalTypeWidget(Widget):
|
class AnimalTypeWidget(Widget):
|
||||||
"""
|
"""
|
||||||
Widget to display an "animal type" field.
|
Widget to display an "animal type" field.
|
||||||
|
|
|
||||||
|
|
@ -134,11 +134,27 @@ class WuttaFarmMenuHandler(base.MenuHandler):
|
||||||
"perm": "logs_observation.list",
|
"perm": "logs_observation.list",
|
||||||
},
|
},
|
||||||
{"type": "sep"},
|
{"type": "sep"},
|
||||||
|
{
|
||||||
|
"title": "All Quantities",
|
||||||
|
"route": "quantities",
|
||||||
|
"perm": "quantities.list",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Standard Quantities",
|
||||||
|
"route": "quantities_standard",
|
||||||
|
"perm": "quantities_standard.list",
|
||||||
|
},
|
||||||
|
{"type": "sep"},
|
||||||
{
|
{
|
||||||
"title": "Log Types",
|
"title": "Log Types",
|
||||||
"route": "log_types",
|
"route": "log_types",
|
||||||
"perm": "log_types.list",
|
"perm": "log_types.list",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": "Measures",
|
||||||
|
"route": "measures",
|
||||||
|
"perm": "measures.list",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": "Quantity Types",
|
"title": "Quantity Types",
|
||||||
"route": "quantity_types",
|
"route": "quantity_types",
|
||||||
|
|
@ -248,6 +264,11 @@ class WuttaFarmMenuHandler(base.MenuHandler):
|
||||||
"route": "farmos_quantity_types",
|
"route": "farmos_quantity_types",
|
||||||
"perm": "farmos_quantity_types.list",
|
"perm": "farmos_quantity_types.list",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": "Standard Quantities",
|
||||||
|
"route": "farmos_quantities_standard",
|
||||||
|
"perm": "farmos_quantities_standard.list",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": "Units",
|
"title": "Units",
|
||||||
"route": "farmos_units",
|
"route": "farmos_units",
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ from .master import FarmOSMasterView
|
||||||
|
|
||||||
def includeme(config):
|
def includeme(config):
|
||||||
config.include("wuttafarm.web.views.farmos.users")
|
config.include("wuttafarm.web.views.farmos.users")
|
||||||
config.include("wuttafarm.web.views.farmos.quantity_types")
|
config.include("wuttafarm.web.views.farmos.quantities")
|
||||||
config.include("wuttafarm.web.views.farmos.asset_types")
|
config.include("wuttafarm.web.views.farmos.asset_types")
|
||||||
config.include("wuttafarm.web.views.farmos.units")
|
config.include("wuttafarm.web.views.farmos.units")
|
||||||
config.include("wuttafarm.web.views.farmos.land_types")
|
config.include("wuttafarm.web.views.farmos.land_types")
|
||||||
|
|
|
||||||
278
src/wuttafarm/web/views/farmos/quantities.py
Normal file
278
src/wuttafarm/web/views/farmos/quantities.py
Normal file
|
|
@ -0,0 +1,278 @@
|
||||||
|
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
View for farmOS Quantity Types
|
||||||
|
"""
|
||||||
|
|
||||||
|
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 FarmOSRef
|
||||||
|
|
||||||
|
|
||||||
|
class QuantityTypeView(FarmOSMasterView):
|
||||||
|
"""
|
||||||
|
View for farmOS Quantity Types
|
||||||
|
"""
|
||||||
|
|
||||||
|
model_name = "farmos_quantity_type"
|
||||||
|
model_title = "farmOS Quantity Type"
|
||||||
|
model_title_plural = "farmOS Quantity Types"
|
||||||
|
|
||||||
|
route_prefix = "farmos_quantity_types"
|
||||||
|
url_prefix = "/farmOS/quantity-types"
|
||||||
|
|
||||||
|
grid_columns = [
|
||||||
|
"label",
|
||||||
|
"description",
|
||||||
|
]
|
||||||
|
|
||||||
|
sort_defaults = "label"
|
||||||
|
|
||||||
|
form_fields = [
|
||||||
|
"label",
|
||||||
|
"description",
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_grid_data(self, columns=None, session=None):
|
||||||
|
result = self.farmos_client.resource.get("quantity_type")
|
||||||
|
return [self.normalize_quantity_type(t) for t in result["data"]]
|
||||||
|
|
||||||
|
def configure_grid(self, grid):
|
||||||
|
g = grid
|
||||||
|
super().configure_grid(g)
|
||||||
|
|
||||||
|
# label
|
||||||
|
g.set_link("label")
|
||||||
|
g.set_searchable("label")
|
||||||
|
|
||||||
|
# description
|
||||||
|
g.set_searchable("description")
|
||||||
|
|
||||||
|
def get_instance(self):
|
||||||
|
result = self.farmos_client.resource.get_id(
|
||||||
|
"quantity_type", "quantity_type", self.request.matchdict["uuid"]
|
||||||
|
)
|
||||||
|
self.raw_json = result
|
||||||
|
return self.normalize_quantity_type(result["data"])
|
||||||
|
|
||||||
|
def get_instance_title(self, quantity_type):
|
||||||
|
return quantity_type["label"]
|
||||||
|
|
||||||
|
def normalize_quantity_type(self, quantity_type):
|
||||||
|
return {
|
||||||
|
"uuid": quantity_type["id"],
|
||||||
|
"drupal_id": quantity_type["attributes"]["drupal_internal__id"],
|
||||||
|
"label": quantity_type["attributes"]["label"],
|
||||||
|
"description": quantity_type["attributes"]["description"],
|
||||||
|
}
|
||||||
|
|
||||||
|
def configure_form(self, form):
|
||||||
|
f = form
|
||||||
|
super().configure_form(f)
|
||||||
|
|
||||||
|
# description
|
||||||
|
f.set_widget("description", "notes")
|
||||||
|
|
||||||
|
def get_xref_buttons(self, quantity_type):
|
||||||
|
model = self.app.model
|
||||||
|
session = self.Session()
|
||||||
|
buttons = []
|
||||||
|
|
||||||
|
if wf_quantity_type := (
|
||||||
|
session.query(model.QuantityType)
|
||||||
|
.filter(model.QuantityType.farmos_uuid == quantity_type["uuid"])
|
||||||
|
.first()
|
||||||
|
):
|
||||||
|
buttons.append(
|
||||||
|
self.make_button(
|
||||||
|
f"View {self.app.get_title()} record",
|
||||||
|
primary=True,
|
||||||
|
url=self.request.route_url(
|
||||||
|
"quantity_types.view", uuid=wf_quantity_type.uuid
|
||||||
|
),
|
||||||
|
icon_left="eye",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return buttons
|
||||||
|
|
||||||
|
|
||||||
|
class QuantityMasterView(FarmOSMasterView):
|
||||||
|
"""
|
||||||
|
Base class for Quantity views
|
||||||
|
"""
|
||||||
|
|
||||||
|
farmos_quantity_type = None
|
||||||
|
|
||||||
|
grid_columns = [
|
||||||
|
"measure",
|
||||||
|
"value",
|
||||||
|
"label",
|
||||||
|
"changed",
|
||||||
|
]
|
||||||
|
|
||||||
|
sort_defaults = ("changed", "desc")
|
||||||
|
|
||||||
|
form_fields = [
|
||||||
|
"measure",
|
||||||
|
"value",
|
||||||
|
"units",
|
||||||
|
"label",
|
||||||
|
"created",
|
||||||
|
"changed",
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_grid_data(self, columns=None, session=None):
|
||||||
|
result = self.farmos_client.resource.get("quantity", self.farmos_quantity_type)
|
||||||
|
return [self.normalize_quantity(t) for t in result["data"]]
|
||||||
|
|
||||||
|
def configure_grid(self, grid):
|
||||||
|
g = grid
|
||||||
|
super().configure_grid(g)
|
||||||
|
|
||||||
|
# value
|
||||||
|
g.set_link("value")
|
||||||
|
|
||||||
|
# changed
|
||||||
|
g.set_renderer("changed", "datetime")
|
||||||
|
|
||||||
|
def get_instance(self):
|
||||||
|
quantity = self.farmos_client.resource.get_id(
|
||||||
|
"quantity", self.farmos_quantity_type, self.request.matchdict["uuid"]
|
||||||
|
)
|
||||||
|
self.raw_json = quantity
|
||||||
|
|
||||||
|
data = self.normalize_quantity(quantity["data"])
|
||||||
|
|
||||||
|
if relationships := quantity["data"].get("relationships"):
|
||||||
|
|
||||||
|
# add units
|
||||||
|
if units := relationships.get("units"):
|
||||||
|
if units["data"]:
|
||||||
|
unit = self.farmos_client.resource.get_id(
|
||||||
|
"taxonomy_term", "unit", units["data"]["id"]
|
||||||
|
)
|
||||||
|
data["units"] = {
|
||||||
|
"uuid": unit["data"]["id"],
|
||||||
|
"name": unit["data"]["attributes"]["name"],
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def get_instance_title(self, quantity):
|
||||||
|
return quantity["value"]
|
||||||
|
|
||||||
|
def normalize_quantity(self, quantity):
|
||||||
|
|
||||||
|
if created := quantity["attributes"]["created"]:
|
||||||
|
created = datetime.datetime.fromisoformat(created)
|
||||||
|
created = self.app.localtime(created)
|
||||||
|
|
||||||
|
if changed := quantity["attributes"]["changed"]:
|
||||||
|
changed = datetime.datetime.fromisoformat(changed)
|
||||||
|
changed = self.app.localtime(changed)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"uuid": quantity["id"],
|
||||||
|
"drupal_id": quantity["attributes"]["drupal_internal__id"],
|
||||||
|
"measure": quantity["attributes"]["measure"],
|
||||||
|
"value": quantity["attributes"]["value"],
|
||||||
|
"label": quantity["attributes"]["label"] or colander.null,
|
||||||
|
"created": created,
|
||||||
|
"changed": changed,
|
||||||
|
}
|
||||||
|
|
||||||
|
def configure_form(self, form):
|
||||||
|
f = form
|
||||||
|
super().configure_form(f)
|
||||||
|
|
||||||
|
# created
|
||||||
|
f.set_node("created", WuttaDateTime(self.request))
|
||||||
|
f.set_widget("created", WuttaDateTimeWidget(self.request))
|
||||||
|
|
||||||
|
# changed
|
||||||
|
f.set_node("changed", WuttaDateTime(self.request))
|
||||||
|
f.set_widget("changed", WuttaDateTimeWidget(self.request))
|
||||||
|
|
||||||
|
# units
|
||||||
|
f.set_node("units", FarmOSRef(self.request, "farmos_units"))
|
||||||
|
|
||||||
|
|
||||||
|
class StandardQuantityView(QuantityMasterView):
|
||||||
|
"""
|
||||||
|
View for farmOS Standard Quantities
|
||||||
|
"""
|
||||||
|
|
||||||
|
model_name = "farmos_standard_quantity"
|
||||||
|
model_title = "farmOS Standard Quantity"
|
||||||
|
model_title_plural = "farmOS Standard Quantities"
|
||||||
|
|
||||||
|
route_prefix = "farmos_quantities_standard"
|
||||||
|
url_prefix = "/farmOS/quantities/standard"
|
||||||
|
|
||||||
|
farmos_quantity_type = "standard"
|
||||||
|
|
||||||
|
def get_xref_buttons(self, standard_quantity):
|
||||||
|
model = self.app.model
|
||||||
|
session = self.Session()
|
||||||
|
buttons = []
|
||||||
|
|
||||||
|
if wf_standard_quantity := (
|
||||||
|
session.query(model.StandardQuantity)
|
||||||
|
.join(model.Quantity)
|
||||||
|
.filter(model.Quantity.farmos_uuid == standard_quantity["uuid"])
|
||||||
|
.first()
|
||||||
|
):
|
||||||
|
buttons.append(
|
||||||
|
self.make_button(
|
||||||
|
f"View {self.app.get_title()} record",
|
||||||
|
primary=True,
|
||||||
|
url=self.request.route_url(
|
||||||
|
"quantities_standard.view", uuid=wf_standard_quantity.uuid
|
||||||
|
),
|
||||||
|
icon_left="eye",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return buttons
|
||||||
|
|
||||||
|
|
||||||
|
def defaults(config, **kwargs):
|
||||||
|
base = globals()
|
||||||
|
|
||||||
|
QuantityTypeView = kwargs.get("QuantityTypeView", base["QuantityTypeView"])
|
||||||
|
QuantityTypeView.defaults(config)
|
||||||
|
|
||||||
|
StandardQuantityView = kwargs.get(
|
||||||
|
"StandardQuantityView", base["StandardQuantityView"]
|
||||||
|
)
|
||||||
|
StandardQuantityView.defaults(config)
|
||||||
|
|
||||||
|
|
||||||
|
def includeme(config):
|
||||||
|
defaults(config)
|
||||||
|
|
@ -1,125 +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 <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
################################################################################
|
|
||||||
"""
|
|
||||||
View for farmOS Quantity Types
|
|
||||||
"""
|
|
||||||
|
|
||||||
from wuttafarm.web.views.farmos import FarmOSMasterView
|
|
||||||
|
|
||||||
|
|
||||||
class QuantityTypeView(FarmOSMasterView):
|
|
||||||
"""
|
|
||||||
View for farmOS Quantity Types
|
|
||||||
"""
|
|
||||||
|
|
||||||
model_name = "farmos_quantity_type"
|
|
||||||
model_title = "farmOS Quantity Type"
|
|
||||||
model_title_plural = "farmOS Quantity Types"
|
|
||||||
|
|
||||||
route_prefix = "farmos_quantity_types"
|
|
||||||
url_prefix = "/farmOS/quantity-types"
|
|
||||||
|
|
||||||
grid_columns = [
|
|
||||||
"label",
|
|
||||||
"description",
|
|
||||||
]
|
|
||||||
|
|
||||||
sort_defaults = "label"
|
|
||||||
|
|
||||||
form_fields = [
|
|
||||||
"label",
|
|
||||||
"description",
|
|
||||||
]
|
|
||||||
|
|
||||||
def get_grid_data(self, columns=None, session=None):
|
|
||||||
result = self.farmos_client.resource.get("quantity_type")
|
|
||||||
return [self.normalize_quantity_type(t) for t in result["data"]]
|
|
||||||
|
|
||||||
def configure_grid(self, grid):
|
|
||||||
g = grid
|
|
||||||
super().configure_grid(g)
|
|
||||||
|
|
||||||
# label
|
|
||||||
g.set_link("label")
|
|
||||||
g.set_searchable("label")
|
|
||||||
|
|
||||||
# description
|
|
||||||
g.set_searchable("description")
|
|
||||||
|
|
||||||
def get_instance(self):
|
|
||||||
result = self.farmos_client.resource.get_id(
|
|
||||||
"quantity_type", "quantity_type", self.request.matchdict["uuid"]
|
|
||||||
)
|
|
||||||
self.raw_json = result
|
|
||||||
return self.normalize_quantity_type(result["data"])
|
|
||||||
|
|
||||||
def get_instance_title(self, quantity_type):
|
|
||||||
return quantity_type["label"]
|
|
||||||
|
|
||||||
def normalize_quantity_type(self, quantity_type):
|
|
||||||
return {
|
|
||||||
"uuid": quantity_type["id"],
|
|
||||||
"drupal_id": quantity_type["attributes"]["drupal_internal__id"],
|
|
||||||
"label": quantity_type["attributes"]["label"],
|
|
||||||
"description": quantity_type["attributes"]["description"],
|
|
||||||
}
|
|
||||||
|
|
||||||
def configure_form(self, form):
|
|
||||||
f = form
|
|
||||||
super().configure_form(f)
|
|
||||||
|
|
||||||
# description
|
|
||||||
f.set_widget("description", "notes")
|
|
||||||
|
|
||||||
def get_xref_buttons(self, quantity_type):
|
|
||||||
model = self.app.model
|
|
||||||
session = self.Session()
|
|
||||||
buttons = []
|
|
||||||
|
|
||||||
if wf_quantity_type := (
|
|
||||||
session.query(model.QuantityType)
|
|
||||||
.filter(model.QuantityType.farmos_uuid == quantity_type["uuid"])
|
|
||||||
.first()
|
|
||||||
):
|
|
||||||
buttons.append(
|
|
||||||
self.make_button(
|
|
||||||
f"View {self.app.get_title()} record",
|
|
||||||
primary=True,
|
|
||||||
url=self.request.route_url(
|
|
||||||
"quantity_types.view", uuid=wf_quantity_type.uuid
|
|
||||||
),
|
|
||||||
icon_left="eye",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return buttons
|
|
||||||
|
|
||||||
|
|
||||||
def defaults(config, **kwargs):
|
|
||||||
base = globals()
|
|
||||||
|
|
||||||
QuantityTypeView = kwargs.get("QuantityTypeView", base["QuantityTypeView"])
|
|
||||||
QuantityTypeView.defaults(config)
|
|
||||||
|
|
||||||
|
|
||||||
def includeme(config):
|
|
||||||
defaults(config)
|
|
||||||
|
|
@ -23,8 +23,24 @@
|
||||||
Master view for Quantities
|
Master view for Quantities
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
from wuttaweb.db import Session
|
||||||
|
|
||||||
from wuttafarm.web.views import WuttaFarmMasterView
|
from wuttafarm.web.views import WuttaFarmMasterView
|
||||||
from wuttafarm.db.model import QuantityType
|
from wuttafarm.db.model import QuantityType, Quantity, StandardQuantity
|
||||||
|
from wuttafarm.web.forms.schema import UnitRef
|
||||||
|
|
||||||
|
|
||||||
|
def get_quantity_type_enum(config):
|
||||||
|
app = config.get_app()
|
||||||
|
model = app.model
|
||||||
|
session = Session()
|
||||||
|
quantity_types = OrderedDict()
|
||||||
|
query = session.query(model.QuantityType).order_by(model.QuantityType.name)
|
||||||
|
for quantity_type in query:
|
||||||
|
quantity_types[quantity_type.drupal_id] = quantity_type.name
|
||||||
|
return quantity_types
|
||||||
|
|
||||||
|
|
||||||
class QuantityTypeView(WuttaFarmMasterView):
|
class QuantityTypeView(WuttaFarmMasterView):
|
||||||
|
|
@ -79,12 +95,199 @@ class QuantityTypeView(WuttaFarmMasterView):
|
||||||
return buttons
|
return buttons
|
||||||
|
|
||||||
|
|
||||||
|
class QuantityMasterView(WuttaFarmMasterView):
|
||||||
|
"""
|
||||||
|
Base class for Quantity master views
|
||||||
|
"""
|
||||||
|
|
||||||
|
grid_columns = [
|
||||||
|
"drupal_id",
|
||||||
|
"as_text",
|
||||||
|
"quantity_type",
|
||||||
|
"measure",
|
||||||
|
"value",
|
||||||
|
"units",
|
||||||
|
"label",
|
||||||
|
]
|
||||||
|
|
||||||
|
sort_defaults = ("drupal_id", "desc")
|
||||||
|
|
||||||
|
form_fields = [
|
||||||
|
"quantity_type",
|
||||||
|
"as_text",
|
||||||
|
"measure",
|
||||||
|
"value",
|
||||||
|
"units",
|
||||||
|
"label",
|
||||||
|
"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()
|
||||||
|
query = session.query(model_class)
|
||||||
|
if model_class is not model.Quantity:
|
||||||
|
query = query.join(model.Quantity)
|
||||||
|
query = query.join(model.Measure).join(model.Unit)
|
||||||
|
return query
|
||||||
|
|
||||||
|
def configure_grid(self, grid):
|
||||||
|
g = grid
|
||||||
|
super().configure_grid(g)
|
||||||
|
model = self.app.model
|
||||||
|
model_class = self.get_model_class()
|
||||||
|
|
||||||
|
# drupal_id
|
||||||
|
g.set_label("drupal_id", "ID", column_only=True)
|
||||||
|
g.set_sorter("drupal_id", model.Quantity.drupal_id)
|
||||||
|
|
||||||
|
# as_text
|
||||||
|
g.set_renderer("as_text", self.render_as_text_for_grid)
|
||||||
|
g.set_link("as_text")
|
||||||
|
|
||||||
|
# quantity_type
|
||||||
|
if model_class is not model.Quantity:
|
||||||
|
g.remove("quantity_type")
|
||||||
|
else:
|
||||||
|
g.set_enum("quantity_type", get_quantity_type_enum(self.config))
|
||||||
|
|
||||||
|
# measure
|
||||||
|
g.set_sorter("measure", model.Measure.name)
|
||||||
|
|
||||||
|
# value
|
||||||
|
g.set_renderer("value", self.render_value_for_grid)
|
||||||
|
|
||||||
|
# units
|
||||||
|
g.set_sorter("units", model.Unit.name)
|
||||||
|
|
||||||
|
# label
|
||||||
|
g.set_sorter("label", model.Quantity.label)
|
||||||
|
|
||||||
|
# view action links to final quantity record
|
||||||
|
if model_class is model.Quantity:
|
||||||
|
|
||||||
|
def quantity_url(quantity, i):
|
||||||
|
return self.request.route_url(
|
||||||
|
f"quantities_{quantity.quantity_type_id}.view", uuid=quantity.uuid
|
||||||
|
)
|
||||||
|
|
||||||
|
g.add_action("view", icon="eye", url=quantity_url)
|
||||||
|
|
||||||
|
def render_as_text_for_grid(self, quantity, field, value):
|
||||||
|
return quantity.render_as_text(self.config)
|
||||||
|
|
||||||
|
def render_value_for_grid(self, quantity, field, value):
|
||||||
|
value = quantity.value_numerator / quantity.value_denominator
|
||||||
|
return self.app.render_quantity(value)
|
||||||
|
|
||||||
|
def get_instance_title(self, quantity):
|
||||||
|
return quantity.render_as_text(self.config)
|
||||||
|
|
||||||
|
def configure_form(self, form):
|
||||||
|
f = form
|
||||||
|
super().configure_form(f)
|
||||||
|
quantity = form.model_instance
|
||||||
|
|
||||||
|
# as_text
|
||||||
|
if self.creating or self.editing:
|
||||||
|
f.remove("as_text")
|
||||||
|
else:
|
||||||
|
f.set_default("as_text", quantity.render_as_text(self.config))
|
||||||
|
|
||||||
|
# quantity_type
|
||||||
|
if self.creating:
|
||||||
|
f.remove("quantity_type")
|
||||||
|
else:
|
||||||
|
f.set_readonly("quantity_type")
|
||||||
|
f.set_default("quantity_type", quantity.quantity_type.name)
|
||||||
|
|
||||||
|
# measure
|
||||||
|
if self.creating:
|
||||||
|
f.remove("measure")
|
||||||
|
else:
|
||||||
|
f.set_readonly("measure")
|
||||||
|
f.set_default("measure", quantity.measure.name)
|
||||||
|
|
||||||
|
# value
|
||||||
|
if self.creating:
|
||||||
|
f.remove("value")
|
||||||
|
else:
|
||||||
|
value = quantity.value_numerator / quantity.value_denominator
|
||||||
|
value = self.app.render_quantity(value)
|
||||||
|
f.set_default(
|
||||||
|
"value",
|
||||||
|
f"{value} ({quantity.value_numerator} / {quantity.value_denominator})",
|
||||||
|
)
|
||||||
|
|
||||||
|
# units
|
||||||
|
if self.creating:
|
||||||
|
f.remove("units")
|
||||||
|
else:
|
||||||
|
f.set_readonly("units")
|
||||||
|
f.set_node("units", UnitRef(self.request))
|
||||||
|
# TODO: ugh
|
||||||
|
f.set_default("units", quantity.quantity.units)
|
||||||
|
|
||||||
|
def get_xref_buttons(self, quantity):
|
||||||
|
buttons = super().get_xref_buttons(quantity)
|
||||||
|
|
||||||
|
if quantity.farmos_uuid:
|
||||||
|
url = self.request.route_url(
|
||||||
|
f"farmos_quantities_{quantity.quantity_type_id}.view",
|
||||||
|
uuid=quantity.farmos_uuid,
|
||||||
|
)
|
||||||
|
buttons.append(
|
||||||
|
self.make_button(
|
||||||
|
"View farmOS record", primary=True, url=url, icon_left="eye"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return buttons
|
||||||
|
|
||||||
|
|
||||||
|
class QuantityView(QuantityMasterView):
|
||||||
|
"""
|
||||||
|
Master view for All Quantities
|
||||||
|
"""
|
||||||
|
|
||||||
|
model_class = Quantity
|
||||||
|
route_prefix = "quantities"
|
||||||
|
url_prefix = "/quantities"
|
||||||
|
|
||||||
|
viewable = False
|
||||||
|
creatable = False
|
||||||
|
editable = False
|
||||||
|
deletable = False
|
||||||
|
model_is_versioned = False
|
||||||
|
|
||||||
|
|
||||||
|
class StandardQuantityView(QuantityMasterView):
|
||||||
|
"""
|
||||||
|
Master view for Standard Quantities
|
||||||
|
"""
|
||||||
|
|
||||||
|
model_class = StandardQuantity
|
||||||
|
route_prefix = "quantities_standard"
|
||||||
|
url_prefix = "/quantities/standard"
|
||||||
|
|
||||||
|
|
||||||
def defaults(config, **kwargs):
|
def defaults(config, **kwargs):
|
||||||
base = globals()
|
base = globals()
|
||||||
|
|
||||||
QuantityTypeView = kwargs.get("QuantityTypeView", base["QuantityTypeView"])
|
QuantityTypeView = kwargs.get("QuantityTypeView", base["QuantityTypeView"])
|
||||||
QuantityTypeView.defaults(config)
|
QuantityTypeView.defaults(config)
|
||||||
|
|
||||||
|
QuantityView = kwargs.get("QuantityView", base["QuantityView"])
|
||||||
|
QuantityView.defaults(config)
|
||||||
|
|
||||||
|
StandardQuantityView = kwargs.get(
|
||||||
|
"StandardQuantityView", base["StandardQuantityView"]
|
||||||
|
)
|
||||||
|
StandardQuantityView.defaults(config)
|
||||||
|
|
||||||
|
|
||||||
def includeme(config):
|
def includeme(config):
|
||||||
defaults(config)
|
defaults(config)
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,40 @@ Master view for Units
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from wuttafarm.web.views import WuttaFarmMasterView
|
from wuttafarm.web.views import WuttaFarmMasterView
|
||||||
from wuttafarm.db.model import Unit
|
from wuttafarm.db.model import Measure, Unit
|
||||||
|
|
||||||
|
|
||||||
|
class MeasureView(WuttaFarmMasterView):
|
||||||
|
"""
|
||||||
|
Master view for Measures
|
||||||
|
"""
|
||||||
|
|
||||||
|
model_class = Measure
|
||||||
|
route_prefix = "measures"
|
||||||
|
url_prefix = "/measures"
|
||||||
|
|
||||||
|
grid_columns = [
|
||||||
|
"name",
|
||||||
|
"drupal_id",
|
||||||
|
]
|
||||||
|
|
||||||
|
sort_defaults = "name"
|
||||||
|
|
||||||
|
filter_defaults = {
|
||||||
|
"name": {"active": True, "verb": "contains"},
|
||||||
|
}
|
||||||
|
|
||||||
|
form_fields = [
|
||||||
|
"name",
|
||||||
|
"drupal_id",
|
||||||
|
]
|
||||||
|
|
||||||
|
def configure_grid(self, grid):
|
||||||
|
g = grid
|
||||||
|
super().configure_grid(g)
|
||||||
|
|
||||||
|
# name
|
||||||
|
g.set_link("name")
|
||||||
|
|
||||||
|
|
||||||
class UnitView(WuttaFarmMasterView):
|
class UnitView(WuttaFarmMasterView):
|
||||||
|
|
@ -34,7 +67,7 @@ class UnitView(WuttaFarmMasterView):
|
||||||
|
|
||||||
model_class = Unit
|
model_class = Unit
|
||||||
route_prefix = "units"
|
route_prefix = "units"
|
||||||
url_prefix = "/animal-types"
|
url_prefix = "/units"
|
||||||
|
|
||||||
farmos_refurl_path = "/admin/structure/taxonomy/manage/unit/overview"
|
farmos_refurl_path = "/admin/structure/taxonomy/manage/unit/overview"
|
||||||
|
|
||||||
|
|
@ -87,6 +120,9 @@ class UnitView(WuttaFarmMasterView):
|
||||||
def defaults(config, **kwargs):
|
def defaults(config, **kwargs):
|
||||||
base = globals()
|
base = globals()
|
||||||
|
|
||||||
|
MeasureView = kwargs.get("MeasureView", base["MeasureView"])
|
||||||
|
MeasureView.defaults(config)
|
||||||
|
|
||||||
UnitView = kwargs.get("UnitView", base["UnitView"])
|
UnitView = kwargs.get("UnitView", base["UnitView"])
|
||||||
UnitView.defaults(config)
|
UnitView.defaults(config)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue