Compare commits

...

5 commits

Author SHA1 Message Date
af2ea18e1d bump: version 0.7.0 → 0.8.0 2026-03-04 20:43:03 -06:00
23af35842d feat: improve support for exporting quantity, log data
and make the Eggs quick form save to wuttafarm app DB first, then
export to farmOS, if app is running as mirror
2026-03-04 20:36:56 -06:00
609a900f39 feat: show related Quantity records when viewing a Measure 2026-03-04 16:51:26 -06:00
a547188a90 feat: show related Quantity records when viewing a Unit 2026-03-04 16:49:28 -06:00
81fa22bbd8 feat: show link to Log record when viewing Quantity 2026-03-04 14:49:12 -06:00
12 changed files with 600 additions and 44 deletions

View file

@ -5,6 +5,19 @@ All notable changes to WuttaFarm will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## v0.8.0 (2026-03-04)
### Feat
- improve support for exporting quantity, log data
- show related Quantity records when viewing a Measure
- show related Quantity records when viewing a Unit
- show link to Log record when viewing Quantity
### Fix
- bump version requirement for wuttaweb
## v0.7.0 (2026-03-04) ## v0.7.0 (2026-03-04)
### Feat ### Feat

View file

@ -5,7 +5,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "WuttaFarm" name = "WuttaFarm"
version = "0.7.0" version = "0.8.0"
description = "Web app to integrate with and extend farmOS" description = "Web app to integrate with and extend farmOS"
readme = "README.md" readme = "README.md"
authors = [ authors = [

View file

@ -376,6 +376,7 @@ class LogQuantity(model.Base):
quantity = orm.relationship( quantity = orm.relationship(
"Quantity", "Quantity",
foreign_keys=quantity_uuid, foreign_keys=quantity_uuid,
back_populates="_log",
) )

View file

@ -26,6 +26,7 @@ Model definition for Quantities
import sqlalchemy as sa import sqlalchemy as sa
from sqlalchemy import orm from sqlalchemy import orm
from sqlalchemy.ext.declarative import declared_attr from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.ext.associationproxy import association_proxy
from wuttjamaican.db import model from wuttjamaican.db import model
@ -161,6 +162,25 @@ class Quantity(model.Base):
""", """,
) )
_log = orm.relationship(
"LogQuantity",
uselist=False,
cascade="all, delete-orphan",
cascade_backrefs=False,
back_populates="quantity",
)
def make_log_quantity(log):
from wuttafarm.db.model import LogQuantity
return LogQuantity(log=log)
log = association_proxy(
"_log",
"log",
creator=make_log_quantity,
)
def render_as_text(self, config=None): def render_as_text(self, config=None):
measure = str(self.measure or self.measure_id or "") measure = str(self.measure or self.measure_id or "")
value = self.value_numerator / self.value_denominator value = self.value_numerator / self.value_denominator
@ -202,6 +222,7 @@ def add_quantity_proxies(subclass):
Quantity.make_proxy(subclass, "quantity", "units_uuid") Quantity.make_proxy(subclass, "quantity", "units_uuid")
Quantity.make_proxy(subclass, "quantity", "units") Quantity.make_proxy(subclass, "quantity", "units")
Quantity.make_proxy(subclass, "quantity", "label") Quantity.make_proxy(subclass, "quantity", "label")
Quantity.make_proxy(subclass, "quantity", "log")
class StandardQuantity(QuantityMixin, model.Base): class StandardQuantity(QuantityMixin, model.Base):

View file

@ -443,6 +443,138 @@ class StructureAssetImporter(ToFarmOSAsset):
return payload return payload
##############################
# quantity importers
##############################
class ToFarmOSQuantity(ToFarmOS):
"""
Base class for quantity data importer targeting the farmOS API.
"""
farmos_quantity_type = None
supported_fields = [
"uuid",
"measure",
"value_numerator",
"value_denominator",
"label",
"quantity_type_uuid",
"unit_uuid",
]
def get_target_objects(self, **kwargs):
return list(
self.farmos_client.resource.iterate("quantity", self.farmos_quantity_type)
)
def get_target_object(self, key):
# fetch from cache, if applicable
if self.caches_target:
return super().get_target_object(key)
# okay now must fetch via API
if self.get_keys() != ["uuid"]:
raise ValueError("must use uuid key for this to work")
uuid = key[0]
try:
qty = self.farmos_client.resource.get_id(
"quantity", self.farmos_quantity_type, str(uuid)
)
except requests.HTTPError as exc:
if exc.response.status_code == 404:
return None
raise
return qty["data"]
def create_target_object(self, key, source_data):
if source_data.get("__ignoreme__"):
return None
if self.dry_run:
return source_data
payload = self.get_quantity_payload(source_data)
result = self.farmos_client.resource.send(
"quantity", self.farmos_quantity_type, payload
)
normal = self.normalize_target_object(result["data"])
normal["_new_object"] = result["data"]
return normal
def update_target_object(self, quantity, source_data, target_data=None):
if self.dry_run:
return quantity
payload = self.get_quantity_payload(source_data)
payload["id"] = str(source_data["uuid"])
result = self.farmos_client.resource.send(
"quantity", self.farmos_quantity_type, payload
)
return self.normalize_target_object(result["data"])
def normalize_target_object(self, qty):
result = {
"uuid": UUID(qty["id"]),
"measure": qty["attributes"]["measure"],
"value_numerator": qty["attributes"]["value"]["numerator"],
"value_denominator": qty["attributes"]["value"]["denominator"],
"label": qty["attributes"]["label"],
"quantity_type_uuid": UUID(
qty["relationships"]["quantity_type"]["data"]["id"]
),
"unit_uuid": None,
}
if unit := qty["relationships"]["units"]["data"]:
result["unit_uuid"] = UUID(unit["id"])
return result
def get_quantity_payload(self, source_data):
attrs = {}
if "measure" in self.fields:
attrs["measure"] = source_data["measure"]
if "value_numerator" in self.fields and "value_denominator" in self.fields:
attrs["value"] = {
"numerator": source_data["value_numerator"],
"denominator": source_data["value_denominator"],
}
if "label" in self.fields:
attrs["label"] = source_data["label"]
rels = {}
if "quantity_type_uuid" in self.fields:
rels["quantity_type"] = {
"data": {
"id": str(source_data["quantity_type_uuid"]),
"type": "quantity_type--quantity_type",
}
}
if "unit_uuid" in self.fields:
rels["units"] = {
"data": {
"id": str(source_data["unit_uuid"]),
"type": "taxonomy_term--unit",
}
}
payload = {"attributes": attrs, "relationships": rels}
return payload
class StandardQuantityImporter(ToFarmOSQuantity):
model_title = "StandardQuantity"
farmos_quantity_type = "standard"
############################## ##############################
# log importers # log importers
############################## ##############################
@ -464,8 +596,14 @@ class ToFarmOSLog(ToFarmOS):
"status", "status",
"notes", "notes",
"quick", "quick",
"assets",
"quantities",
] ]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.normal = self.app.get_normalizer(self.farmos_client)
def get_target_objects(self, **kwargs): def get_target_objects(self, **kwargs):
result = self.farmos_client.log.get(self.farmos_log_type) result = self.farmos_client.log.get(self.farmos_log_type)
return result["data"] return result["data"]
@ -511,19 +649,18 @@ class ToFarmOSLog(ToFarmOS):
return self.normalize_target_object(result["data"]) return self.normalize_target_object(result["data"])
def normalize_target_object(self, log): def normalize_target_object(self, log):
normal = self.normal.normalize_farmos_log(log)
if notes := log["attributes"]["notes"]:
notes = notes["value"]
return { return {
"uuid": UUID(log["id"]), "uuid": UUID(normal["uuid"]),
"name": log["attributes"]["name"], "name": normal["name"],
"timestamp": self.normalize_datetime(log["attributes"]["timestamp"]), "timestamp": self.app.make_utc(normal["timestamp"]),
"is_movement": log["attributes"]["is_movement"], "is_movement": normal["is_movement"],
"is_group_assignment": log["attributes"]["is_group_assignment"], "is_group_assignment": normal["is_group_assignment"],
"status": log["attributes"]["status"], "status": normal["status"],
"notes": notes, "notes": normal["notes"],
"quick": log["attributes"]["quick"], "quick": normal["quick"],
"assets": [(a["asset_type"], UUID(a["uuid"])) for a in normal["assets"]],
"quantities": [UUID(uuid) for uuid in normal["quantity_uuids"]],
} }
def get_log_payload(self, source_data): def get_log_payload(self, source_data):
@ -542,10 +679,32 @@ class ToFarmOSLog(ToFarmOS):
if "notes" in self.fields: if "notes" in self.fields:
attrs["notes"] = {"value": source_data["notes"]} attrs["notes"] = {"value": source_data["notes"]}
if "quick" in self.fields: if "quick" in self.fields:
attrs["quick"] = {"value": source_data["quick"]} attrs["quick"] = source_data["quick"]
payload = {"attributes": attrs} rels = {}
if "assets" in self.fields:
assets = []
for asset_type, uuid in source_data["assets"]:
assets.append(
{
"type": f"asset--{asset_type}",
"id": str(uuid),
}
)
rels["asset"] = {"data": assets}
if "quantities" in self.fields:
quantities = []
for uuid in source_data["quantities"]:
quantities.append(
{
# TODO: support other quantity types
"type": "quantity--standard",
"id": str(uuid),
}
)
rels["quantity"] = {"data": quantities}
payload = {"attributes": attrs, "relationships": rels}
return payload return payload

View file

@ -104,6 +104,7 @@ class FromWuttaFarmToFarmOS(FromWuttaFarmHandler, ToFarmOSHandler):
importers["PlantType"] = PlantTypeImporter importers["PlantType"] = PlantTypeImporter
importers["PlantAsset"] = PlantAssetImporter importers["PlantAsset"] = PlantAssetImporter
importers["Unit"] = UnitImporter importers["Unit"] = UnitImporter
importers["StandardQuantity"] = StandardQuantityImporter
importers["ActivityLog"] = ActivityLogImporter importers["ActivityLog"] = ActivityLogImporter
importers["HarvestLog"] = HarvestLogImporter importers["HarvestLog"] = HarvestLogImporter
importers["MedicalLog"] = MedicalLogImporter importers["MedicalLog"] = MedicalLogImporter
@ -347,6 +348,49 @@ class StructureAssetImporter(
} }
##############################
# quantity importers
##############################
class FromWuttaFarmQuantity(FromWuttaFarm):
"""
Base class for WuttaFarm -> farmOS quantity importers
"""
supported_fields = [
"uuid",
"measure",
"value_numerator",
"value_denominator",
"label",
"quantity_type_uuid",
"unit_uuid",
]
def normalize_source_object(self, qty):
return {
"uuid": qty.farmos_uuid or self.app.make_true_uuid(),
"measure": qty.measure_id,
"value_numerator": qty.value_numerator,
"value_denominator": qty.value_denominator,
"label": qty.label,
"quantity_type_uuid": qty.quantity_type.farmos_uuid,
"unit_uuid": qty.units.farmos_uuid,
"_src_object": qty,
}
class StandardQuantityImporter(
FromWuttaFarmQuantity, farmos_importing.model.StandardQuantityImporter
):
"""
WuttaFarm farmOS API exporter for Standard Quantities
"""
source_model_class = model.StandardQuantity
############################## ##############################
# log importers # log importers
############################## ##############################
@ -365,6 +409,9 @@ class FromWuttaFarmLog(FromWuttaFarm):
"is_group_assignment", "is_group_assignment",
"status", "status",
"notes", "notes",
"quick",
"assets",
"quantities",
] ]
def normalize_source_object(self, log): def normalize_source_object(self, log):
@ -376,6 +423,9 @@ class FromWuttaFarmLog(FromWuttaFarm):
"is_group_assignment": log.is_group_assignment, "is_group_assignment": log.is_group_assignment,
"status": log.status, "status": log.status,
"notes": log.notes, "notes": log.notes,
"quick": self.config.parse_list(log.quick) if log.quick else [],
"assets": [(a.asset_type, a.farmos_uuid) for a in log.assets],
"quantities": [qty.farmos_uuid for qty in log.quantities],
"_src_object": log, "_src_object": log,
} }

View file

@ -77,6 +77,31 @@ class LogQuick(WuttaSet):
return LogQuickWidget(**kwargs) return LogQuickWidget(**kwargs)
class LogRef(ObjectRef):
"""
Custom schema type for a
:class:`~wuttafarm.db.model.log.Log` reference field.
This is a subclass of
:class:`~wuttaweb:wuttaweb.forms.schema.ObjectRef`.
"""
@property
def model_class(self): # pylint: disable=empty-docstring
""" """
model = self.app.model
return model.Log
def sort_query(self, query): # pylint: disable=empty-docstring
""" """
return query.order_by(self.model_class.message)
def get_object_url(self, obj): # pylint: disable=empty-docstring
""" """
log = obj
return self.request.route_url(f"logs_{log.log_type}.view", uuid=log.uuid)
class FarmOSUnitRef(colander.SchemaType): class FarmOSUnitRef(colander.SchemaType):
def serialize(self, node, appstruct): def serialize(self, node, appstruct):

