feat: add edit/sync support for Material Types + Material Quantities
This commit is contained in:
parent
6bc5f06f7a
commit
dfc8dc0de3
20 changed files with 1075 additions and 86 deletions
|
|
@ -177,6 +177,48 @@ class WuttaFarmAppHandler(base.AppHandler):
|
|||
with self.short_session(session=session) as sess:
|
||||
return sess.query(model.Unit).order_by(model.Unit.name).all()
|
||||
|
||||
def get_material_types(self, session=None):
|
||||
"""
|
||||
Returns a list of all known material types.
|
||||
"""
|
||||
model = self.model
|
||||
with self.short_session(session=session) as sess:
|
||||
return (
|
||||
sess.query(model.MaterialType).order_by(model.MaterialType.name).all()
|
||||
)
|
||||
|
||||
def get_quantity_models(self):
|
||||
model = self.model
|
||||
return {
|
||||
"standard": model.StandardQuantity,
|
||||
"material": model.MaterialQuantity,
|
||||
}
|
||||
|
||||
def get_true_quantity(self, quantity, require=True):
|
||||
model = self.model
|
||||
if not isinstance(quantity, model.Quantity):
|
||||
if require and not quantity:
|
||||
raise ValueError(f"quantity is not valid: {quantity}")
|
||||
return quantity
|
||||
|
||||
session = self.get_session(quantity)
|
||||
models = self.get_quantity_models()
|
||||
if require and quantity.quantity_type_id not in models:
|
||||
raise ValueError(
|
||||
f"quantity has invalid quantity_type_id: {quantity.quantity_type_id}"
|
||||
)
|
||||
|
||||
true_quantity = session.get(models[quantity.quantity_type_id], quantity.uuid)
|
||||
if require and not true_quantity:
|
||||
raise ValueError(f"quantity has no true/typed quantity record: {quantity}")
|
||||
|
||||
return true_quantity
|
||||
|
||||
def make_true_quantity(self, quantity_type_id, **kwargs):
|
||||
models = self.get_quantity_models()
|
||||
kwargs["quantity_type_id"] = quantity_type_id
|
||||
return models[quantity_type_id](**kwargs)
|
||||
|
||||
def auto_sync_to_farmos(self, obj, model_name=None, client=None, require=True):
|
||||
"""
|
||||
Export the given object to farmOS, using configured handler.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,211 @@
|
|||
"""add MaterialQuantity
|
||||
|
||||
Revision ID: 9c53513f8862
|
||||
Revises: 1c89f3fbb521
|
||||
Create Date: 2026-03-08 18:14:05.587678
|
||||
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import wuttjamaican.db.util
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = "9c53513f8862"
|
||||
down_revision: Union[str, None] = "1c89f3fbb521"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
|
||||
# quantity_material
|
||||
op.create_table(
|
||||
"quantity_material",
|
||||
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
["uuid"], ["quantity.uuid"], name=op.f("fk_quantity_material_uuid_quantity")
|
||||
),
|
||||
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_quantity_material")),
|
||||
)
|
||||
op.create_table(
|
||||
"quantity_material_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_material_version")
|
||||
),
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_quantity_material_version_end_transaction_id"),
|
||||
"quantity_material_version",
|
||||
["end_transaction_id"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_quantity_material_version_operation_type"),
|
||||
"quantity_material_version",
|
||||
["operation_type"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_quantity_material_version_pk_transaction_id",
|
||||
"quantity_material_version",
|
||||
["uuid", sa.literal_column("transaction_id DESC")],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_quantity_material_version_pk_validity",
|
||||
"quantity_material_version",
|
||||
["uuid", "transaction_id", "end_transaction_id"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_quantity_material_version_transaction_id"),
|
||||
"quantity_material_version",
|
||||
["transaction_id"],
|
||||
unique=False,
|
||||
)
|
||||
|
||||
# quantity_material_material_type
|
||||
op.create_table(
|
||||
"quantity_material_material_type",
|
||||
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
|
||||
sa.Column("quantity_uuid", wuttjamaican.db.util.UUID(), nullable=False),
|
||||
sa.Column("material_type_uuid", wuttjamaican.db.util.UUID(), nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
["material_type_uuid"],
|
||||
["material_type.uuid"],
|
||||
name=op.f(
|
||||
"fk_quantity_material_material_type_material_type_uuid_material_type"
|
||||
),
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["quantity_uuid"],
|
||||
["quantity_material.uuid"],
|
||||
name=op.f(
|
||||
"fk_quantity_material_material_type_quantity_uuid_quantity_material"
|
||||
),
|
||||
),
|
||||
sa.PrimaryKeyConstraint(
|
||||
"uuid", name=op.f("pk_quantity_material_material_type")
|
||||
),
|
||||
)
|
||||
op.create_table(
|
||||
"quantity_material_material_type_version",
|
||||
sa.Column(
|
||||
"uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
|
||||
),
|
||||
sa.Column(
|
||||
"quantity_uuid",
|
||||
wuttjamaican.db.util.UUID(),
|
||||
autoincrement=False,
|
||||
nullable=True,
|
||||
),
|
||||
sa.Column(
|
||||
"material_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_quantity_material_material_type_version"),
|
||||
),
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_quantity_material_material_type_version_end_transaction_id"),
|
||||
"quantity_material_material_type_version",
|
||||
["end_transaction_id"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_quantity_material_material_type_version_operation_type"),
|
||||
"quantity_material_material_type_version",
|
||||
["operation_type"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_quantity_material_material_type_version_pk_transaction_id",
|
||||
"quantity_material_material_type_version",
|
||||
["uuid", sa.literal_column("transaction_id DESC")],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_quantity_material_material_type_version_pk_validity",
|
||||
"quantity_material_material_type_version",
|
||||
["uuid", "transaction_id", "end_transaction_id"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_quantity_material_material_type_version_transaction_id"),
|
||||
"quantity_material_material_type_version",
|
||||
["transaction_id"],
|
||||
unique=False,
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
# quantity_material_material_type
|
||||
op.drop_index(
|
||||
op.f("ix_quantity_material_material_type_version_transaction_id"),
|
||||
table_name="quantity_material_material_type_version",
|
||||
)
|
||||
op.drop_index(
|
||||
"ix_quantity_material_material_type_version_pk_validity",
|
||||
table_name="quantity_material_material_type_version",
|
||||
)
|
||||
op.drop_index(
|
||||
"ix_quantity_material_material_type_version_pk_transaction_id",
|
||||
table_name="quantity_material_material_type_version",
|
||||
)
|
||||
op.drop_index(
|
||||
op.f("ix_quantity_material_material_type_version_operation_type"),
|
||||
table_name="quantity_material_material_type_version",
|
||||
)
|
||||
op.drop_index(
|
||||
op.f("ix_quantity_material_material_type_version_end_transaction_id"),
|
||||
table_name="quantity_material_material_type_version",
|
||||
)
|
||||
op.drop_table("quantity_material_material_type_version")
|
||||
op.drop_table("quantity_material_material_type")
|
||||
|
||||
# quantity_material
|
||||
op.drop_index(
|
||||
op.f("ix_quantity_material_version_transaction_id"),
|
||||
table_name="quantity_material_version",
|
||||
)
|
||||
op.drop_index(
|
||||
"ix_quantity_material_version_pk_validity",
|
||||
table_name="quantity_material_version",
|
||||
)
|
||||
op.drop_index(
|
||||
"ix_quantity_material_version_pk_transaction_id",
|
||||
table_name="quantity_material_version",
|
||||
)
|
||||
op.drop_index(
|
||||
op.f("ix_quantity_material_version_operation_type"),
|
||||
table_name="quantity_material_version",
|
||||
)
|
||||
op.drop_index(
|
||||
op.f("ix_quantity_material_version_end_transaction_id"),
|
||||
table_name="quantity_material_version",
|
||||
)
|
||||
op.drop_table("quantity_material_version")
|
||||
op.drop_table("quantity_material")
|
||||
|
|
@ -32,7 +32,13 @@ from .users import WuttaFarmUser
|
|||
# wuttafarm proper models
|
||||
from .unit import Unit, Measure
|
||||
from .material_type import MaterialType
|
||||
from .quantities import QuantityType, Quantity, StandardQuantity
|
||||
from .quantities import (
|
||||
QuantityType,
|
||||
Quantity,
|
||||
StandardQuantity,
|
||||
MaterialQuantity,
|
||||
MaterialQuantityMaterialType,
|
||||
)
|
||||
from .asset import AssetType, Asset, AssetParent
|
||||
from .asset_land import LandType, LandAsset
|
||||
from .asset_structure import StructureType, StructureAsset
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ Model definition for Material Types
|
|||
"""
|
||||
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy import orm
|
||||
from sqlalchemy.ext.associationproxy import association_proxy
|
||||
|
||||
from wuttjamaican.db import model
|
||||
|
||||
|
|
@ -76,5 +78,23 @@ class MaterialType(model.Base):
|
|||
""",
|
||||
)
|
||||
|
||||
_quantities = orm.relationship(
|
||||
"MaterialQuantityMaterialType",
|
||||
cascade="all, delete-orphan",
|
||||
cascade_backrefs=False,
|
||||
back_populates="material_type",
|
||||
)
|
||||
|
||||
def _make_material_quantity(qty):
|
||||
from wuttafarm.db.model import MaterialQuantityMaterialType
|
||||
|
||||
return MaterialQuantityMaterialType(quantity=qty)
|
||||
|
||||
quantities = association_proxy(
|
||||
"_quantities",
|
||||
"quantity",
|
||||
creator=_make_material_quantity,
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.name or ""
|
||||
|
|
|
|||
|
|
@ -211,6 +211,9 @@ class QuantityMixin:
|
|||
cascade_backrefs=False,
|
||||
)
|
||||
|
||||
def get_value_decimal(self):
|
||||
return self.quantity.get_value_decimal()
|
||||
|
||||
def render_as_text(self, config=None):
|
||||
return self.quantity.render_as_text(config)
|
||||
|
||||
|
|
@ -249,3 +252,64 @@ class StandardQuantity(QuantityMixin, model.Base):
|
|||
|
||||
|
||||
add_quantity_proxies(StandardQuantity)
|
||||
|
||||
|
||||
class MaterialQuantity(QuantityMixin, model.Base):
|
||||
"""
|
||||
Represents a Material Quantity from farmOS
|
||||
"""
|
||||
|
||||
__tablename__ = "quantity_material"
|
||||
__versioned__ = {}
|
||||
__wutta_hint__ = {
|
||||
"model_title": "Material Quantity",
|
||||
"model_title_plural": "Material Quantities",
|
||||
"farmos_quantity_type": "material",
|
||||
}
|
||||
|
||||
_material_types = orm.relationship(
|
||||
"MaterialQuantityMaterialType",
|
||||
cascade="all, delete-orphan",
|
||||
cascade_backrefs=False,
|
||||
back_populates="quantity",
|
||||
)
|
||||
|
||||
material_types = association_proxy(
|
||||
"_material_types",
|
||||
"material_type",
|
||||
creator=lambda mtype: MaterialQuantityMaterialType(material_type=mtype),
|
||||
)
|
||||
|
||||
def render_as_text(self, config=None):
|
||||
text = super().render_as_text(config)
|
||||
mtypes = ", ".join([str(mt) for mt in self.material_types])
|
||||
return f"{mtypes} {text}"
|
||||
|
||||
|
||||
add_quantity_proxies(MaterialQuantity)
|
||||
|
||||
|
||||
class MaterialQuantityMaterialType(model.Base):
|
||||
"""
|
||||
Represents a "material quantity's material type relationship" from
|
||||
farmOS.
|
||||
"""
|
||||
|
||||
__tablename__ = "quantity_material_material_type"
|
||||
__versioned__ = {}
|
||||
|
||||
uuid = model.uuid_column()
|
||||
|
||||
quantity_uuid = model.uuid_fk_column("quantity_material.uuid", nullable=False)
|
||||
quantity = orm.relationship(
|
||||
MaterialQuantity,
|
||||
foreign_keys=quantity_uuid,
|
||||
back_populates="_material_types",
|
||||
)
|
||||
|
||||
material_type_uuid = model.uuid_fk_column("material_type.uuid", nullable=False)
|
||||
material_type = orm.relationship(
|
||||
"MaterialType",
|
||||
foreign_keys=material_type_uuid,
|
||||
back_populates="_quantities",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -617,6 +617,49 @@ class ToFarmOSQuantity(ToFarmOS):
|
|||
return payload
|
||||
|
||||
|
||||
class MaterialQuantityImporter(ToFarmOSQuantity):
|
||||
|
||||
model_title = "MaterialQuantity"
|
||||
farmos_quantity_type = "material"
|
||||
|
||||
def get_supported_fields(self):
|
||||
fields = list(super().get_supported_fields())
|
||||
fields.extend(
|
||||
[
|
||||
"material_types",
|
||||
]
|
||||
)
|
||||
return fields
|
||||
|
||||
def normalize_target_object(self, quantity):
|
||||
data = super().normalize_target_object(quantity)
|
||||
|
||||
if "material_types" in self.fields:
|
||||
data["material_types"] = [
|
||||
UUID(mtype["id"])
|
||||
for mtype in quantity["relationships"]["material_type"]["data"]
|
||||
]
|
||||
|
||||
return data
|
||||
|
||||
def get_quantity_payload(self, source_data):
|
||||
payload = super().get_quantity_payload(source_data)
|
||||
|
||||
rels = {}
|
||||
if "material_types" in self.fields:
|
||||
rels["material_type"] = {"data": []}
|
||||
for uuid in source_data["material_types"]:
|
||||
rels["material_type"]["data"].append(
|
||||
{
|
||||
"id": str(uuid),
|
||||
"type": "taxonomy_term--material_type",
|
||||
}
|
||||
)
|
||||
|
||||
payload.setdefault("relationships", {}).update(rels)
|
||||
return payload
|
||||
|
||||
|
||||
class StandardQuantityImporter(ToFarmOSQuantity):
|
||||
|
||||
model_title = "StandardQuantity"
|
||||
|
|
|
|||
|
|
@ -106,6 +106,7 @@ class FromWuttaFarmToFarmOS(FromWuttaFarmHandler, ToFarmOSHandler):
|
|||
importers["PlantAsset"] = PlantAssetImporter
|
||||
importers["Unit"] = UnitImporter
|
||||
importers["MaterialType"] = MaterialTypeImporter
|
||||
importers["MaterialQuantity"] = MaterialQuantityImporter
|
||||
importers["StandardQuantity"] = StandardQuantityImporter
|
||||
importers["ActivityLog"] = ActivityLogImporter
|
||||
importers["HarvestLog"] = HarvestLogImporter
|
||||
|
|
@ -449,6 +450,24 @@ class FromWuttaFarmQuantity(FromWuttaFarm):
|
|||
}
|
||||
|
||||
|
||||
class MaterialQuantityImporter(
|
||||
FromWuttaFarmQuantity, farmos_importing.model.MaterialQuantityImporter
|
||||
):
|
||||
"""
|
||||
WuttaFarm → farmOS API exporter for Material Quantities
|
||||
"""
|
||||
|
||||
source_model_class = model.MaterialQuantity
|
||||
|
||||
def normalize_source_object(self, quantity):
|
||||
data = super().normalize_source_object(quantity)
|
||||
|
||||
if "material_types" in self.fields:
|
||||
data["material_types"] = [mt.farmos_uuid for mt in quantity.material_types]
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class StandardQuantityImporter(
|
||||
FromWuttaFarmQuantity, farmos_importing.model.StandardQuantityImporter
|
||||
):
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ class FromFarmOSToWuttaFarm(FromFarmOSHandler, ToWuttaFarmHandler):
|
|||
importers["MaterialType"] = MaterialTypeImporter
|
||||
importers["QuantityType"] = QuantityTypeImporter
|
||||
importers["StandardQuantity"] = StandardQuantityImporter
|
||||
importers["MaterialQuantity"] = MaterialQuantityImporter
|
||||
importers["LogType"] = LogTypeImporter
|
||||
importers["ActivityLog"] = ActivityLogImporter
|
||||
importers["HarvestLog"] = HarvestLogImporter
|
||||
|
|
@ -1419,3 +1420,76 @@ class StandardQuantityImporter(QuantityImporterBase):
|
|||
"units_uuid",
|
||||
"label",
|
||||
]
|
||||
|
||||
|
||||
class MaterialQuantityImporter(QuantityImporterBase):
|
||||
"""
|
||||
farmOS API → WuttaFarm importer for Material Quantities
|
||||
"""
|
||||
|
||||
model_class = model.MaterialQuantity
|
||||
|
||||
def get_supported_fields(self):
|
||||
fields = list(super().get_supported_fields())
|
||||
fields.extend(
|
||||
[
|
||||
"material_types",
|
||||
]
|
||||
)
|
||||
return fields
|
||||
|
||||
def normalize_source_object(self, quantity):
|
||||
""" """
|
||||
data = super().normalize_source_object(quantity)
|
||||
|
||||
if "material_types" in self.fields:
|
||||
data["material_types"] = [
|
||||
UUID(mtype["id"])
|
||||
for mtype in quantity["relationships"]["material_type"]["data"]
|
||||
]
|
||||
|
||||
return data
|
||||
|
||||
def normalize_target_object(self, quantity):
|
||||
data = super().normalize_target_object(quantity)
|
||||
|
||||
if "material_types" in self.fields:
|
||||
data["material_types"] = [
|
||||
mtype.farmos_uuid for mtype in quantity.material_types
|
||||
]
|
||||
|
||||
return data
|
||||
|
||||
def update_target_object(self, quantity, source_data, target_data=None):
|
||||
model = self.app.model
|
||||
quantity = super().update_target_object(quantity, source_data, target_data)
|
||||
|
||||
if "material_types" in self.fields:
|
||||
if (
|
||||
not target_data
|
||||
or target_data["material_types"] != source_data["material_types"]
|
||||
):
|
||||
|
||||
for farmos_uuid in source_data["material_types"]:
|
||||
if (
|
||||
not target_data
|
||||
or farmos_uuid not in target_data["material_types"]
|
||||
):
|
||||
mtype = (
|
||||
self.target_session.query(model.MaterialType)
|
||||
.filter(model.MaterialType.farmos_uuid == farmos_uuid)
|
||||
.one()
|
||||
)
|
||||
quantity.material_types.append(mtype)
|
||||
|
||||
if target_data:
|
||||
for farmos_uuid in target_data["material_types"]:
|
||||
if farmos_uuid not in source_data["material_types"]:
|
||||
mtype = (
|
||||
self.target_session.query(model.MaterialType)
|
||||
.filter(model.MaterialType.farmos_uuid == farmos_uuid)
|
||||
.one()
|
||||
)
|
||||
quantity.material_types.remove(mtype)
|
||||
|
||||
return quantity
|
||||
|
|
|
|||
|
|
@ -260,27 +260,29 @@ class Normalizer(GenericHandler):
|
|||
|
||||
measure_id = attrs["measure"]
|
||||
|
||||
quantity_objects.append(
|
||||
{
|
||||
quantity_object = {
|
||||
"uuid": quantity["id"],
|
||||
"drupal_id": attrs["drupal_internal__id"],
|
||||
"quantity_type_uuid": rels["quantity_type"]["data"][
|
||||
"id"
|
||||
"quantity_type_uuid": rels["quantity_type"]["data"]["id"],
|
||||
"quantity_type_id": rels["quantity_type"]["data"]["meta"][
|
||||
"drupal_internal__target_id"
|
||||
],
|
||||
"quantity_type_id": rels["quantity_type"]["data"][
|
||||
"meta"
|
||||
]["drupal_internal__target_id"],
|
||||
"measure_id": measure_id,
|
||||
"measure_name": self.get_farmos_measure_name(
|
||||
measure_id
|
||||
),
|
||||
"measure_name": self.get_farmos_measure_name(measure_id),
|
||||
"value_numerator": value["numerator"],
|
||||
"value_decimal": value["decimal"],
|
||||
"value_denominator": value["denominator"],
|
||||
"unit_uuid": unit_uuid,
|
||||
"unit_name": unit["attributes"]["name"],
|
||||
}
|
||||
)
|
||||
if quantity_object["quantity_type_id"] == "material":
|
||||
quantity_object["material_types"] = [
|
||||
{"uuid": mtype["id"]}
|
||||
for mtype in quantity["relationships"]["material_type"][
|
||||
"data"
|
||||
]
|
||||
]
|
||||
quantity_objects.append(quantity_object)
|
||||
|
||||
if owners := relationships.get("owner"):
|
||||
for user in owners["data"]:
|
||||
|
|
|
|||
|
|
@ -164,10 +164,9 @@ class FarmOSRefs(WuttaSet):
|
|||
self.route_prefix = route_prefix
|
||||
|
||||
def serialize(self, node, appstruct):
|
||||
if appstruct is colander.null:
|
||||
if not appstruct:
|
||||
return colander.null
|
||||
|
||||
return json.dumps(appstruct)
|
||||
return appstruct
|
||||
|
||||
def widget_maker(self, **kwargs):
|
||||
from wuttafarm.web.forms.widgets import FarmOSRefsWidget
|
||||
|
|
@ -288,6 +287,37 @@ class PlantTypeRefs(WuttaSet):
|
|||
return PlantTypeRefsWidget(self.request, **kwargs)
|
||||
|
||||
|
||||
class MaterialTypeRefs(colander.List):
|
||||
"""
|
||||
Schema type for Material Types field (on a Material Asset).
|
||||
"""
|
||||
|
||||
def __init__(self, request):
|
||||
super().__init__()
|
||||
self.request = request
|
||||
self.config = self.request.wutta_config
|
||||
self.app = self.config.get_app()
|
||||
|
||||
def serialize(self, node, appstruct):
|
||||
if not appstruct:
|
||||
return colander.null
|
||||
|
||||
mtypes = []
|
||||
for mtype in appstruct:
|
||||
mtypes.append(
|
||||
{
|
||||
"uuid": mtype.uuid.hex,
|
||||
"name": mtype.name,
|
||||
}
|
||||
)
|
||||
return mtypes
|
||||
|
||||
def widget_maker(self, **kwargs):
|
||||
from wuttafarm.web.forms.widgets import MaterialTypeRefsWidget
|
||||
|
||||
return MaterialTypeRefsWidget(self.request, **kwargs)
|
||||
|
||||
|
||||
class SeasonRefs(WuttaSet):
|
||||
"""
|
||||
Schema type for Plant Types field (on a Plant Asset).
|
||||
|
|
@ -454,11 +484,11 @@ class QuantityRefs(colander.List):
|
|||
|
||||
quantities = []
|
||||
for qty in appstruct:
|
||||
quantities.append(
|
||||
{
|
||||
|
||||
quantity = {
|
||||
"uuid": qty.uuid.hex,
|
||||
"quantity_type": {
|
||||
"id": qty.quantity_type_id,
|
||||
"drupal_id": qty.quantity_type_id,
|
||||
"name": qty.quantity_type.name,
|
||||
},
|
||||
"measure": qty.measure_id,
|
||||
|
|
@ -468,9 +498,23 @@ class QuantityRefs(colander.List):
|
|||
"name": qty.units.name,
|
||||
},
|
||||
"as_text": qty.render_as_text(self.config),
|
||||
# nb. always include this regardless of quantity type,
|
||||
# for sake of easier frontend logic
|
||||
"material_types": [],
|
||||
}
|
||||
|
||||
if qty.quantity_type_id == "material":
|
||||
quantity["material_types"] = []
|
||||
for mtype in qty.material_types:
|
||||
quantity["material_types"].append(
|
||||
{
|
||||
"uuid": mtype.uuid.hex,
|
||||
"name": mtype.name,
|
||||
}
|
||||
)
|
||||
|
||||
quantities.append(quantity)
|
||||
|
||||
return quantities
|
||||
|
||||
def widget_maker(self, **kwargs):
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ class FarmOSRefsWidget(Widget):
|
|||
return HTML.tag("span")
|
||||
|
||||
links = []
|
||||
for obj in json.loads(cstruct):
|
||||
for obj in cstruct:
|
||||
url = self.request.route_url(
|
||||
f"{self.route_prefix}.view", uuid=obj["uuid"]
|
||||
)
|
||||
|
|
@ -332,6 +332,72 @@ class PlantTypeRefsWidget(Widget):
|
|||
return set(pstruct.split(","))
|
||||
|
||||
|
||||
class MaterialTypeRefsWidget(Widget):
|
||||
"""
|
||||
Widget for Material Types field (on a Material Asset).
|
||||
"""
|
||||
|
||||
template = "materialtyperefs"
|
||||
values = ()
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.request = request
|
||||
self.config = self.request.wutta_config
|
||||
self.app = self.config.get_app()
|
||||
|
||||
def serialize(self, field, cstruct, **kw):
|
||||
""" """
|
||||
model = self.app.model
|
||||
session = Session()
|
||||
|
||||
if not cstruct:
|
||||
cstruct = []
|
||||
|
||||
if readonly := kw.get("readonly", self.readonly):
|
||||
items = []
|
||||
for mtype in cstruct:
|
||||
items.append(
|
||||
HTML.tag(
|
||||
"li",
|
||||
c=tags.link_to(
|
||||
mtype["name"],
|
||||
self.request.route_url(
|
||||
"material_types.view", uuid=mtype["uuid"]
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
return HTML.tag("ul", c=items)
|
||||
|
||||
tmpl_values = self.get_template_values(field, cstruct, kw)
|
||||
return field.renderer(self.template, **tmpl_values)
|
||||
|
||||
def get_template_values(self, field, cstruct, kw):
|
||||
""" """
|
||||
values = super().get_template_values(field, cstruct, kw)
|
||||
session = Session()
|
||||
|
||||
material_types = []
|
||||
for mtype in self.app.get_material_types(session):
|
||||
material_types.append(
|
||||
{
|
||||
"uuid": mtype.uuid.hex,
|
||||
"name": mtype.name,
|
||||
}
|
||||
)
|
||||
values["material_types"] = json.dumps(material_types)
|
||||
|
||||
return values
|
||||
|
||||
def deserialize(self, field, pstruct):
|
||||
""" """
|
||||
if not pstruct:
|
||||
return []
|
||||
|
||||
return json.loads(pstruct)
|
||||
|
||||
|
||||
class SeasonRefsWidget(Widget):
|
||||
"""
|
||||
Widget for Seasons field (on a Plant Asset).
|
||||
|
|
@ -550,11 +616,10 @@ class QuantityRefsWidget(Widget):
|
|||
return ""
|
||||
|
||||
quantities = []
|
||||
|
||||
for qty in cstruct:
|
||||
# TODO: support more quantity types
|
||||
url = self.request.route_url(
|
||||
"quantities_standard.view", uuid=qty["uuid"]
|
||||
f"quantities_{qty['quantity_type']['drupal_id']}.view",
|
||||
uuid=qty["uuid"],
|
||||
)
|
||||
quantities.append(HTML.tag("li", c=tags.link_to(qty["as_text"], url)))
|
||||
|
||||
|
|
@ -570,8 +635,6 @@ class QuantityRefsWidget(Widget):
|
|||
|
||||
qtypes = []
|
||||
for qtype in self.app.get_quantity_types(session):
|
||||
# TODO: add support for other quantity types
|
||||
if qtype.drupal_id == "standard":
|
||||
qtypes.append(
|
||||
{
|
||||
"uuid": qtype.uuid.hex,
|
||||
|
|
@ -581,6 +644,16 @@ class QuantityRefsWidget(Widget):
|
|||
)
|
||||
values["quantity_types"] = qtypes
|
||||
|
||||
material_types = []
|
||||
for mtype in self.app.get_material_types(session):
|
||||
material_types.append(
|
||||
{
|
||||
"uuid": mtype.uuid.hex,
|
||||
"name": mtype.name,
|
||||
}
|
||||
)
|
||||
values["material_types"] = material_types
|
||||
|
||||
measures = []
|
||||
for measure in self.app.get_measures(session):
|
||||
measures.append(
|
||||
|
|
|
|||
|
|
@ -182,6 +182,11 @@ class WuttaFarmMenuHandler(base.MenuHandler):
|
|||
"route": "quantities",
|
||||
"perm": "quantities.list",
|
||||
},
|
||||
{
|
||||
"title": "Material Quantities",
|
||||
"route": "quantities_material",
|
||||
"perm": "quantities_material.list",
|
||||
},
|
||||
{
|
||||
"title": "Standard Quantities",
|
||||
"route": "quantities_standard",
|
||||
|
|
@ -322,6 +327,11 @@ class WuttaFarmMenuHandler(base.MenuHandler):
|
|||
"route": "farmos_quantity_types",
|
||||
"perm": "farmos_quantity_types.list",
|
||||
},
|
||||
{
|
||||
"title": "Material Quantities",
|
||||
"route": "farmos_quantities_material",
|
||||
"perm": "farmos_quantities_material.list",
|
||||
},
|
||||
{
|
||||
"title": "Standard Quantities",
|
||||
"route": "farmos_quantities_standard",
|
||||
|
|
@ -451,6 +461,11 @@ class WuttaFarmMenuHandler(base.MenuHandler):
|
|||
"route": "farmos_quantity_types",
|
||||
"perm": "farmos_quantity_types.list",
|
||||
},
|
||||
{
|
||||
"title": "Material Quantities",
|
||||
"route": "farmos_quantities_material",
|
||||
"perm": "farmos_quantities_material.list",
|
||||
},
|
||||
{
|
||||
"title": "Standard Quantities",
|
||||
"route": "farmos_quantities_standard",
|
||||
|
|
|
|||
13
src/wuttafarm/web/templates/deform/materialtyperefs.pt
Normal file
13
src/wuttafarm/web/templates/deform/materialtyperefs.pt
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<div tal:define="
|
||||
name name|field.name;
|
||||
oid oid|field.oid;
|
||||
vmodel vmodel|'modelData.'+oid;
|
||||
can_create can_create|False;"
|
||||
tal:omit-tag="">
|
||||
|
||||
<material-types-picker tal:attributes="name name;
|
||||
v-model vmodel;
|
||||
:material-types material_types;
|
||||
:can-create str(can_create).lower();" />
|
||||
|
||||
</div>
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
<quantities-editor tal:attributes="name name;
|
||||
v-model vmodel;
|
||||
:quantity-types quantity_types;
|
||||
:material-types material_types;
|
||||
:measures measures;
|
||||
:units units;" />
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
<%def name="make_wuttafarm_components()">
|
||||
${self.make_assets_picker_component()}
|
||||
${self.make_animal_type_picker_component()}
|
||||
${self.make_material_types_picker_component()}
|
||||
${self.make_quantity_editor_component()}
|
||||
${self.make_quantities_editor_component()}
|
||||
${self.make_plant_types_picker_component()}
|
||||
|
|
@ -241,10 +242,134 @@
|
|||
</script>
|
||||
</%def>
|
||||
|
||||
<%def name="make_material_types_picker_component()">
|
||||
<script type="text/x-template" id="material-types-picker-template">
|
||||
<div>
|
||||
<input type="hidden" :name="name" :value="JSON.stringify(value)" />
|
||||
|
||||
<div style="display: flex; gap: 0.5rem; align-items: center;">
|
||||
|
||||
<span>Add:</span>
|
||||
|
||||
<b-autocomplete v-model="addName"
|
||||
ref="addName"
|
||||
:data="addNameData"
|
||||
field="name"
|
||||
open-on-focus
|
||||
keep-first
|
||||
@select="addNameSelected"
|
||||
clear-on-select
|
||||
style="flex-grow: 1;">
|
||||
<template #empty>No results found</template>
|
||||
</b-autocomplete>
|
||||
|
||||
</div>
|
||||
|
||||
<${b}-table :data="materialTypeData">
|
||||
|
||||
<${b}-table-column field="name" v-slot="props">
|
||||
<span>{{ props.row.name }}</span>
|
||||
</${b}-table-column>
|
||||
|
||||
<${b}-table-column v-slot="props">
|
||||
<a href="#"
|
||||
class="has-text-danger"
|
||||
@click.prevent="removeMaterialType(props.row)">
|
||||
<i class="fas fa-trash" /> Remove
|
||||
</a>
|
||||
</${b}-table-column>
|
||||
|
||||
</${b}-table>
|
||||
</div>
|
||||
</script>
|
||||
<script>
|
||||
const MaterialTypesPicker = {
|
||||
template: '#material-types-picker-template',
|
||||
props: {
|
||||
name: String,
|
||||
value: Array,
|
||||
materialTypes: Array,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
addName: '',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
materialTypeData() {
|
||||
const data = []
|
||||
|
||||
if (this.value) {
|
||||
|
||||
const mtypes = new Map(this.materialTypes.map((mtype) => {
|
||||
return [mtype.uuid, mtype.name]
|
||||
}))
|
||||
|
||||
for (const mtype of this.value) {
|
||||
if (mtypes.has(mtype.uuid)) {
|
||||
data.push(mtype)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
},
|
||||
addNameData() {
|
||||
if (!this.addName) {
|
||||
return this.materialTypes
|
||||
}
|
||||
|
||||
return this.materialTypes.filter((mtype) => {
|
||||
return mtype.name.toLowerCase().indexOf(this.addName.toLowerCase()) >= 0
|
||||
})
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
||||
focus() {
|
||||
this.$refs.addName.focus()
|
||||
},
|
||||
|
||||
addNameSelected(option) {
|
||||
if (!option) {
|
||||
return
|
||||
}
|
||||
|
||||
const value = Array.from(this.value || [])
|
||||
|
||||
if (!value.includes(option)) {
|
||||
value.push(option)
|
||||
this.$emit('input', value)
|
||||
}
|
||||
|
||||
this.addName = null
|
||||
},
|
||||
|
||||
removeMaterialType(ptype) {
|
||||
const value = Array.from(this.value)
|
||||
const i = value.indexOf(ptype)
|
||||
value.splice(i, 1)
|
||||
this.$emit('input', value)
|
||||
},
|
||||
},
|
||||
}
|
||||
Vue.component('material-types-picker', MaterialTypesPicker)
|
||||
<% request.register_component('material-types-picker', 'MaterialTypesPicker') %>
|
||||
</script>
|
||||
</%def>
|
||||
|
||||
<%def name="make_quantity_editor_component()">
|
||||
<script type="text/x-template" id="quantity-editor-template">
|
||||
<div>
|
||||
|
||||
<b-field v-if="quantityType == 'material'"
|
||||
label="Material Types"
|
||||
horizontal>
|
||||
<material-types-picker v-model="value.material_types"
|
||||
ref="materials"
|
||||
:material-types="materialTypes" />
|
||||
</b-field>
|
||||
|
||||
<b-field label="Measure" horizontal
|
||||
## TODO: why is this needed?
|
||||
style="margin-bottom: 1rem;">
|
||||
|
|
@ -306,6 +431,8 @@
|
|||
props: {
|
||||
name: String,
|
||||
value: Object,
|
||||
quantityType: String,
|
||||
materialTypes: Array,
|
||||
measures: Array,
|
||||
units: Array,
|
||||
creating: {
|
||||
|
|
@ -314,8 +441,6 @@
|
|||
}
|
||||
},
|
||||
data() {
|
||||
|
||||
|
||||
return {
|
||||
measure: this.value.measure,
|
||||
valueAmount: this.value.value,
|
||||
|
|
@ -349,8 +474,12 @@
|
|||
},
|
||||
methods: {
|
||||
|
||||
focusMeasure() {
|
||||
focusForCreate() {
|
||||
if (this.value.quantity_type.drupal_id == 'material') {
|
||||
this.$refs.materials.focus()
|
||||
} else {
|
||||
this.$refs.measure.focus()
|
||||
}
|
||||
},
|
||||
|
||||
focusValue() {
|
||||
|
|
@ -439,6 +568,8 @@
|
|||
|
||||
<template #detail="props">
|
||||
<quantity-editor v-model="props.row"
|
||||
:quantity-type="props.row.quantity_type.drupal_id"
|
||||
:material-types="materialTypes"
|
||||
:measures="measures"
|
||||
:units="units"
|
||||
@save="editSave"
|
||||
|
|
@ -468,6 +599,8 @@
|
|||
|
||||
<quantity-editor v-show="creating"
|
||||
v-model="newQuantity"
|
||||
:quantity-type="quantityType"
|
||||
:material-types="materialTypes"
|
||||
ref="newQuantity"
|
||||
creating
|
||||
@save="createSave"
|
||||
|
|
@ -484,6 +617,7 @@
|
|||
name: String,
|
||||
value: Array,
|
||||
quantityTypes: Array,
|
||||
materialTypes: Array,
|
||||
defaultQuantityType: {
|
||||
type: String,
|
||||
default: 'standard',
|
||||
|
|
@ -530,7 +664,7 @@
|
|||
qty = Object.fromEntries(Object.entries(qty))
|
||||
|
||||
qty.uuid = 'new_' + this.newCounter++
|
||||
qty.as_text = "( " + this.measureMap[qty.measure] + " ) " + qty.value + " " + qty.units.name
|
||||
qty.as_text = this.getQuantityText(qty)
|
||||
|
||||
const value = Array.from(this.value || [])
|
||||
value.push(qty)
|
||||
|
|
@ -557,7 +691,7 @@
|
|||
|
||||
this.creating = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.newQuantity.focusMeasure()
|
||||
this.$refs.newQuantity.focusForCreate()
|
||||
})
|
||||
},
|
||||
|
||||
|
|
@ -566,11 +700,25 @@
|
|||
this.editing[qty.uuid] = true
|
||||
},
|
||||
|
||||
editSave(row) {
|
||||
row.as_text = "( " + this.measureMap[row.measure] + " ) " + row.value + " " + row.units.name
|
||||
getQuantityText(qty) {
|
||||
let text = "( " + this.measureMap[qty.measure] + " ) "
|
||||
+ qty.value + " " + qty.units.name
|
||||
|
||||
if (qty.quantity_type.drupal_id == 'material') {
|
||||
const materials = qty.material_types.map((mtype) => {
|
||||
return mtype.name
|
||||
}).join(', ')
|
||||
text = materials + " " + text
|
||||
}
|
||||
|
||||
return text
|
||||
},
|
||||
|
||||
editSave(qty) {
|
||||
qty.as_text = this.getQuantityText(qty)
|
||||
this.$emit('input', this.value)
|
||||
this.editing[row.uuid] = false
|
||||
this.$refs.table.closeDetailRow(row)
|
||||
this.editing[qty.uuid] = false
|
||||
this.$refs.table.closeDetailRow(qty)
|
||||
},
|
||||
|
||||
editCancel(qty) {
|
||||
|
|
|
|||
|
|
@ -78,4 +78,10 @@ def render_quantity_object(quantity):
|
|||
measure = quantity["measure_name"]
|
||||
value = quantity["value_decimal"]
|
||||
unit = quantity["unit_name"]
|
||||
return f"( {measure} ) {value} {unit}"
|
||||
text = f"( {measure} ) {value} {unit}"
|
||||
|
||||
if quantity["quantity_type_id"] == "material":
|
||||
materials = ", ".join([mtype["name"] for mtype in quantity["material_types"]])
|
||||
return f"{materials} {text}"
|
||||
|
||||
return text
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ from wuttafarm.web.forms.schema import (
|
|||
LogQuick,
|
||||
Notes,
|
||||
)
|
||||
from wuttafarm.web.util import render_quantity_objects
|
||||
from wuttafarm.web.util import render_quantity_objects, render_quantity_object
|
||||
|
||||
|
||||
class LogMasterView(FarmOSMasterView):
|
||||
|
|
@ -199,7 +199,20 @@ class LogMasterView(FarmOSMasterView):
|
|||
)
|
||||
self.raw_json = result
|
||||
included = {obj["id"]: obj for obj in result.get("included", [])}
|
||||
return self.normalize_log(result["data"], included)
|
||||
instance = self.normalize_log(result["data"], included)
|
||||
|
||||
for qty in instance["quantities"]:
|
||||
|
||||
if qty["quantity_type_id"] == "material":
|
||||
for mtype in qty["material_types"]:
|
||||
result = self.farmos_client.resource.get_id(
|
||||
"taxonomy_term", "material_type", mtype["uuid"]
|
||||
)
|
||||
mtype["name"] = result["data"]["attributes"]["name"]
|
||||
|
||||
qty["as_text"] = render_quantity_object(qty)
|
||||
|
||||
return instance
|
||||
|
||||
def get_instance_title(self, log):
|
||||
return log["name"]
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ 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 FarmOSUnitRef
|
||||
from wuttafarm.web.forms.schema import FarmOSUnitRef, FarmOSRefs
|
||||
from wuttafarm.web.grids import ResourceData
|
||||
|
||||
|
||||
|
|
@ -143,6 +143,7 @@ class QuantityMasterView(FarmOSMasterView):
|
|||
sort_defaults = ("drupal_id", "desc")
|
||||
|
||||
form_fields = [
|
||||
"quantity_type_name",
|
||||
"measure",
|
||||
"value",
|
||||
"units",
|
||||
|
|
@ -207,18 +208,23 @@ class QuantityMasterView(FarmOSMasterView):
|
|||
def get_instance(self):
|
||||
# TODO: this pattern should be repeated for other views
|
||||
try:
|
||||
quantity = self.farmos_client.resource.get_id(
|
||||
"quantity", self.farmos_quantity_type, self.request.matchdict["uuid"]
|
||||
result = self.farmos_client.resource.get_id(
|
||||
"quantity",
|
||||
self.farmos_quantity_type,
|
||||
self.request.matchdict["uuid"],
|
||||
params={"include": self.get_farmos_api_includes()},
|
||||
)
|
||||
except requests.HTTPError as exc:
|
||||
if exc.response.status_code == 404:
|
||||
raise self.notfound()
|
||||
|
||||
self.raw_json = quantity
|
||||
self.raw_json = result
|
||||
|
||||
data = self.normalize_quantity(quantity["data"])
|
||||
included = {obj["id"]: obj for obj in result.get("included", [])}
|
||||
assert included
|
||||
data = self.normalize_quantity(result["data"], included)
|
||||
|
||||
if relationships := quantity["data"].get("relationships"):
|
||||
if relationships := result["data"].get("relationships"):
|
||||
|
||||
# add units
|
||||
if units := relationships.get("units"):
|
||||
|
|
@ -286,6 +292,11 @@ class QuantityMasterView(FarmOSMasterView):
|
|||
f = form
|
||||
super().configure_form(f)
|
||||
|
||||
# quantity_type_name
|
||||
f.set_label("quantity_type_name", "Quantity Type")
|
||||
f.set_readonly("quantity_type_name")
|
||||
f.set_default("quantity_type_name", self.farmos_quantity_type.capitalize())
|
||||
|
||||
# created
|
||||
f.set_node("created", WuttaDateTime(self.request))
|
||||
f.set_widget("created", WuttaDateTimeWidget(self.request))
|
||||
|
|
@ -338,6 +349,90 @@ class StandardQuantityView(QuantityMasterView):
|
|||
return buttons
|
||||
|
||||
|
||||
class MaterialQuantityView(QuantityMasterView):
|
||||
"""
|
||||
View for farmOS Material Quantities
|
||||
"""
|
||||
|
||||
model_name = "farmos_material_quantity"
|
||||
model_title = "farmOS Material Quantity"
|
||||
model_title_plural = "farmOS Material Quantities"
|
||||
|
||||
route_prefix = "farmos_quantities_material"
|
||||
url_prefix = "/farmOS/quantities/material"
|
||||
|
||||
farmos_quantity_type = "material"
|
||||
farmos_refurl_path = "/log-quantities/material"
|
||||
|
||||
def get_farmos_api_includes(self):
|
||||
includes = super().get_farmos_api_includes()
|
||||
includes.update({"material_type"})
|
||||
return includes
|
||||
|
||||
def normalize_quantity(self, quantity, included={}):
|
||||
normal = super().normalize_quantity(quantity, included)
|
||||
|
||||
material_type_objects = []
|
||||
material_type_uuids = []
|
||||
if relationships := quantity["relationships"]:
|
||||
|
||||
if material_types := relationships["material_type"]["data"]:
|
||||
for mtype in material_types:
|
||||
uuid = mtype["id"]
|
||||
material_type_uuids.append(uuid)
|
||||
material_type = {
|
||||
"uuid": uuid,
|
||||
"type": mtype["type"],
|
||||
}
|
||||
if mtype := included.get(uuid):
|
||||
material_type.update(
|
||||
{
|
||||
"name": mtype["attributes"]["name"],
|
||||
}
|
||||
)
|
||||
material_type_objects.append(material_type)
|
||||
|
||||
normal.update(
|
||||
{
|
||||
"material_types": material_type_objects,
|
||||
"material_type_uuids": material_type_uuids,
|
||||
}
|
||||
)
|
||||
return normal
|
||||
|
||||
def configure_form(self, form):
|
||||
f = form
|
||||
super().configure_form(f)
|
||||
|
||||
# material_types
|
||||
f.fields.insert_before("measure", "material_types")
|
||||
f.set_node("material_types", FarmOSRefs(self.request, "farmos_material_types"))
|
||||
|
||||
def get_xref_buttons(self, material_quantity):
|
||||
model = self.app.model
|
||||
session = self.Session()
|
||||
buttons = []
|
||||
|
||||
if wf_material_quantity := (
|
||||
session.query(model.MaterialQuantity)
|
||||
.join(model.Quantity)
|
||||
.filter(model.Quantity.farmos_uuid == material_quantity["uuid"])
|
||||
.first()
|
||||
):
|
||||
buttons.append(
|
||||
self.make_button(
|
||||
f"View {self.app.get_title()} record",
|
||||
primary=True,
|
||||
url=self.request.route_url(
|
||||
"quantities_material.view", uuid=wf_material_quantity.uuid
|
||||
),
|
||||
icon_left="eye",
|
||||
)
|
||||
)
|
||||
|
||||
return buttons
|
||||
|
||||
|
||||
def defaults(config, **kwargs):
|
||||
base = globals()
|
||||
|
||||
|
|
@ -349,6 +444,11 @@ def defaults(config, **kwargs):
|
|||
)
|
||||
StandardQuantityView.defaults(config)
|
||||
|
||||
MaterialQuantityView = kwargs.get(
|
||||
"MaterialQuantityView", base["MaterialQuantityView"]
|
||||
)
|
||||
MaterialQuantityView.defaults(config)
|
||||
|
||||
|
||||
def includeme(config):
|
||||
defaults(config)
|
||||
|
|
|
|||
|
|
@ -290,7 +290,9 @@ class LogMasterView(WuttaFarmMasterView):
|
|||
f.set_node("quantities", QuantityRefs(self.request))
|
||||
if not self.creating:
|
||||
# nb. must explicity declare value for non-standard field
|
||||
f.set_default("quantities", log.quantities)
|
||||
f.set_default(
|
||||
"quantities", [self.app.get_true_quantity(q) for q in log.quantities]
|
||||
)
|
||||
|
||||
# notes
|
||||
f.set_widget("notes", "notes")
|
||||
|
|
@ -389,15 +391,15 @@ class LogMasterView(WuttaFarmMasterView):
|
|||
model = self.app.model
|
||||
session = self.Session()
|
||||
|
||||
current = {qty.uuid.hex: qty for qty in log.quantities}
|
||||
current = {
|
||||
qty.uuid.hex: self.app.get_true_quantity(qty) for qty in log.quantities
|
||||
}
|
||||
for new_qty in desired:
|
||||
units = session.get(model.Unit, new_qty["units"]["uuid"])
|
||||
assert units
|
||||
if new_qty["uuid"].startswith("new_"):
|
||||
assert new_qty["quantity_type"]["drupal_id"] == "standard"
|
||||
factory = model.StandardQuantity
|
||||
qty = factory(
|
||||
quantity_type_id=new_qty["quantity_type"]["drupal_id"],
|
||||
qty = self.app.make_true_quantity(
|
||||
new_qty["quantity_type"]["drupal_id"],
|
||||
measure_id=new_qty["measure"],
|
||||
value_numerator=int(new_qty["value"]),
|
||||
value_denominator=1,
|
||||
|
|
@ -413,6 +415,8 @@ class LogMasterView(WuttaFarmMasterView):
|
|||
old_qty.value_numerator = int(new_qty["value"])
|
||||
old_qty.value_denominator = 1
|
||||
old_qty.units = units
|
||||
if old_qty.quantity_type_id == "material":
|
||||
self.set_material_types(old_qty, new_qty["material_types"])
|
||||
|
||||
desired = [qty["uuid"] for qty in desired]
|
||||
for old_qty in list(log.quantities):
|
||||
|
|
@ -421,6 +425,22 @@ class LogMasterView(WuttaFarmMasterView):
|
|||
if old_qty.uuid and old_qty.uuid.hex not in desired:
|
||||
log.quantities.remove(old_qty)
|
||||
|
||||
def set_material_types(self, quantity, desired):
|
||||
model = self.app.model
|
||||
session = self.Session()
|
||||
current = {mtype.uuid: mtype for mtype in quantity.material_types}
|
||||
|
||||
for new_mtype in desired:
|
||||
mtype = session.get(model.MaterialType, new_mtype["uuid"])
|
||||
assert mtype
|
||||
if mtype.uuid not in current:
|
||||
quantity.material_types.append(mtype)
|
||||
|
||||
desired = [mtype["uuid"] for mtype in desired]
|
||||
for old_mtype in current.values():
|
||||
if old_mtype.uuid.hex not in desired:
|
||||
quantity.material_types.remove(old_mtype)
|
||||
|
||||
def auto_sync_to_farmos(self, client, log):
|
||||
model = self.app.model
|
||||
session = self.Session()
|
||||
|
|
@ -429,10 +449,7 @@ class LogMasterView(WuttaFarmMasterView):
|
|||
session.flush()
|
||||
|
||||
for qty in log.quantities:
|
||||
# TODO: support more quantity types
|
||||
if qty.quantity_type_id == "standard":
|
||||
qty = session.get(model.StandardQuantity, qty.uuid)
|
||||
assert qty
|
||||
qty = self.app.get_true_quantity(qty)
|
||||
self.app.auto_sync_to_farmos(qty, client=client)
|
||||
|
||||
self.app.auto_sync_to_farmos(log, client=client)
|
||||
|
|
|
|||
|
|
@ -30,8 +30,13 @@ from webhelpers2.html import tags
|
|||
from wuttaweb.db import Session
|
||||
|
||||
from wuttafarm.web.views import WuttaFarmMasterView
|
||||
from wuttafarm.db.model import QuantityType, Quantity, StandardQuantity
|
||||
from wuttafarm.web.forms.schema import UnitRef, LogRef
|
||||
from wuttafarm.db.model import (
|
||||
QuantityType,
|
||||
Quantity,
|
||||
StandardQuantity,
|
||||
MaterialQuantity,
|
||||
)
|
||||
from wuttafarm.web.forms.schema import UnitRef, LogRef, MaterialTypeRefs
|
||||
from wuttafarm.util import get_log_type_enum
|
||||
|
||||
|
||||
|
|
@ -370,6 +375,74 @@ class StandardQuantityView(QuantityMasterView):
|
|||
farmos_refurl_path = "/log-quantities/standard"
|
||||
|
||||
|
||||
class MaterialQuantityView(QuantityMasterView):
|
||||
"""
|
||||
Master view for Material Quantities
|
||||
"""
|
||||
|
||||
model_class = MaterialQuantity
|
||||
route_prefix = "quantities_material"
|
||||
url_prefix = "/quantities/material"
|
||||
|
||||
farmos_bundle = "material"
|
||||
farmos_refurl_path = "/log-quantities/material"
|
||||
|
||||
def configure_grid(self, grid):
|
||||
g = grid
|
||||
super().configure_grid(g)
|
||||
|
||||
# material_types
|
||||
g.columns.append("material_types")
|
||||
g.set_label("material_types", "Material Type", column_only=True)
|
||||
g.set_renderer("material_types", self.render_material_types_for_grid)
|
||||
|
||||
def render_material_types_for_grid(self, quantity, field, value):
|
||||
if self.farmos_style_grid_links:
|
||||
links = []
|
||||
for mtype in quantity.material_types:
|
||||
url = self.request.route_url("material_types.view", uuid=mtype.uuid)
|
||||
links.append(tags.link_to(str(mtype), url))
|
||||
return ", ".join(links)
|
||||
|
||||
return ", ".join([str(mtype) for mtype in quantity.material_types])
|
||||
|
||||
def configure_form(self, form):
|
||||
f = form
|
||||
super().configure_form(f)
|
||||
quantity = form.model_instance
|
||||
|
||||
# material_types
|
||||
f.fields.insert_after("quantity_type", "material_types")
|
||||
f.set_node("material_types", MaterialTypeRefs(self.request))
|
||||
if not self.creating:
|
||||
f.set_default("material_types", quantity.material_types)
|
||||
|
||||
def objectify(self, form):
|
||||
quantity = super().objectify(form)
|
||||
data = form.validated
|
||||
|
||||
self.set_material_types(quantity, data["material_types"])
|
||||
|
||||
return quantity
|
||||
|
||||
def set_material_types(self, quantity, desired):
|
||||
model = self.app.model
|
||||
session = self.Session()
|
||||
|
||||
current = {mt.uuid.hex: mt for mt in quantity.material_types}
|
||||
|
||||
for mtype in desired:
|
||||
if mtype["uuid"] not in current:
|
||||
mtype = session.get(model.MaterialType, mtype["uuid"])
|
||||
assert mtype
|
||||
quantity.material_types.append(mtype)
|
||||
|
||||
desired = [mtype["uuid"] for mtype in desired]
|
||||
for uuid, mtype in current.items():
|
||||
if uuid not in desired:
|
||||
quantity.material_types.remove(mtype)
|
||||
|
||||
|
||||
def defaults(config, **kwargs):
|
||||
base = globals()
|
||||
|
||||
|
|
@ -384,6 +457,11 @@ def defaults(config, **kwargs):
|
|||
)
|
||||
StandardQuantityView.defaults(config)
|
||||
|
||||
MaterialQuantityView = kwargs.get(
|
||||
"MaterialQuantityView", base["MaterialQuantityView"]
|
||||
)
|
||||
MaterialQuantityView.defaults(config)
|
||||
|
||||
|
||||
def includeme(config):
|
||||
defaults(config)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue