431 lines
12 KiB
Python
431 lines
12 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/>.
|
|
#
|
|
################################################################################
|
|
"""
|
|
Base views for Logs
|
|
"""
|
|
|
|
from collections import OrderedDict
|
|
|
|
import colander
|
|
from webhelpers2.html import tags, HTML
|
|
|
|
from wuttaweb.forms.schema import WuttaDictEnum
|
|
from wuttaweb.db import Session
|
|
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 AssetRefs, LogQuantityRefs, OwnerRefs
|
|
from wuttafarm.util import get_log_type_enum
|
|
|
|
|
|
class LogTypeView(WuttaFarmMasterView):
|
|
"""
|
|
Master view for Log Types
|
|
"""
|
|
|
|
model_class = LogType
|
|
route_prefix = "log_types"
|
|
url_prefix = "/log-types"
|
|
|
|
grid_columns = [
|
|
"name",
|
|
"description",
|
|
]
|
|
|
|
sort_defaults = "name"
|
|
|
|
filter_defaults = {
|
|
"name": {"active": True, "verb": "contains"},
|
|
}
|
|
|
|
form_fields = [
|
|
"name",
|
|
"description",
|
|
"drupal_id",
|
|
"farmos_uuid",
|
|
]
|
|
|
|
def configure_grid(self, grid):
|
|
g = grid
|
|
super().configure_grid(g)
|
|
|
|
# name
|
|
g.set_link("name")
|
|
|
|
def get_xref_buttons(self, log_type):
|
|
buttons = super().get_xref_buttons(log_type)
|
|
|
|
if log_type.farmos_uuid:
|
|
buttons.append(
|
|
self.make_button(
|
|
"View farmOS record",
|
|
primary=True,
|
|
url=self.request.route_url(
|
|
"farmos_log_types.view", uuid=log_type.farmos_uuid
|
|
),
|
|
icon_left="eye",
|
|
)
|
|
)
|
|
|
|
return buttons
|
|
|
|
|
|
class LogMasterView(WuttaFarmMasterView):
|
|
"""
|
|
Base class for Asset master views
|
|
"""
|
|
|
|
farmos_entity_type = "log"
|
|
|
|
labels = {
|
|
"message": "Log Name",
|
|
"locations": "Location",
|
|
"quantities": "Quantity",
|
|
}
|
|
|
|
grid_columns = [
|
|
"status",
|
|
"drupal_id",
|
|
"timestamp",
|
|
"message",
|
|
"assets",
|
|
"locations",
|
|
"quantities",
|
|
"is_group_assignment",
|
|
"owners",
|
|
]
|
|
|
|
sort_defaults = ("timestamp", "desc")
|
|
|
|
filter_defaults = {
|
|
"message": {"active": True, "verb": "contains"},
|
|
"status": {"active": True, "verb": "not_equal", "value": "abandoned"},
|
|
}
|
|
|
|
form_fields = [
|
|
"message",
|
|
"timestamp",
|
|
"assets",
|
|
"groups",
|
|
"locations",
|
|
"quantities",
|
|
"notes",
|
|
"status",
|
|
"log_type",
|
|
"owners",
|
|
"is_movement",
|
|
"is_group_assignment",
|
|
"quick",
|
|
"drupal_id",
|
|
"farmos_uuid",
|
|
]
|
|
|
|
def get_query(self, session=None):
|
|
""" """
|
|
model = self.app.model
|
|
model_class = self.get_model_class()
|
|
session = session or self.Session()
|
|
query = session.query(model_class)
|
|
if model_class is not model.Log:
|
|
query = query.join(model.Log)
|
|
return query
|
|
|
|
def configure_grid(self, grid):
|
|
g = grid
|
|
super().configure_grid(g)
|
|
model = self.app.model
|
|
enum = self.app.enum
|
|
|
|
# status
|
|
g.set_enum("status", enum.LOG_STATUS)
|
|
g.set_sorter("status", model.Log.status)
|
|
g.set_filter(
|
|
"status",
|
|
model.Log.status,
|
|
verbs=["equal", "not_equal"],
|
|
choices=enum.LOG_STATUS,
|
|
)
|
|
|
|
# drupal_id
|
|
g.set_label("drupal_id", "ID", column_only=True)
|
|
g.set_sorter("drupal_id", model.Log.drupal_id)
|
|
g.set_filter("drupal_id", model.Log.drupal_id)
|
|
|
|
# timestamp
|
|
g.set_renderer("timestamp", "date")
|
|
g.set_link("timestamp")
|
|
g.set_sorter("timestamp", model.Log.timestamp)
|
|
g.set_filter("timestamp", model.Log.timestamp)
|
|
|
|
# message
|
|
g.set_link("message")
|
|
g.set_sorter("message", model.Log.message)
|
|
g.set_filter("message", model.Log.message)
|
|
|
|
# assets
|
|
g.set_renderer("assets", self.render_assets_for_grid)
|
|
|
|
# groups
|
|
g.set_renderer("groups", self.render_assets_for_grid)
|
|
|
|
# locations
|
|
g.set_renderer("locations", self.render_assets_for_grid)
|
|
|
|
# quantities
|
|
g.set_renderer("quantities", self.render_quantities_for_grid)
|
|
|
|
# is_group_assignment
|
|
g.set_renderer("is_group_assignment", "boolean")
|
|
g.set_sorter("is_group_assignment", model.Log.is_group_assignment)
|
|
g.set_filter("is_group_assignment", model.Log.is_group_assignment)
|
|
|
|
# owners
|
|
g.set_label("owners", "Owner")
|
|
g.set_renderer("owners", self.render_owners_for_grid)
|
|
|
|
def render_assets_for_grid(self, log, field, value):
|
|
assets = getattr(log, field)
|
|
|
|
if self.farmos_style_grid_links:
|
|
links = []
|
|
for asset in assets:
|
|
url = self.request.route_url(
|
|
f"{asset.asset_type}_assets.view", uuid=asset.uuid
|
|
)
|
|
links.append(tags.link_to(str(asset), url))
|
|
return ", ".join(links)
|
|
|
|
return ", ".join([str(a) for a in assets])
|
|
|
|
def render_quantities_for_grid(self, log, field, value):
|
|
quantities = getattr(log, field) or []
|
|
items = []
|
|
for qty in quantities:
|
|
items.append(HTML.tag("li", c=qty.render_as_text(self.config)))
|
|
return HTML.tag("ul", c=items)
|
|
|
|
def render_owners_for_grid(self, log, field, value):
|
|
|
|
if self.farmos_style_grid_links:
|
|
links = []
|
|
for user in log.owners:
|
|
url = self.request.route_url("users.view", uuid=user.uuid)
|
|
links.append(tags.link_to(user.username, url))
|
|
return ", ".join(links)
|
|
|
|
return ", ".join([user.username for user in log.owners])
|
|
|
|
def grid_row_class(self, log, data, i):
|
|
if log.status == "pending":
|
|
return "has-background-warning"
|
|
if log.status == "abandoned":
|
|
return "has-background-danger"
|
|
return None
|
|
|
|
def configure_form(self, form):
|
|
f = form
|
|
super().configure_form(f)
|
|
enum = self.app.enum
|
|
session = self.Session()
|
|
log = f.model_instance
|
|
|
|
# timestamp
|
|
# TODO: the widget should be automatic (assn proxy field)
|
|
f.set_widget("timestamp", WuttaDateTimeWidget(self.request))
|
|
if self.creating:
|
|
f.set_default("timestamp", self.app.make_utc())
|
|
|
|
# assets
|
|
if self.creating or self.editing:
|
|
f.remove("assets") # TODO: need to support this
|
|
else:
|
|
f.set_node("assets", AssetRefs(self.request))
|
|
# nb. must explicity declare value for non-standard field
|
|
f.set_default("assets", log.assets)
|
|
|
|
# groups
|
|
if self.creating or self.editing:
|
|
f.remove("groups") # TODO: need to support this
|
|
else:
|
|
f.set_node("groups", AssetRefs(self.request))
|
|
# nb. must explicity declare value for non-standard field
|
|
f.set_default("groups", log.groups)
|
|
|
|
# locations
|
|
if self.creating or self.editing:
|
|
f.remove("locations") # TODO: need to support this
|
|
else:
|
|
f.set_node("locations", AssetRefs(self.request))
|
|
# nb. must explicity declare value for non-standard field
|
|
f.set_default("locations", log.locations)
|
|
|
|
# log_type
|
|
if self.creating:
|
|
f.remove("log_type")
|
|
else:
|
|
f.set_node(
|
|
"log_type",
|
|
WuttaDictEnum(
|
|
self.request, get_log_type_enum(self.config, session=session)
|
|
),
|
|
)
|
|
f.set_readonly("log_type")
|
|
|
|
# quantities
|
|
if self.creating or self.editing:
|
|
f.remove("quantities") # TODO: need to support this
|
|
else:
|
|
f.set_node("quantities", LogQuantityRefs(self.request))
|
|
# nb. must explicity declare value for non-standard field
|
|
f.set_default("quantities", log.quantities)
|
|
|
|
# notes
|
|
f.set_widget("notes", "notes")
|
|
|
|
# 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", log.owners)
|
|
|
|
# status
|
|
f.set_node("status", WuttaDictEnum(self.request, enum.LOG_STATUS))
|
|
|
|
# is_movement
|
|
f.set_node("is_movement", colander.Boolean())
|
|
|
|
# is_group_assignment
|
|
f.set_node("is_group_assignment", colander.Boolean())
|
|
|
|
# quick
|
|
f.set_readonly("quick") # TODO
|
|
|
|
def objectify(self, form):
|
|
log = super().objectify(form)
|
|
|
|
if self.creating:
|
|
model_class = self.get_model_class()
|
|
log.log_type = self.get_farmos_log_type()
|
|
|
|
return log
|
|
|
|
def get_farmos_url(self, log):
|
|
return self.app.get_farmos_url(f"/log/{log.drupal_id}")
|
|
|
|
def get_farmos_log_type(self):
|
|
return self.model_class.__wutta_hint__["farmos_log_type"]
|
|
|
|
def get_xref_buttons(self, log):
|
|
buttons = super().get_xref_buttons(log)
|
|
|
|
if log.farmos_uuid:
|
|
log_type = self.get_farmos_log_type()
|
|
route = f"farmos_logs_{log_type}.view"
|
|
buttons.append(
|
|
self.make_button(
|
|
"View farmOS record",
|
|
primary=True,
|
|
url=self.request.route_url(route, uuid=log.farmos_uuid),
|
|
icon_left="eye",
|
|
)
|
|
)
|
|
|
|
return buttons
|
|
|
|
def get_version_joins(self):
|
|
"""
|
|
We override this to declare the relationship between the
|
|
view's data model (which is some type of log table) and the
|
|
canonical ``Log`` model, so the revision history views include
|
|
transactions which reference either version table.
|
|
|
|
See also parent method,
|
|
:meth:`~wuttaweb:wuttaweb.views.master.MasterView.get_version_joins()`
|
|
"""
|
|
model = self.app.model
|
|
return super().get_version_joins() + [
|
|
model.Log,
|
|
(model.LogAsset, "log_uuid", "uuid"),
|
|
]
|
|
|
|
|
|
class AllLogView(LogMasterView):
|
|
"""
|
|
Master view for All Logs
|
|
"""
|
|
|
|
model_class = Log
|
|
route_prefix = "log"
|
|
url_prefix = "/logs"
|
|
|
|
farmos_refurl_path = "/logs"
|
|
|
|
viewable = False
|
|
creatable = False
|
|
editable = False
|
|
deletable = False
|
|
model_is_versioned = False
|
|
|
|
grid_columns = [
|
|
"status",
|
|
"drupal_id",
|
|
"timestamp",
|
|
"message",
|
|
"log_type",
|
|
"assets",
|
|
"locations",
|
|
"quantities",
|
|
"groups",
|
|
"is_group_assignment",
|
|
"owners",
|
|
]
|
|
|
|
def configure_grid(self, grid):
|
|
g = grid
|
|
super().configure_grid(g)
|
|
session = self.Session()
|
|
|
|
# log_type
|
|
g.set_enum("log_type", get_log_type_enum(self.config, session=session))
|
|
|
|
# view action links to final log record
|
|
def log_url(log, i):
|
|
return self.request.route_url(f"logs_{log.log_type}.view", uuid=log.uuid)
|
|
|
|
g.add_action("view", icon="eye", url=log_url)
|
|
|
|
|
|
def defaults(config, **kwargs):
|
|
base = globals()
|
|
|
|
LogTypeView = kwargs.get("LogTypeView", base["LogTypeView"])
|
|
LogTypeView.defaults(config)
|
|
|
|
AllLogView = kwargs.get("AllLogView", base["AllLogView"])
|
|
AllLogView.defaults(config)
|
|
|
|
|
|
def includeme(config):
|
|
defaults(config)
|