View file

@ -32,6 +32,7 @@ from wuttaweb.forms.widgets import WuttaDateTimeWidget
from wuttafarm.web.views.farmos import FarmOSMasterView from wuttafarm.web.views.farmos import FarmOSMasterView
from wuttafarm.web.forms.schema import FarmOSUnitRef from wuttafarm.web.forms.schema import FarmOSUnitRef
from wuttafarm.web.grids import ResourceData
class QuantityTypeView(FarmOSMasterView): class QuantityTypeView(FarmOSMasterView):
@ -130,13 +131,15 @@ class QuantityMasterView(FarmOSMasterView):
farmos_quantity_type = None farmos_quantity_type = None
grid_columns = [ grid_columns = [
"drupal_id",
"as_text",
"measure", "measure",
"value", "value",
"unit",
"label", "label",
"changed",
] ]
sort_defaults = ("changed", "desc") sort_defaults = ("drupal_id", "desc")
form_fields = [ form_fields = [
"measure", "measure",
@ -147,20 +150,58 @@ class QuantityMasterView(FarmOSMasterView):
"changed", "changed",
] ]
def get_grid_data(self, columns=None, session=None): def get_farmos_api_includes(self):
result = self.farmos_client.resource.get("quantity", self.farmos_quantity_type) return {"units"}
return [self.normalize_quantity(t) for t in result["data"]]
def get_grid_data(self, **kwargs):
return ResourceData(
self.config,
self.farmos_client,
f"quantity--{self.farmos_quantity_type}",
include=",".join(self.get_farmos_api_includes()),
normalizer=self.normalize_quantity,
)
def configure_grid(self, grid): def configure_grid(self, grid):
g = grid g = grid
super().configure_grid(g) super().configure_grid(g)
# drupal_id
g.set_label("drupal_id", "ID", column_only=True)
# as_text
g.set_renderer("as_text", self.render_as_text_for_grid)
# measure
g.set_renderer("measure", self.render_measure_for_grid)
# value # value
g.set_link("value") g.set_renderer("value", self.render_value_for_grid)
# unit
g.set_renderer("unit", self.render_unit_for_grid)
# changed # changed
g.set_renderer("changed", "datetime") g.set_renderer("changed", "datetime")
def render_as_text_for_grid(self, qty, field, value):
measure = qty["measure"].capitalize()
value = qty["value"]["decimal"]
units = qty["unit"]["name"] if qty["unit"] else "??"
return f"( {measure} ) {value} {units}"
def render_measure_for_grid(self, qty, field, value):
return qty["measure"].capitalize()
def render_unit_for_grid(self, qty, field, value):
unit = qty[field]
if not unit:
return ""
return unit["name"]
def render_value_for_grid(self, qty, field, value):
return qty["value"]["decimal"]
def get_instance(self): def get_instance(self):
quantity = self.farmos_client.resource.get_id( quantity = self.farmos_client.resource.get_id(
"quantity", self.farmos_quantity_type, self.request.matchdict["uuid"] "quantity", self.farmos_quantity_type, self.request.matchdict["uuid"]
@ -187,7 +228,7 @@ class QuantityMasterView(FarmOSMasterView):
def get_instance_title(self, quantity): def get_instance_title(self, quantity):
return quantity["value"] return quantity["value"]
def normalize_quantity(self, quantity): def normalize_quantity(self, quantity, included={}):
if created := quantity["attributes"]["created"]: if created := quantity["attributes"]["created"]:
created = datetime.datetime.fromisoformat(created) created = datetime.datetime.fromisoformat(created)
@ -197,11 +238,37 @@ class QuantityMasterView(FarmOSMasterView):
changed = datetime.datetime.fromisoformat(changed) changed = datetime.datetime.fromisoformat(changed)
changed = self.app.localtime(changed) changed = self.app.localtime(changed)
quantity_type_object = None
quantity_type_uuid = None
unit_object = None
unit_uuid = None
if relationships := quantity["relationships"]:
if quantity_type := relationships["quantity_type"]["data"]:
quantity_type_uuid = quantity_type["id"]
quantity_type_object = {
"uuid": quantity_type_uuid,
"type": "quantity_type--quantity_type",
}
if unit := relationships["units"]["data"]:
unit_uuid = unit["id"]
if unit := included.get(unit_uuid):
unit_object = {
"uuid": unit_uuid,
"type": "taxonomy_term--unit",
"name": unit["attributes"]["name"],
}
return { return {
"uuid": quantity["id"], "uuid": quantity["id"],
"drupal_id": quantity["attributes"]["drupal_internal__id"], "drupal_id": quantity["attributes"]["drupal_internal__id"],
"quantity_type": quantity_type_object,
"quantity_type_uuid": quantity_type_uuid,
"measure": quantity["attributes"]["measure"], "measure": quantity["attributes"]["measure"],
"value": quantity["attributes"]["value"], "value": quantity["attributes"]["value"],
"unit": unit_object,
"unit_uuid": unit_uuid,
"label": quantity["attributes"]["label"] or colander.null, "label": quantity["attributes"]["label"] or colander.null,
"created": created, "created": created,
"changed": changed, "changed": changed,

View file

@ -29,7 +29,7 @@ from wuttaweb.db import Session
from wuttafarm.web.views import WuttaFarmMasterView from wuttafarm.web.views import WuttaFarmMasterView
from wuttafarm.db.model import QuantityType, Quantity, StandardQuantity from wuttafarm.db.model import QuantityType, Quantity, StandardQuantity
from wuttafarm.web.forms.schema import UnitRef from wuttafarm.web.forms.schema import UnitRef, LogRef
def get_quantity_type_enum(config): def get_quantity_type_enum(config):
@ -119,6 +119,7 @@ class QuantityMasterView(WuttaFarmMasterView):
"value", "value",
"units", "units",
"label", "label",
"log",
"drupal_id", "drupal_id",
"farmos_uuid", "farmos_uuid",
] ]
@ -231,6 +232,13 @@ class QuantityMasterView(WuttaFarmMasterView):
# TODO: ugh # TODO: ugh
f.set_default("units", quantity.quantity.units) f.set_default("units", quantity.quantity.units)
# log
if self.creating or self.editing:
f.remove("log")
else:
f.set_node("log", LogRef(self.request))
f.set_default("log", quantity.log)
def get_xref_buttons(self, quantity): def get_xref_buttons(self, quantity):
buttons = super().get_xref_buttons(quantity) buttons = super().get_xref_buttons(quantity)

View file

@ -28,6 +28,7 @@ import logging
from pyramid.renderers import render_to_response from pyramid.renderers import render_to_response
from wuttaweb.views import View from wuttaweb.views import View
from wuttaweb.db import Session
from wuttafarm.web.util import get_farmos_client_for_user from wuttafarm.web.util import get_farmos_client_for_user
@ -40,6 +41,8 @@ class QuickFormView(View):
Base class for quick form views. Base class for quick form views.
""" """
Session = Session
def __init__(self, request, context=None): def __init__(self, request, context=None):
super().__init__(request, context=context) super().__init__(request, context=context)
self.farmos_client = get_farmos_client_for_user(self.request) self.farmos_client = get_farmos_client_for_user(self.request)

View file

@ -34,7 +34,6 @@ from wuttaweb.forms.schema import WuttaDateTime
from wuttaweb.forms.widgets import WuttaDateTimeWidget from wuttaweb.forms.widgets import WuttaDateTimeWidget
from wuttafarm.web.views.quick import QuickFormView from wuttafarm.web.views.quick import QuickFormView
from wuttafarm.web.util import get_farmos_client_for_user
class EggsQuickForm(QuickFormView): class EggsQuickForm(QuickFormView):
@ -49,6 +48,9 @@ class EggsQuickForm(QuickFormView):
_layer_assets = None _layer_assets = None
# TODO: make this configurable?
unit_name = "egg(s)"
def make_quick_form(self): def make_quick_form(self):
f = self.make_form( f = self.make_form(
fields=[ fields=[
@ -89,6 +91,47 @@ class EggsQuickForm(QuickFormView):
if self._layer_assets is not None: if self._layer_assets is not None:
return self._layer_assets return self._layer_assets
if self.app.is_farmos_wrapper():
assets = self.get_layer_assets_from_farmos()
else:
assets = self.get_layer_assets_from_wuttafarm()
assets.sort(key=lambda a: a["name"])
self._layer_assets = assets
return assets
def get_layer_assets_from_wuttafarm(self):
model = self.app.model
session = self.Session()
assets = []
def normalize(asset):
asset_type = asset.__wutta_hint__["farmos_asset_type"]
return {
"uuid": str(asset.farmos_uuid),
"name": asset.asset_name,
"type": f"asset--{asset_type}",
}
query = (
session.query(model.AnimalAsset)
.join(model.Asset)
.filter(model.AnimalAsset.produces_eggs == True)
.order_by(model.Asset.asset_name)
)
assets.extend([normalize(a) for a in query])
query = (
session.query(model.GroupAsset)
.join(model.Asset)
.filter(model.GroupAsset.produces_eggs == True)
.order_by(model.Asset.asset_name)
)
assets.extend([normalize(a) for a in query])
return assets
def get_layer_assets_from_farmos(self):
assets = [] assets = []
params = { params = {
"filter[produces_eggs]": 1, "filter[produces_eggs]": 1,
@ -108,24 +151,14 @@ class EggsQuickForm(QuickFormView):
result = self.farmos_client.asset.get("group", params=params) result = self.farmos_client.asset.get("group", params=params)
assets.extend([normalize(a) for a in result["data"]]) assets.extend([normalize(a) for a in result["data"]])
assets.sort(key=lambda a: a["name"])
self._layer_assets = assets
return assets return assets
def save_quick_form(self, form): def save_quick_form(self, form):
response = self.save_to_farmos(form) if self.app.is_farmos_wrapper():
log = json.loads(response["create-log#body{0}"]["body"]) return self.save_to_farmos(form)
if self.app.is_farmos_mirror(): return self.save_to_wuttafarm(form)
quantity = json.loads(response["create-quantity"]["body"])
client = get_farmos_client_for_user(self.request)
self.app.auto_sync_from_farmos(
quantity["data"], "StandardQuantity", client=client
)
self.app.auto_sync_from_farmos(log["data"], "HarvestLog", client=client)
return log
def save_to_farmos(self, form): def save_to_farmos(self, form):
data = form.validated data = form.validated
@ -135,7 +168,7 @@ class EggsQuickForm(QuickFormView):
asset = assets[data["asset"]] asset = assets[data["asset"]]
# TODO: make this configurable? # TODO: make this configurable?
unit_name = "egg(s)" unit_name = self.unit_name
unit = {"data": {"type": "taxonomy_term--unit"}} unit = {"data": {"type": "taxonomy_term--unit"}}
new_unit = None new_unit = None
@ -234,13 +267,87 @@ class EggsQuickForm(QuickFormView):
blueprints.insert(0, new_unit) blueprints.insert(0, new_unit)
blueprint = SubrequestsBlueprint.parse_obj(blueprints) blueprint = SubrequestsBlueprint.parse_obj(blueprints)
response = self.farmos_client.subrequests.send(blueprint, format=Format.json) response = self.farmos_client.subrequests.send(blueprint, format=Format.json)
return response
def redirect_after_save(self, result): log = json.loads(response["create-log#body{0}"]["body"])
return self.redirect(
self.request.route_url( if self.app.is_farmos_mirror():
"farmos_logs_harvest.view", uuid=result["data"]["id"] if new_unit:
unit = json.loads(response["create-unit"]["body"])
self.app.auto_sync_from_farmos(
unit["data"], "Unit", client=self.farmos_client
)
quantity = json.loads(response["create-quantity"]["body"])
self.app.auto_sync_from_farmos(
quantity["data"], "StandardQuantity", client=self.farmos_client
) )
self.app.auto_sync_from_farmos(
log["data"], "HarvestLog", client=self.farmos_client
)
return log
def save_to_wuttafarm(self, form):
model = self.app.model
session = self.Session()
data = form.validated
asset = (
session.query(model.Asset)
.filter(model.Asset.farmos_uuid == data["asset"])
.one()
)
# TODO: make this configurable?
unit_name = self.unit_name
new_unit = False
unit = session.query(model.Unit).filter(model.Unit.name == unit_name).first()
if not unit:
unit = model.Unit(name=unit_name)
session.add(unit)
new_unit = True
quantity = model.StandardQuantity(
quantity_type_id="standard",
measure_id="count",
value_numerator=data["count"],
value_denominator=1,
units=unit,
)
session.add(quantity)
log = model.HarvestLog(
log_type="harvest",
message=f"Collected {data['count']} {unit_name}",
timestamp=self.app.make_utc(data["timestamp"]),
notes=data["notes"] or None,
quick="eggs",
status="done",
)
session.add(log)
log.assets.append(asset)
log.quantities.append(quantity.quantity)
log.owners.append(self.request.user)
session.flush()
if self.app.is_farmos_mirror():
if new_unit:
self.app.auto_sync_to_farmos(unit, client=self.farmos_client)
self.app.auto_sync_to_farmos(quantity, client=self.farmos_client)
self.app.auto_sync_to_farmos(log, client=self.farmos_client)
return log
def redirect_after_save(self, log):
model = self.app.model
if isinstance(log, model.HarvestLog):
return self.redirect(
self.request.route_url("logs_harvest.view", uuid=log.uuid)
)
return self.redirect(
self.request.route_url("farmos_logs_harvest.view", uuid=log["data"]["id"])
) )

View file

@ -24,7 +24,7 @@ Master view for Units
""" """
from wuttafarm.web.views import WuttaFarmMasterView from wuttafarm.web.views import WuttaFarmMasterView
from wuttafarm.db.model import Measure, Unit from wuttafarm.db.model import Measure, Unit, Quantity
class MeasureView(WuttaFarmMasterView): class MeasureView(WuttaFarmMasterView):
@ -52,6 +52,26 @@ class MeasureView(WuttaFarmMasterView):
"drupal_id", "drupal_id",
] ]
has_rows = True
row_model_class = Quantity
rows_viewable = True
row_labels = {
"quantity_type_id": "Quantity Type ID",
"measure_id": "Measure ID",
}
row_grid_columns = [
"drupal_id",
"as_text",
"quantity_type",
"value",
"units",
"label",
]
rows_sort_defaults = ("drupal_id", "desc")
def configure_grid(self, grid): def configure_grid(self, grid):
g = grid g = grid
super().configure_grid(g) super().configure_grid(g)
@ -59,6 +79,37 @@ class MeasureView(WuttaFarmMasterView):
# name # name
g.set_link("name") g.set_link("name")
def get_row_grid_data(self, measure):
model = self.app.model
session = self.Session()
return session.query(model.Quantity).filter(model.Quantity.measure == measure)
def configure_row_grid(self, grid):
g = grid
super().configure_row_grid(g)
# drupal_id
g.set_label("drupal_id", "ID", column_only=True)
# as_text
g.set_renderer("as_text", self.render_as_text_for_grid)
g.set_link("as_text")
# value
g.set_renderer("value", self.render_value_for_grid)
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_row_action_url_view(self, quantity, i):
return self.request.route_url(
f"quantities_{quantity.quantity_type_id}.view", uuid=quantity.uuid
)
@classmethod @classmethod
def defaults(cls, config): def defaults(cls, config):
""" """ """ """
@ -104,6 +155,26 @@ class UnitView(WuttaFarmMasterView):
"farmos_uuid", "farmos_uuid",
] ]
has_rows = True
row_model_class = Quantity
rows_viewable = True
row_labels = {
"quantity_type_id": "Quantity Type ID",
"measure_id": "Measure ID",
}
row_grid_columns = [
"drupal_id",
"as_text",
"quantity_type",
"measure",
"value",
"label",
]
rows_sort_defaults = ("drupal_id", "desc")
def configure_grid(self, grid): def configure_grid(self, grid):
g = grid g = grid
super().configure_grid(g) super().configure_grid(g)
@ -131,6 +202,37 @@ class UnitView(WuttaFarmMasterView):
return buttons return buttons
def get_row_grid_data(self, unit):
model = self.app.model
session = self.Session()
return session.query(model.Quantity).filter(model.Quantity.units == unit)
def configure_row_grid(self, grid):
g = grid
super().configure_row_grid(g)
# drupal_id
g.set_label("drupal_id", "ID", column_only=True)
# as_text
g.set_renderer("as_text", self.render_as_text_for_grid)
g.set_link("as_text")
# value
g.set_renderer("value", self.render_value_for_grid)
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_row_action_url_view(self, quantity, i):
return self.request.route_url(
f"quantities_{quantity.quantity_type_id}.view", uuid=quantity.uuid
)
def defaults(config, **kwargs): def defaults(config, **kwargs):
base = globals() base = globals()