diff --git a/src/wuttafarm/app.py b/src/wuttafarm/app.py index a3fa566..cb9aed3 100644 --- a/src/wuttafarm/app.py +++ b/src/wuttafarm/app.py @@ -36,6 +36,21 @@ class WuttaFarmAppHandler(base.AppHandler): default_auth_handler_spec = "wuttafarm.auth:WuttaFarmAuthHandler" default_install_handler_spec = "wuttafarm.install:WuttaFarmInstallHandler" + def get_asset_handler(self): + """ + Get the configured asset handler. + + :rtype: :class:`~wuttafarm.assets.AssetHandler` + """ + if "asset" not in self.handlers: + spec = self.config.get( + f"{self.appname}.asset_handler", + default="wuttafarm.assets:AssetHandler", + ) + factory = self.load_object(spec) + self.handlers["asset"] = factory(self.config) + return self.handlers["asset"] + def get_farmos_handler(self): """ Get the configured farmOS integration handler. diff --git a/src/wuttafarm/assets.py b/src/wuttafarm/assets.py new file mode 100644 index 0000000..321c3e5 --- /dev/null +++ b/src/wuttafarm/assets.py @@ -0,0 +1,49 @@ +# -*- 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 . +# +################################################################################ +""" +Asset handler +""" + +from wuttjamaican.app import GenericHandler + + +class AssetHandler(GenericHandler): + """ + Base class and default implementation for the asset + :term:`handler`. + """ + + def get_locations(self, asset): + model = self.app.model + session = self.app.get_session(asset) + + loclog = ( + session.query(model.Log) + .join(model.LogAsset) + .filter(model.LogAsset.asset == asset) + .filter(model.Log.is_movement == True) + .order_by(model.Log.timestamp.desc()) + .first() + ) + if loclog: + return loclog.locations + return [] diff --git a/src/wuttafarm/web/forms/schema.py b/src/wuttafarm/web/forms/schema.py index 0a7a72f..a2a72b5 100644 --- a/src/wuttafarm/web/forms/schema.py +++ b/src/wuttafarm/web/forms/schema.py @@ -364,7 +364,7 @@ class AssetParentRefs(WuttaSet): return AssetParentRefsWidget(self.request, **kwargs) -class LogAssetRefs(WuttaSet): +class AssetRefs(WuttaSet): """ Schema type for Assets field (on a Log record) """ @@ -376,9 +376,9 @@ class LogAssetRefs(WuttaSet): return {asset.uuid for asset in appstruct} def widget_maker(self, **kwargs): - from wuttafarm.web.forms.widgets import LogAssetRefsWidget + from wuttafarm.web.forms.widgets import AssetRefsWidget - return LogAssetRefsWidget(self.request, **kwargs) + return AssetRefsWidget(self.request, **kwargs) class LogQuantityRefs(WuttaSet): @@ -398,7 +398,7 @@ class LogQuantityRefs(WuttaSet): return LogQuantityRefsWidget(self.request, **kwargs) -class LogOwnerRefs(WuttaSet): +class OwnerRefs(WuttaSet): """ Schema type for Owners field (on a Log record) """ @@ -410,9 +410,9 @@ class LogOwnerRefs(WuttaSet): return {user.uuid for user in appstruct} def widget_maker(self, **kwargs): - from wuttafarm.web.forms.widgets import LogOwnerRefsWidget + from wuttafarm.web.forms.widgets import OwnerRefsWidget - return LogOwnerRefsWidget(self.request, **kwargs) + return OwnerRefsWidget(self.request, **kwargs) class Notes(colander.String): diff --git a/src/wuttafarm/web/forms/widgets.py b/src/wuttafarm/web/forms/widgets.py index 046e85b..0fd9221 100644 --- a/src/wuttafarm/web/forms/widgets.py +++ b/src/wuttafarm/web/forms/widgets.py @@ -423,9 +423,9 @@ class AssetParentRefsWidget(WuttaCheckboxChoiceWidget): return super().serialize(field, cstruct, **kw) -class LogAssetRefsWidget(WuttaCheckboxChoiceWidget): +class AssetRefsWidget(WuttaCheckboxChoiceWidget): """ - Widget for Assets field (on a Log record) + Widget for Assets field (of various kinds). """ def serialize(self, field, cstruct, **kw): @@ -486,9 +486,9 @@ class LogQuantityRefsWidget(WuttaCheckboxChoiceWidget): return super().serialize(field, cstruct, **kw) -class LogOwnerRefsWidget(WuttaCheckboxChoiceWidget): +class OwnerRefsWidget(WuttaCheckboxChoiceWidget): """ - Widget for Owners field (on a Log record) + Widget for Owners field (on an Asset or Log record) """ def serialize(self, field, cstruct, **kw): diff --git a/src/wuttafarm/web/views/animals.py b/src/wuttafarm/web/views/animals.py index d9d8db7..5756525 100644 --- a/src/wuttafarm/web/views/animals.py +++ b/src/wuttafarm/web/views/animals.py @@ -215,7 +215,7 @@ class AnimalAssetView(AssetMasterView): farmos_bundle = "animal" labels = { - "animal_type": "Species/Breed", + "animal_type": "Species / Breed", "is_sterile": "Sterile", } @@ -243,6 +243,8 @@ class AnimalAssetView(AssetMasterView): "is_sterile", "notes", "asset_type", + "owners", + "locations", "archived", "drupal_id", "farmos_uuid", diff --git a/src/wuttafarm/web/views/assets.py b/src/wuttafarm/web/views/assets.py index 38746bd..7035413 100644 --- a/src/wuttafarm/web/views/assets.py +++ b/src/wuttafarm/web/views/assets.py @@ -32,7 +32,7 @@ from wuttaweb.db import Session from wuttafarm.web.views import WuttaFarmMasterView from wuttafarm.db.model import Asset, Log -from wuttafarm.web.forms.schema import AssetParentRefs +from wuttafarm.web.forms.schema import AssetParentRefs, OwnerRefs, AssetRefs from wuttafarm.web.forms.widgets import ImageWidget from wuttafarm.util import get_log_type_enum from wuttafarm.web.util import get_farmos_client_for_user @@ -140,6 +140,10 @@ class AssetMasterView(WuttaFarmMasterView): g.set_label("owners", "Owner") g.set_renderer("owners", self.render_owners_for_grid) + # locations + g.set_label("locations", "Location") + g.set_renderer("locations", self.render_locations_for_grid) + # archived g.set_renderer("archived", "boolean") g.set_sorter("archived", model.Asset.archived) @@ -170,6 +174,21 @@ class AssetMasterView(WuttaFarmMasterView): return ", ".join([user.username for user in asset.owners]) + def render_locations_for_grid(self, asset, field, value): + asset_handler = self.app.get_asset_handler() + locations = asset_handler.get_locations(asset) + + if self.farmos_style_grid_links: + links = [] + for loc in locations: + url = self.request.route_url( + f"{loc.asset_type}_assets.view", uuid=loc.uuid + ) + links.append(tags.link_to(str(loc), url)) + return ", ".join(links) + + return ", ".join([str(loc) for loc in locations]) + def grid_row_class(self, asset, data, i): """ """ if asset.archived: @@ -179,6 +198,7 @@ class AssetMasterView(WuttaFarmMasterView): def configure_form(self, form): f = form super().configure_form(f) + asset_handler = self.app.get_asset_handler() asset = form.model_instance # asset_type @@ -191,12 +211,30 @@ class AssetMasterView(WuttaFarmMasterView): ) f.set_readonly("asset_type") + # owners + if self.creating or self.editing: + f.remove("owners") # TODO: need to support this + else: + f.set_node("owners", OwnerRefs(self.request)) + # nb. must explicity declare value for non-standard field + f.set_default("owners", asset.owners) + + # locations + if self.creating or self.editing: + # nb. this is a calculated field + f.remove("locations") + else: + f.set_label("locations", "Current Location") + f.set_node("locations", AssetRefs(self.request)) + # nb. must explicity declare value for non-standard field + f.set_default("locations", asset_handler.get_locations(asset)) + # parents if self.creating or self.editing: f.remove("parents") # TODO: add support for this else: f.set_node("parents", AssetParentRefs(self.request)) - f.set_default("parents", [p.parent_uuid for p in asset.asset._parents]) + f.set_default("parents", [p.uuid for p in asset.parents]) # notes f.set_widget("notes", "notes") diff --git a/src/wuttafarm/web/views/farmos/assets.py b/src/wuttafarm/web/views/farmos/assets.py index d1ae226..69e6321 100644 --- a/src/wuttafarm/web/views/farmos/assets.py +++ b/src/wuttafarm/web/views/farmos/assets.py @@ -53,7 +53,6 @@ class AssetMasterView(FarmOSMasterView): labels = { "name": "Asset Name", "asset_type_name": "Asset Type", - "owners": "Owner", "locations": "Location", "thumbnail_url": "Thumbnail URL", "image_url": "Image URL", @@ -104,6 +103,7 @@ class AssetMasterView(FarmOSMasterView): g.set_filter("name", StringFilter) # owners + g.set_label("owners", "Owner") g.set_renderer("owners", self.render_owners_for_grid) # locations @@ -239,6 +239,7 @@ class AssetMasterView(FarmOSMasterView): if self.creating or self.editing: f.remove("locations") else: + f.set_label("locations", "Current Location") f.set_node("locations", FarmOSLocationRefs(self.request)) # owners diff --git a/src/wuttafarm/web/views/logs.py b/src/wuttafarm/web/views/logs.py index e88e550..9c983b7 100644 --- a/src/wuttafarm/web/views/logs.py +++ b/src/wuttafarm/web/views/logs.py @@ -34,7 +34,7 @@ from wuttaweb.forms.widgets import WuttaDateTimeWidget from wuttafarm.web.views import WuttaFarmMasterView from wuttafarm.db.model import LogType, Log -from wuttafarm.web.forms.schema import LogAssetRefs, LogQuantityRefs, LogOwnerRefs +from wuttafarm.web.forms.schema import AssetRefs, LogQuantityRefs, OwnerRefs from wuttafarm.util import get_log_type_enum @@ -259,7 +259,7 @@ class LogMasterView(WuttaFarmMasterView): if self.creating or self.editing: f.remove("assets") # TODO: need to support this else: - f.set_node("assets", LogAssetRefs(self.request)) + f.set_node("assets", AssetRefs(self.request)) # nb. must explicity declare value for non-standard field f.set_default("assets", log.assets) @@ -267,7 +267,7 @@ class LogMasterView(WuttaFarmMasterView): if self.creating or self.editing: f.remove("groups") # TODO: need to support this else: - f.set_node("groups", LogAssetRefs(self.request)) + f.set_node("groups", AssetRefs(self.request)) # nb. must explicity declare value for non-standard field f.set_default("groups", log.groups) @@ -275,7 +275,7 @@ class LogMasterView(WuttaFarmMasterView): if self.creating or self.editing: f.remove("locations") # TODO: need to support this else: - f.set_node("locations", LogAssetRefs(self.request)) + f.set_node("locations", AssetRefs(self.request)) # nb. must explicity declare value for non-standard field f.set_default("locations", log.locations) @@ -306,7 +306,7 @@ class LogMasterView(WuttaFarmMasterView): if self.creating or self.editing: f.remove("owners") # TODO: need to support this else: - f.set_node("owners", LogOwnerRefs(self.request)) + f.set_node("owners", OwnerRefs(self.request)) # nb. must explicity declare value for non-standard field f.set_default("owners", log.owners)