wuttafarm/src/wuttafarm/web/views/quick/eggs.py
Lance Edgar 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

355 lines
11 KiB
Python

# -*- 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/>.
#
################################################################################
"""
Quick Form for "Eggs"
"""
import json
import colander
from deform.widget import SelectWidget
from farmOS.subrequests import Action, Subrequest, SubrequestsBlueprint, Format
from wuttaweb.forms.schema import WuttaDateTime
from wuttaweb.forms.widgets import WuttaDateTimeWidget
from wuttafarm.web.views.quick import QuickFormView
class EggsQuickForm(QuickFormView):
"""
Use this form to record an egg harvest. A harvest log will be
created with standard details filled in.
"""
form_title = "Eggs"
route_slug = "eggs"
url_slug = "eggs"
_layer_assets = None
# TODO: make this configurable?
unit_name = "egg(s)"
def make_quick_form(self):
f = self.make_form(
fields=[
"timestamp",
"count",
"asset",
"notes",
],
labels={
"timestamp": "Date",
"count": "Quantity",
"asset": "Layer Asset",
},
)
# timestamp
f.set_node("timestamp", WuttaDateTime())
f.set_widget("timestamp", WuttaDateTimeWidget(self.request))
f.set_default("timestamp", self.app.make_utc())
# count
f.set_node("count", colander.Integer())
# asset
assets = self.get_layer_assets()
values = [(a["uuid"], a["name"]) for a in assets]
f.set_widget("asset", SelectWidget(values=values))
if len(assets) == 1:
f.set_default("asset", assets[0]["uuid"])
# notes
f.set_widget("notes", "notes")
f.set_required("notes", False)
return f
def get_layer_assets(self):
if self._layer_assets is not None:
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 = []
params = {
"filter[produces_eggs]": 1,
"sort": "name",
}
def normalize(asset):
return {
"uuid": asset["id"],
"name": asset["attributes"]["name"],
"type": asset["type"],
}
result = self.farmos_client.asset.get("animal", params=params)
assets.extend([normalize(a) for a in result["data"]])
result = self.farmos_client.asset.get("group", params=params)
assets.extend([normalize(a) for a in result["data"]])
return assets
def save_quick_form(self, form):
if self.app.is_farmos_wrapper():
return self.save_to_farmos(form)
return self.save_to_wuttafarm(form)
def save_to_farmos(self, form):
data = form.validated
assets = self.get_layer_assets()
assets = {a["uuid"]: a for a in assets}
asset = assets[data["asset"]]
# TODO: make this configurable?
unit_name = self.unit_name
unit = {"data": {"type": "taxonomy_term--unit"}}
new_unit = None
result = self.farmos_client.resource.get(
"taxonomy_term",
"unit",
params={
"filter[name]": unit_name,
},
)
if result["data"]:
unit["data"]["id"] = result["data"][0]["id"]
else:
payload = dict(unit)
payload["data"]["attributes"] = {"name": unit_name}
new_unit = Subrequest(
action=Action.create,
requestId="create-unit",
endpoint="api/taxonomy_term/unit",
body=payload,
)
unit["data"]["id"] = "{{create-unit.body@$.data.id}}"
quantity = {
"data": {
"type": "quantity--standard",
"attributes": {
"measure": "count",
"value": {
"numerator": data["count"],
"denominator": 1,
},
},
"relationships": {
"units": unit,
},
},
}
kw = {}
if new_unit:
kw["waitFor"] = ["create-unit"]
new_quantity = Subrequest(
action=Action.create,
requestId="create-quantity",
endpoint="api/quantity/standard",
body=quantity,
**kw,
)
notes = None
if data["notes"]:
notes = {"value": data["notes"]}
log = {
"data": {
"type": "log--harvest",
"attributes": {
"name": f"Collected {data['count']} {unit_name}",
"timestamp": self.app.localtime(data["timestamp"]).timestamp(),
"notes": notes,
"quick": ["eggs"],
},
"relationships": {
"asset": {
"data": [
{
"id": asset["uuid"],
"type": asset["type"],
},
],
},
"quantity": {
"data": [
{
"id": "{{create-quantity.body@$.data.id}}",
"type": "quantity--standard",
},
],
},
},
},
}
new_log = Subrequest(
action=Action.create,
requestId="create-log",
waitFor=["create-quantity"],
endpoint="api/log/harvest",
body=log,
)
blueprints = [new_quantity, new_log]
if new_unit:
blueprints.insert(0, new_unit)
blueprint = SubrequestsBlueprint.parse_obj(blueprints)
response = self.farmos_client.subrequests.send(blueprint, format=Format.json)
log = json.loads(response["create-log#body{0}"]["body"])
if self.app.is_farmos_mirror():
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"])
)
def includeme(config):
EggsQuickForm.defaults(config)