feat: add common normalizer to simplify code in view, importer etc.
only the "log" normalizer exists so far, but will add more..
This commit is contained in:
parent
1a6870b8fe
commit
e7ef5c3d32
9 changed files with 331 additions and 208 deletions
|
|
@ -56,24 +56,38 @@ class WuttaFarmAppHandler(base.AppHandler):
|
||||||
Returns the integration mode for farmOS, i.e. to control the
|
Returns the integration mode for farmOS, i.e. to control the
|
||||||
app's behavior regarding that.
|
app's behavior regarding that.
|
||||||
"""
|
"""
|
||||||
handler = self.get_farmos_handler()
|
enum = self.enum
|
||||||
return handler.get_farmos_integration_mode()
|
return self.config.get(
|
||||||
|
f"{self.appname}.farmos_integration_mode",
|
||||||
|
default=enum.FARMOS_INTEGRATION_MODE_WRAPPER,
|
||||||
|
)
|
||||||
|
|
||||||
def is_farmos_mirror(self):
|
def is_farmos_mirror(self):
|
||||||
"""
|
"""
|
||||||
Returns ``True`` if the app is configured in "mirror"
|
Returns ``True`` if the app is configured in "mirror"
|
||||||
integration mode with regard to farmOS.
|
integration mode with regard to farmOS.
|
||||||
"""
|
"""
|
||||||
handler = self.get_farmos_handler()
|
enum = self.enum
|
||||||
return handler.is_farmos_mirror()
|
mode = self.get_farmos_integration_mode()
|
||||||
|
return mode == enum.FARMOS_INTEGRATION_MODE_MIRROR
|
||||||
|
|
||||||
def is_farmos_wrapper(self):
|
def is_farmos_wrapper(self):
|
||||||
"""
|
"""
|
||||||
Returns ``True`` if the app is configured in "wrapper"
|
Returns ``True`` if the app is configured in "wrapper"
|
||||||
integration mode with regard to farmOS.
|
integration mode with regard to farmOS.
|
||||||
"""
|
"""
|
||||||
handler = self.get_farmos_handler()
|
enum = self.enum
|
||||||
return handler.is_farmos_wrapper()
|
mode = self.get_farmos_integration_mode()
|
||||||
|
return mode == enum.FARMOS_INTEGRATION_MODE_WRAPPER
|
||||||
|
|
||||||
|
def is_standalone(self):
|
||||||
|
"""
|
||||||
|
Returns ``True`` if the app is configured in "standalone" mode
|
||||||
|
with regard to farmOS.
|
||||||
|
"""
|
||||||
|
enum = self.enum
|
||||||
|
mode = self.get_farmos_integration_mode()
|
||||||
|
return mode == enum.FARMOS_INTEGRATION_MODE_NONE
|
||||||
|
|
||||||
def get_farmos_url(self, *args, **kwargs):
|
def get_farmos_url(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
@ -109,7 +123,20 @@ class WuttaFarmAppHandler(base.AppHandler):
|
||||||
handler = self.get_farmos_handler()
|
handler = self.get_farmos_handler()
|
||||||
return handler.is_farmos_4x(*args, **kwargs)
|
return handler.is_farmos_4x(*args, **kwargs)
|
||||||
|
|
||||||
def export_to_farmos(self, obj, require=True):
|
def get_normalizer(self, farmos_client=None):
|
||||||
|
"""
|
||||||
|
Get the configured farmOS integration handler.
|
||||||
|
|
||||||
|
:rtype: :class:`~wuttafarm.farmos.FarmOSHandler`
|
||||||
|
"""
|
||||||
|
spec = self.config.get(
|
||||||
|
f"{self.appname}.normalizer_spec",
|
||||||
|
default="wuttafarm.normal:Normalizer",
|
||||||
|
)
|
||||||
|
factory = self.load_object(spec)
|
||||||
|
return factory(self.config, farmos_client)
|
||||||
|
|
||||||
|
def auto_sync_to_farmos(self, obj, model_name=None, require=True):
|
||||||
"""
|
"""
|
||||||
Export the given object to farmOS, using configured handler.
|
Export the given object to farmOS, using configured handler.
|
||||||
|
|
||||||
|
|
@ -127,7 +154,8 @@ class WuttaFarmAppHandler(base.AppHandler):
|
||||||
"""
|
"""
|
||||||
handler = self.app.get_import_handler("export.to_farmos.from_wuttafarm")
|
handler = self.app.get_import_handler("export.to_farmos.from_wuttafarm")
|
||||||
|
|
||||||
model_name = type(obj).__name__
|
if not model_name:
|
||||||
|
model_name = type(obj).__name__
|
||||||
if model_name not in handler.importers:
|
if model_name not in handler.importers:
|
||||||
if require:
|
if require:
|
||||||
raise ValueError(f"no exporter found for {model_name}")
|
raise ValueError(f"no exporter found for {model_name}")
|
||||||
|
|
@ -141,6 +169,37 @@ class WuttaFarmAppHandler(base.AppHandler):
|
||||||
normal = importer.normalize_source_object(obj)
|
normal = importer.normalize_source_object(obj)
|
||||||
importer.process_data(source_data=[normal])
|
importer.process_data(source_data=[normal])
|
||||||
|
|
||||||
|
def auto_sync_from_farmos(self, obj, model_name, require=True):
|
||||||
|
"""
|
||||||
|
Import the given object from farmOS, using configured handler.
|
||||||
|
|
||||||
|
:param obj: Any data record from farmOS.
|
||||||
|
|
||||||
|
:param model_name': Model name for the importer to use,
|
||||||
|
e.g. ``"AnimalAsset"``.
|
||||||
|
|
||||||
|
:param require: If true, this will *require* the import
|
||||||
|
handler to support objects of the given type. If false,
|
||||||
|
then nothing will happen / import is silently skipped when
|
||||||
|
there is no such importer.
|
||||||
|
"""
|
||||||
|
handler = self.app.get_import_handler("import.to_wuttafarm.from_farmos")
|
||||||
|
|
||||||
|
if model_name not in handler.importers:
|
||||||
|
if require:
|
||||||
|
raise ValueError(f"no importer found for {model_name}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# nb. begin txn to establish the API client
|
||||||
|
# TODO: should probably use current user oauth2 token instead
|
||||||
|
# of always making a new one here, which is what happens IIUC
|
||||||
|
handler.begin_source_transaction()
|
||||||
|
with self.short_session(commit=True) as session:
|
||||||
|
handler.target_session = session
|
||||||
|
importer = handler.get_importer(model_name, caches_target=False)
|
||||||
|
normal = importer.normalize_source_object(obj)
|
||||||
|
importer.process_data(source_data=[normal])
|
||||||
|
|
||||||
|
|
||||||
class WuttaFarmAppProvider(base.AppProvider):
|
class WuttaFarmAppProvider(base.AppProvider):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -34,35 +34,6 @@ class FarmOSHandler(GenericHandler):
|
||||||
:term:`handler`.
|
:term:`handler`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get_farmos_integration_mode(self):
|
|
||||||
"""
|
|
||||||
Returns the integration mode for farmOS, i.e. to control the
|
|
||||||
app's behavior regarding that.
|
|
||||||
"""
|
|
||||||
enum = self.app.enum
|
|
||||||
return self.config.get(
|
|
||||||
f"{self.app.appname}.farmos_integration_mode",
|
|
||||||
default=enum.FARMOS_INTEGRATION_MODE_WRAPPER,
|
|
||||||
)
|
|
||||||
|
|
||||||
def is_farmos_mirror(self):
|
|
||||||
"""
|
|
||||||
Returns ``True`` if the app is configured in "mirror"
|
|
||||||
integration mode with regard to farmOS.
|
|
||||||
"""
|
|
||||||
enum = self.app.enum
|
|
||||||
mode = self.get_farmos_integration_mode()
|
|
||||||
return mode == enum.FARMOS_INTEGRATION_MODE_MIRROR
|
|
||||||
|
|
||||||
def is_farmos_wrapper(self):
|
|
||||||
"""
|
|
||||||
Returns ``True`` if the app is configured in "wrapper"
|
|
||||||
integration mode with regard to farmOS.
|
|
||||||
"""
|
|
||||||
enum = self.app.enum
|
|
||||||
mode = self.get_farmos_integration_mode()
|
|
||||||
return mode == enum.FARMOS_INTEGRATION_MODE_WRAPPER
|
|
||||||
|
|
||||||
def get_farmos_client(self, hostname=None, **kwargs):
|
def get_farmos_client(self, hostname=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Returns a new farmOS API client.
|
Returns a new farmOS API client.
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ class FromFarmOSHandler(ImportHandler):
|
||||||
token = self.get_farmos_oauth2_token()
|
token = self.get_farmos_oauth2_token()
|
||||||
self.farmos_client = self.app.get_farmos_client(token=token)
|
self.farmos_client = self.app.get_farmos_client(token=token)
|
||||||
self.farmos_4x = self.app.is_farmos_4x(self.farmos_client)
|
self.farmos_4x = self.app.is_farmos_4x(self.farmos_client)
|
||||||
|
self.normal = self.app.get_normalizer(self.farmos_client)
|
||||||
|
|
||||||
def get_farmos_oauth2_token(self):
|
def get_farmos_oauth2_token(self):
|
||||||
|
|
||||||
|
|
@ -76,6 +77,7 @@ class FromFarmOSHandler(ImportHandler):
|
||||||
kwargs = super().get_importer_kwargs(key, **kwargs)
|
kwargs = super().get_importer_kwargs(key, **kwargs)
|
||||||
kwargs["farmos_client"] = self.farmos_client
|
kwargs["farmos_client"] = self.farmos_client
|
||||||
kwargs["farmos_4x"] = self.farmos_4x
|
kwargs["farmos_4x"] = self.farmos_4x
|
||||||
|
kwargs["normal"] = self.normal
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -981,33 +983,25 @@ class LogImporterBase(FromFarmOS, ToWutta):
|
||||||
def get_source_objects(self):
|
def get_source_objects(self):
|
||||||
""" """
|
""" """
|
||||||
log_type = self.get_farmos_log_type()
|
log_type = self.get_farmos_log_type()
|
||||||
result = self.farmos_client.log.get(log_type)
|
return list(self.farmos_client.log.iterate(log_type))
|
||||||
return result["data"]
|
|
||||||
|
|
||||||
def get_asset_type(self, asset):
|
|
||||||
return asset["type"].split("--")[1]
|
|
||||||
|
|
||||||
def normalize_source_object(self, log):
|
def normalize_source_object(self, log):
|
||||||
""" """
|
""" """
|
||||||
if notes := log["attributes"]["notes"]:
|
data = self.normal.normalize_farmos_log(log)
|
||||||
notes = notes["value"]
|
|
||||||
|
data["farmos_uuid"] = UUID(data.pop("uuid"))
|
||||||
|
data["message"] = data.pop("name")
|
||||||
|
data["timestamp"] = self.app.make_utc(data["timestamp"])
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
data["log_type"] = self.get_farmos_log_type()
|
||||||
|
|
||||||
assets = None
|
|
||||||
if "assets" in self.fields:
|
if "assets" in self.fields:
|
||||||
assets = []
|
data["assets"] = [
|
||||||
for asset in log["relationships"]["asset"]["data"]:
|
(a["asset_type"], UUID(a["uuid"])) for a in data["assets"]
|
||||||
assets.append((self.get_asset_type(asset), UUID(asset["id"])))
|
]
|
||||||
|
|
||||||
return {
|
return data
|
||||||
"farmos_uuid": UUID(log["id"]),
|
|
||||||
"drupal_id": log["attributes"]["drupal_internal__id"],
|
|
||||||
"log_type": self.get_farmos_log_type(),
|
|
||||||
"message": log["attributes"]["name"],
|
|
||||||
"timestamp": self.normalize_datetime(log["attributes"]["timestamp"]),
|
|
||||||
"notes": notes,
|
|
||||||
"status": log["attributes"]["status"],
|
|
||||||
"assets": assets,
|
|
||||||
}
|
|
||||||
|
|
||||||
def normalize_target_object(self, log):
|
def normalize_target_object(self, log):
|
||||||
data = super().normalize_target_object(log)
|
data = super().normalize_target_object(log)
|
||||||
|
|
@ -1183,6 +1177,28 @@ class QuantityImporterBase(FromFarmOS, ToWutta):
|
||||||
result = self.farmos_client.resource.get("quantity", quantity_type)
|
result = self.farmos_client.resource.get("quantity", quantity_type)
|
||||||
return result["data"]
|
return result["data"]
|
||||||
|
|
||||||
|
def get_quantity_type_by_farmos_uuid(self, uuid):
|
||||||
|
if hasattr(self, "quantity_types_by_farmos_uuid"):
|
||||||
|
return self.quantity_types_by_farmos_uuid.get(UUID(uuid))
|
||||||
|
|
||||||
|
model = self.app.model
|
||||||
|
return (
|
||||||
|
self.target_session.query(model.QuantityType)
|
||||||
|
.filter(model.QuantityType.farmos_uuid == uuid)
|
||||||
|
.one()
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_unit_by_farmos_uuid(self, uuid):
|
||||||
|
if hasattr(self, "units_by_farmos_uuid"):
|
||||||
|
return self.units_by_farmos_uuid.get(UUID(uuid))
|
||||||
|
|
||||||
|
model = self.app.model
|
||||||
|
return (
|
||||||
|
self.target_session.query(model.Unit)
|
||||||
|
.filter(model.Unit.farmos_uuid == uuid)
|
||||||
|
.one()
|
||||||
|
)
|
||||||
|
|
||||||
def normalize_source_object(self, quantity):
|
def normalize_source_object(self, quantity):
|
||||||
""" """
|
""" """
|
||||||
quantity_type_id = None
|
quantity_type_id = None
|
||||||
|
|
@ -1191,16 +1207,14 @@ class QuantityImporterBase(FromFarmOS, ToWutta):
|
||||||
|
|
||||||
if quantity_type := relationships.get("quantity_type"):
|
if quantity_type := relationships.get("quantity_type"):
|
||||||
if quantity_type["data"]:
|
if quantity_type["data"]:
|
||||||
if wf_quantity_type := self.quantity_types_by_farmos_uuid.get(
|
if wf_quantity_type := self.get_quantity_type_by_farmos_uuid(
|
||||||
UUID(quantity_type["data"]["id"])
|
quantity_type["data"]["id"]
|
||||||
):
|
):
|
||||||
quantity_type_id = wf_quantity_type.drupal_id
|
quantity_type_id = wf_quantity_type.drupal_id
|
||||||
|
|
||||||
if units := relationships.get("units"):
|
if units := relationships.get("units"):
|
||||||
if units["data"]:
|
if units["data"]:
|
||||||
if wf_unit := self.units_by_farmos_uuid.get(
|
if wf_unit := self.get_unit_by_farmos_uuid(units["data"]["id"]):
|
||||||
UUID(units["data"]["id"])
|
|
||||||
):
|
|
||||||
units_uuid = wf_unit.uuid
|
units_uuid = wf_unit.uuid
|
||||||
|
|
||||||
if not quantity_type_id:
|
if not quantity_type_id:
|
||||||
|
|
|
||||||
199
src/wuttafarm/normal.py
Normal file
199
src/wuttafarm/normal.py
Normal file
|
|
@ -0,0 +1,199 @@
|
||||||
|
# -*- 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/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
Data normalizer for WuttaFarm / farmOS
|
||||||
|
"""
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from wuttjamaican.app import GenericHandler
|
||||||
|
|
||||||
|
|
||||||
|
class Normalizer(GenericHandler):
|
||||||
|
"""
|
||||||
|
Base class and default implementation for the global data
|
||||||
|
normalizer. This should be used for normalizing records from
|
||||||
|
WuttaFarm and/or farmOS.
|
||||||
|
|
||||||
|
The point here is to have a single place to put the normalization
|
||||||
|
logic, and let it be another thing which can be customized via
|
||||||
|
subclass.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_farmos_units = None
|
||||||
|
_farmos_measures = None
|
||||||
|
|
||||||
|
def __init__(self, config, farmos_client=None):
|
||||||
|
super().__init__(config)
|
||||||
|
self.farmos_client = farmos_client
|
||||||
|
|
||||||
|
def get_farmos_measures(self):
|
||||||
|
if self._farmos_measures:
|
||||||
|
return self._farmos_measures
|
||||||
|
|
||||||
|
measures = {}
|
||||||
|
response = self.farmos_client.session.get(
|
||||||
|
self.app.get_farmos_url("/api/quantity/standard/resource/schema")
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
for measure in data["definitions"]["attributes"]["properties"]["measure"][
|
||||||
|
"oneOf"
|
||||||
|
]:
|
||||||
|
measures[measure["const"]] = measure["title"]
|
||||||
|
|
||||||
|
self._farmos_measures = measures
|
||||||
|
return self._farmos_measures
|
||||||
|
|
||||||
|
def get_farmos_measure_name(self, measure_id):
|
||||||
|
measures = self.get_farmos_measures()
|
||||||
|
return measures[measure_id]
|
||||||
|
|
||||||
|
def get_farmos_unit(self, uuid):
|
||||||
|
units = self.get_farmos_units()
|
||||||
|
return units[uuid]
|
||||||
|
|
||||||
|
def get_farmos_units(self):
|
||||||
|
if self._farmos_units:
|
||||||
|
return self._farmos_units
|
||||||
|
|
||||||
|
units = {}
|
||||||
|
result = self.farmos_client.resource.get("taxonomy_term", "unit")
|
||||||
|
for unit in result["data"]:
|
||||||
|
units[unit["id"]] = unit
|
||||||
|
|
||||||
|
self._farmos_units = units
|
||||||
|
return self._farmos_units
|
||||||
|
|
||||||
|
def normalize_farmos_log(self, log, included={}):
|
||||||
|
|
||||||
|
if timestamp := log["attributes"]["timestamp"]:
|
||||||
|
timestamp = datetime.datetime.fromisoformat(timestamp)
|
||||||
|
timestamp = self.app.localtime(timestamp)
|
||||||
|
|
||||||
|
if notes := log["attributes"]["notes"]:
|
||||||
|
notes = notes["value"]
|
||||||
|
|
||||||
|
log_type_object = {}
|
||||||
|
log_type_uuid = None
|
||||||
|
asset_objects = []
|
||||||
|
quantity_objects = []
|
||||||
|
quantity_uuids = []
|
||||||
|
owner_objects = []
|
||||||
|
owner_uuids = []
|
||||||
|
if relationships := log.get("relationships"):
|
||||||
|
|
||||||
|
if log_type := relationships.get("log_type"):
|
||||||
|
log_type_uuid = log_type["data"]["id"]
|
||||||
|
if log_type := included.get(log_type_uuid):
|
||||||
|
log_type_object = {
|
||||||
|
"uuid": log_type["id"],
|
||||||
|
"name": log_type["attributes"]["label"],
|
||||||
|
}
|
||||||
|
|
||||||
|
if assets := relationships.get("asset"):
|
||||||
|
for asset in assets["data"]:
|
||||||
|
asset_object = {
|
||||||
|
"uuid": asset["id"],
|
||||||
|
"type": asset["type"],
|
||||||
|
"asset_type": asset["type"].split("--")[1],
|
||||||
|
}
|
||||||
|
if asset := included.get(asset["id"]):
|
||||||
|
attrs = asset["attributes"]
|
||||||
|
rels = asset["relationships"]
|
||||||
|
asset_object.update(
|
||||||
|
{
|
||||||
|
"drupal_id": attrs["drupal_internal__id"],
|
||||||
|
"name": attrs["name"],
|
||||||
|
"is_location": attrs["is_location"],
|
||||||
|
"is_fixed": attrs["is_fixed"],
|
||||||
|
"archived": attrs["archived"],
|
||||||
|
"notes": attrs["notes"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
asset_objects.append(asset_object)
|
||||||
|
|
||||||
|
if quantities := relationships.get("quantity"):
|
||||||
|
for quantity in quantities["data"]:
|
||||||
|
quantity_uuid = quantity["id"]
|
||||||
|
quantity_uuids.append(quantity_uuid)
|
||||||
|
if quantity := included.get(quantity_uuid):
|
||||||
|
attrs = quantity["attributes"]
|
||||||
|
rels = quantity["relationships"]
|
||||||
|
value = attrs["value"]
|
||||||
|
|
||||||
|
unit_uuid = rels["units"]["data"]["id"]
|
||||||
|
unit = self.get_farmos_unit(unit_uuid)
|
||||||
|
|
||||||
|
measure_id = attrs["measure"]
|
||||||
|
|
||||||
|
quantity_objects.append(
|
||||||
|
{
|
||||||
|
"uuid": quantity["id"],
|
||||||
|
"drupal_id": attrs["drupal_internal__id"],
|
||||||
|
"quantity_type_uuid": rels["quantity_type"]["data"][
|
||||||
|
"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
|
||||||
|
),
|
||||||
|
"value_numerator": value["numerator"],
|
||||||
|
"value_decimal": value["decimal"],
|
||||||
|
"value_denominator": value["denominator"],
|
||||||
|
"unit_uuid": unit_uuid,
|
||||||
|
"unit_name": unit["attributes"]["name"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if owners := relationships.get("owner"):
|
||||||
|
for user in owners["data"]:
|
||||||
|
user_uuid = user["id"]
|
||||||
|
owner_uuids.append(user_uuid)
|
||||||
|
if user := included.get(user_uuid):
|
||||||
|
owner_objects.append(
|
||||||
|
{
|
||||||
|
"uuid": user["id"],
|
||||||
|
"name": user["attributes"]["name"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"uuid": log["id"],
|
||||||
|
"drupal_id": log["attributes"]["drupal_internal__id"],
|
||||||
|
"log_type_uuid": log_type_uuid,
|
||||||
|
"log_type": log_type_object,
|
||||||
|
"name": log["attributes"]["name"],
|
||||||
|
"timestamp": timestamp,
|
||||||
|
"assets": asset_objects,
|
||||||
|
"quantities": quantity_objects,
|
||||||
|
"quantity_uuids": quantity_uuids,
|
||||||
|
"is_group_assignment": log["attributes"]["is_group_assignment"],
|
||||||
|
"quick": log["attributes"]["quick"],
|
||||||
|
"status": log["attributes"]["status"],
|
||||||
|
"notes": notes,
|
||||||
|
"owners": owner_objects,
|
||||||
|
"owner_uuids": owner_uuids,
|
||||||
|
}
|
||||||
|
|
@ -23,12 +23,9 @@
|
||||||
View for farmOS Harvest Logs
|
View for farmOS Harvest Logs
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
import colander
|
|
||||||
from webhelpers2.html import tags
|
from webhelpers2.html import tags
|
||||||
|
|
||||||
from wuttaweb.forms.schema import WuttaDateTime, WuttaDictEnum
|
from wuttaweb.forms.schema import WuttaDateTime, WuttaDictEnum, Notes
|
||||||
from wuttaweb.forms.widgets import WuttaDateTimeWidget
|
from wuttaweb.forms.widgets import WuttaDateTimeWidget
|
||||||
|
|
||||||
from wuttafarm.web.views.farmos import FarmOSMasterView
|
from wuttafarm.web.views.farmos import FarmOSMasterView
|
||||||
|
|
@ -58,9 +55,6 @@ class LogMasterView(FarmOSMasterView):
|
||||||
filterable = True
|
filterable = True
|
||||||
sort_on_backend = True
|
sort_on_backend = True
|
||||||
|
|
||||||
_farmos_units = None
|
|
||||||
_farmos_measures = None
|
|
||||||
|
|
||||||
labels = {
|
labels = {
|
||||||
"name": "Log Name",
|
"name": "Log Name",
|
||||||
"log_type_name": "Log Type",
|
"log_type_name": "Log Type",
|
||||||
|
|
@ -193,141 +187,14 @@ class LogMasterView(FarmOSMasterView):
|
||||||
def get_instance_title(self, log):
|
def get_instance_title(self, log):
|
||||||
return log["name"]
|
return log["name"]
|
||||||
|
|
||||||
def get_farmos_units(self):
|
|
||||||
if self._farmos_units:
|
|
||||||
return self._farmos_units
|
|
||||||
|
|
||||||
units = {}
|
|
||||||
result = self.farmos_client.resource.get("taxonomy_term", "unit")
|
|
||||||
for unit in result["data"]:
|
|
||||||
units[unit["id"]] = unit
|
|
||||||
|
|
||||||
self._farmos_units = units
|
|
||||||
return self._farmos_units
|
|
||||||
|
|
||||||
def get_farmos_unit(self, uuid):
|
|
||||||
units = self.get_farmos_units()
|
|
||||||
return units[uuid]
|
|
||||||
|
|
||||||
def get_farmos_measures(self):
|
|
||||||
if self._farmos_measures:
|
|
||||||
return self._farmos_measures
|
|
||||||
|
|
||||||
measures = {}
|
|
||||||
response = self.farmos_client.session.get(
|
|
||||||
self.app.get_farmos_url("/api/quantity/standard/resource/schema")
|
|
||||||
)
|
|
||||||
response.raise_for_status()
|
|
||||||
data = response.json()
|
|
||||||
for measure in data["definitions"]["attributes"]["properties"]["measure"][
|
|
||||||
"oneOf"
|
|
||||||
]:
|
|
||||||
measures[measure["const"]] = measure["title"]
|
|
||||||
|
|
||||||
self._farmos_measures = measures
|
|
||||||
return self._farmos_measures
|
|
||||||
|
|
||||||
def get_farmos_measure_name(self, measure_id):
|
|
||||||
measures = self.get_farmos_measures()
|
|
||||||
return measures[measure_id]
|
|
||||||
|
|
||||||
def normalize_log(self, log, included):
|
def normalize_log(self, log, included):
|
||||||
|
data = self.normal.normalize_farmos_log(log, included)
|
||||||
if timestamp := log["attributes"]["timestamp"]:
|
data.update(
|
||||||
timestamp = datetime.datetime.fromisoformat(timestamp)
|
{
|
||||||
timestamp = self.app.localtime(timestamp)
|
"log_type_name": data["log_type"].get("name"),
|
||||||
|
}
|
||||||
if notes := log["attributes"]["notes"]:
|
)
|
||||||
notes = notes["value"]
|
return data
|
||||||
|
|
||||||
log_type_object = {}
|
|
||||||
log_type_name = None
|
|
||||||
asset_objects = []
|
|
||||||
quantity_objects = []
|
|
||||||
owner_objects = []
|
|
||||||
if relationships := log.get("relationships"):
|
|
||||||
|
|
||||||
if log_type := relationships.get("log_type"):
|
|
||||||
log_type = included[log_type["data"]["id"]]
|
|
||||||
log_type_object = {
|
|
||||||
"uuid": log_type["id"],
|
|
||||||
"name": log_type["attributes"]["label"],
|
|
||||||
}
|
|
||||||
log_type_name = log_type_object["name"]
|
|
||||||
|
|
||||||
if assets := relationships.get("asset"):
|
|
||||||
for asset in assets["data"]:
|
|
||||||
asset = included[asset["id"]]
|
|
||||||
attrs = asset["attributes"]
|
|
||||||
rels = asset["relationships"]
|
|
||||||
asset_objects.append(
|
|
||||||
{
|
|
||||||
"uuid": asset["id"],
|
|
||||||
"drupal_id": attrs["drupal_internal__id"],
|
|
||||||
"name": attrs["name"],
|
|
||||||
"is_location": attrs["is_location"],
|
|
||||||
"is_fixed": attrs["is_fixed"],
|
|
||||||
"archived": attrs["archived"],
|
|
||||||
"notes": attrs["notes"],
|
|
||||||
"asset_type": asset["type"].split("--")[1],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if quantities := relationships.get("quantity"):
|
|
||||||
for quantity in quantities["data"]:
|
|
||||||
quantity = included[quantity["id"]]
|
|
||||||
attrs = quantity["attributes"]
|
|
||||||
rels = quantity["relationships"]
|
|
||||||
value = attrs["value"]
|
|
||||||
|
|
||||||
unit_uuid = rels["units"]["data"]["id"]
|
|
||||||
unit = self.get_farmos_unit(unit_uuid)
|
|
||||||
|
|
||||||
measure_id = attrs["measure"]
|
|
||||||
|
|
||||||
quantity_objects.append(
|
|
||||||
{
|
|
||||||
"uuid": quantity["id"],
|
|
||||||
"drupal_id": attrs["drupal_internal__id"],
|
|
||||||
"quantity_type_uuid": rels["quantity_type"]["data"]["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),
|
|
||||||
"value_numerator": value["numerator"],
|
|
||||||
"value_decimal": value["decimal"],
|
|
||||||
"value_denominator": value["denominator"],
|
|
||||||
"unit_uuid": unit_uuid,
|
|
||||||
"unit_name": unit["attributes"]["name"],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if owners := relationships.get("owner"):
|
|
||||||
for user in owners["data"]:
|
|
||||||
user = included[user["id"]]
|
|
||||||
owner_objects.append(
|
|
||||||
{
|
|
||||||
"uuid": user["id"],
|
|
||||||
"name": user["attributes"]["name"],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"uuid": log["id"],
|
|
||||||
"drupal_id": log["attributes"]["drupal_internal__id"],
|
|
||||||
"log_type": log_type_object,
|
|
||||||
"log_type_name": log_type_name,
|
|
||||||
"name": log["attributes"]["name"],
|
|
||||||
"timestamp": timestamp,
|
|
||||||
"assets": asset_objects,
|
|
||||||
"quantities": quantity_objects,
|
|
||||||
"is_group_assignment": log["attributes"]["is_group_assignment"],
|
|
||||||
"quick": log["attributes"]["quick"],
|
|
||||||
"status": log["attributes"]["status"],
|
|
||||||
"notes": notes or colander.null,
|
|
||||||
"owners": owner_objects,
|
|
||||||
}
|
|
||||||
|
|
||||||
def configure_form(self, form):
|
def configure_form(self, form):
|
||||||
f = form
|
f = form
|
||||||
|
|
@ -346,7 +213,7 @@ class LogMasterView(FarmOSMasterView):
|
||||||
f.set_node("quantities", FarmOSQuantityRefs(self.request))
|
f.set_node("quantities", FarmOSQuantityRefs(self.request))
|
||||||
|
|
||||||
# notes
|
# notes
|
||||||
f.set_widget("notes", "notes")
|
f.set_node("notes", Notes())
|
||||||
|
|
||||||
# status
|
# status
|
||||||
f.set_node("status", WuttaDictEnum(self.request, enum.LOG_STATUS))
|
f.set_node("status", WuttaDictEnum(self.request, enum.LOG_STATUS))
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@ class FarmOSMasterView(MasterView):
|
||||||
super().__init__(request, context=context)
|
super().__init__(request, context=context)
|
||||||
self.farmos_client = self.get_farmos_client()
|
self.farmos_client = self.get_farmos_client()
|
||||||
self.farmos_4x = self.app.is_farmos_4x(self.farmos_client)
|
self.farmos_4x = self.app.is_farmos_4x(self.farmos_client)
|
||||||
|
self.normal = self.app.get_normalizer(self.farmos_client)
|
||||||
self.raw_json = None
|
self.raw_json = None
|
||||||
self.farmos_style_grid_links = use_farmos_style_grid_links(self.config)
|
self.farmos_style_grid_links = use_farmos_style_grid_links(self.config)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -105,4 +105,4 @@ class WuttaFarmMasterView(MasterView):
|
||||||
|
|
||||||
def persist(self, obj, session=None):
|
def persist(self, obj, session=None):
|
||||||
super().persist(obj, session)
|
super().persist(obj, session)
|
||||||
self.app.export_to_farmos(obj, require=False)
|
self.app.auto_sync_to_farmos(obj, require=False)
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ class QuickFormView(View):
|
||||||
super().__init__(request, context=context)
|
super().__init__(request, context=context)
|
||||||
self.farmos_client = self.get_farmos_client()
|
self.farmos_client = self.get_farmos_client()
|
||||||
self.farmos_4x = self.app.is_farmos_4x(self.farmos_client)
|
self.farmos_4x = self.app.is_farmos_4x(self.farmos_client)
|
||||||
|
self.normal = self.app.get_normalizer(self.farmos_client)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_route_slug(cls):
|
def get_route_slug(cls):
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,18 @@ class EggsQuickForm(QuickFormView):
|
||||||
return assets
|
return assets
|
||||||
|
|
||||||
def save_quick_form(self, form):
|
def save_quick_form(self, form):
|
||||||
|
|
||||||
|
response = self.save_to_farmos(form)
|
||||||
|
log = json.loads(response["create-log#body{0}"]["body"])
|
||||||
|
|
||||||
|
if self.app.is_farmos_mirror():
|
||||||
|
quantity = json.loads(response["create-quantity"]["body"])
|
||||||
|
self.app.auto_sync_from_farmos(quantity["data"], "StandardQuantity")
|
||||||
|
self.app.auto_sync_from_farmos(log["data"], "HarvestLog")
|
||||||
|
|
||||||
|
return log
|
||||||
|
|
||||||
|
def save_to_farmos(self, form):
|
||||||
data = form.validated
|
data = form.validated
|
||||||
|
|
||||||
assets = self.get_layer_assets()
|
assets = self.get_layer_assets()
|
||||||
|
|
@ -217,8 +229,7 @@ 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)
|
||||||
result = json.loads(response["create-log#body{0}"]["body"])
|
return response
|
||||||
return result
|
|
||||||
|
|
||||||
def redirect_after_save(self, result):
|
def redirect_after_save(self, result):
|
||||||
return self.redirect(
|
return self.redirect(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue