feat: overhaul farmOS log views; add Eggs quick form
probably a few other changes...i'm tired and need a savepoint
This commit is contained in:
parent
ad6ac13d50
commit
1a6870b8fe
15 changed files with 914 additions and 36 deletions
|
|
@ -55,6 +55,34 @@ class AnimalTypeRef(ObjectRef):
|
||||||
return self.request.route_url("animal_types.view", uuid=animal_type.uuid)
|
return self.request.route_url("animal_types.view", uuid=animal_type.uuid)
|
||||||
|
|
||||||
|
|
||||||
|
class LogQuick(WuttaSet):
|
||||||
|
|
||||||
|
def serialize(self, node, appstruct):
|
||||||
|
if appstruct is colander.null:
|
||||||
|
return colander.null
|
||||||
|
|
||||||
|
return json.dumps(appstruct)
|
||||||
|
|
||||||
|
def widget_maker(self, **kwargs):
|
||||||
|
from wuttafarm.web.forms.widgets import LogQuickWidget
|
||||||
|
|
||||||
|
return LogQuickWidget(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class FarmOSUnitRef(colander.SchemaType):
|
||||||
|
|
||||||
|
def serialize(self, node, appstruct):
|
||||||
|
if appstruct is colander.null:
|
||||||
|
return colander.null
|
||||||
|
|
||||||
|
return json.dumps(appstruct)
|
||||||
|
|
||||||
|
def widget_maker(self, **kwargs):
|
||||||
|
from wuttafarm.web.forms.widgets import FarmOSUnitRefWidget
|
||||||
|
|
||||||
|
return FarmOSUnitRefWidget(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
class FarmOSRef(colander.SchemaType):
|
class FarmOSRef(colander.SchemaType):
|
||||||
|
|
||||||
def __init__(self, request, route_prefix, *args, **kwargs):
|
def __init__(self, request, route_prefix, *args, **kwargs):
|
||||||
|
|
@ -114,6 +142,20 @@ class FarmOSRefs(WuttaSet):
|
||||||
return FarmOSRefsWidget(self.request, self.route_prefix, **kwargs)
|
return FarmOSRefsWidget(self.request, self.route_prefix, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class FarmOSAssetRefs(WuttaSet):
|
||||||
|
|
||||||
|
def serialize(self, node, appstruct):
|
||||||
|
if appstruct is colander.null:
|
||||||
|
return colander.null
|
||||||
|
|
||||||
|
return json.dumps(appstruct)
|
||||||
|
|
||||||
|
def widget_maker(self, **kwargs):
|
||||||
|
from wuttafarm.web.forms.widgets import FarmOSAssetRefsWidget
|
||||||
|
|
||||||
|
return FarmOSAssetRefsWidget(self.request, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class FarmOSLocationRefs(WuttaSet):
|
class FarmOSLocationRefs(WuttaSet):
|
||||||
|
|
||||||
def serialize(self, node, appstruct):
|
def serialize(self, node, appstruct):
|
||||||
|
|
@ -128,6 +170,20 @@ class FarmOSLocationRefs(WuttaSet):
|
||||||
return FarmOSLocationRefsWidget(self.request, **kwargs)
|
return FarmOSLocationRefsWidget(self.request, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class FarmOSQuantityRefs(WuttaSet):
|
||||||
|
|
||||||
|
def serialize(self, node, appstruct):
|
||||||
|
if appstruct is colander.null:
|
||||||
|
return colander.null
|
||||||
|
|
||||||
|
return json.dumps(appstruct)
|
||||||
|
|
||||||
|
def widget_maker(self, **kwargs):
|
||||||
|
from wuttafarm.web.forms.widgets import FarmOSQuantityRefsWidget
|
||||||
|
|
||||||
|
return FarmOSQuantityRefsWidget(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
class AnimalTypeType(colander.SchemaType):
|
class AnimalTypeType(colander.SchemaType):
|
||||||
|
|
||||||
def __init__(self, request, *args, **kwargs):
|
def __init__(self, request, *args, **kwargs):
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@ from webhelpers2.html import HTML, tags
|
||||||
from wuttaweb.forms.widgets import WuttaCheckboxChoiceWidget
|
from wuttaweb.forms.widgets import WuttaCheckboxChoiceWidget
|
||||||
from wuttaweb.db import Session
|
from wuttaweb.db import Session
|
||||||
|
|
||||||
|
from wuttafarm.web.util import render_quantity_objects
|
||||||
|
|
||||||
|
|
||||||
class ImageWidget(Widget):
|
class ImageWidget(Widget):
|
||||||
"""
|
"""
|
||||||
|
|
@ -54,6 +56,26 @@ class ImageWidget(Widget):
|
||||||
return super().serialize(field, cstruct, **kw)
|
return super().serialize(field, cstruct, **kw)
|
||||||
|
|
||||||
|
|
||||||
|
class LogQuickWidget(Widget):
|
||||||
|
"""
|
||||||
|
Widget to display an image URL for a record.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def serialize(self, field, cstruct, **kw):
|
||||||
|
""" """
|
||||||
|
readonly = kw.get("readonly", self.readonly)
|
||||||
|
if readonly:
|
||||||
|
if cstruct in (colander.null, None):
|
||||||
|
return HTML.tag("span")
|
||||||
|
|
||||||
|
items = []
|
||||||
|
for quick in json.loads(cstruct):
|
||||||
|
items.append(HTML.tag("li", c=quick))
|
||||||
|
return HTML.tag("ul", c=items)
|
||||||
|
|
||||||
|
return super().serialize(field, cstruct, **kw)
|
||||||
|
|
||||||
|
|
||||||
class FarmOSRefWidget(SelectWidget):
|
class FarmOSRefWidget(SelectWidget):
|
||||||
"""
|
"""
|
||||||
Generic widget to display "any reference field" - as a link to
|
Generic widget to display "any reference field" - as a link to
|
||||||
|
|
@ -111,6 +133,33 @@ class FarmOSRefsWidget(Widget):
|
||||||
return super().serialize(field, cstruct, **kw)
|
return super().serialize(field, cstruct, **kw)
|
||||||
|
|
||||||
|
|
||||||
|
class FarmOSAssetRefsWidget(Widget):
|
||||||
|
"""
|
||||||
|
Widget to display a "Assets" field for an asset.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, request, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.request = request
|
||||||
|
|
||||||
|
def serialize(self, field, cstruct, **kw):
|
||||||
|
readonly = kw.get("readonly", self.readonly)
|
||||||
|
if readonly:
|
||||||
|
if cstruct in (colander.null, None):
|
||||||
|
return HTML.tag("span")
|
||||||
|
|
||||||
|
assets = []
|
||||||
|
for asset in json.loads(cstruct):
|
||||||
|
url = self.request.route_url(
|
||||||
|
f"farmos_{asset['asset_type']}_assets.view", uuid=asset["uuid"]
|
||||||
|
)
|
||||||
|
assets.append(HTML.tag("li", c=tags.link_to(asset["name"], url)))
|
||||||
|
|
||||||
|
return HTML.tag("ul", c=assets)
|
||||||
|
|
||||||
|
return super().serialize(field, cstruct, **kw)
|
||||||
|
|
||||||
|
|
||||||
class FarmOSLocationRefsWidget(Widget):
|
class FarmOSLocationRefsWidget(Widget):
|
||||||
"""
|
"""
|
||||||
Widget to display a "Locations" field for an asset.
|
Widget to display a "Locations" field for an asset.
|
||||||
|
|
@ -139,6 +188,40 @@ class FarmOSLocationRefsWidget(Widget):
|
||||||
return super().serialize(field, cstruct, **kw)
|
return super().serialize(field, cstruct, **kw)
|
||||||
|
|
||||||
|
|
||||||
|
class FarmOSQuantityRefsWidget(Widget):
|
||||||
|
"""
|
||||||
|
Widget to display a "Quantities" field for a log.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def serialize(self, field, cstruct, **kw):
|
||||||
|
readonly = kw.get("readonly", self.readonly)
|
||||||
|
if readonly:
|
||||||
|
if cstruct in (colander.null, None):
|
||||||
|
return HTML.tag("span")
|
||||||
|
|
||||||
|
quantities = json.loads(cstruct)
|
||||||
|
return render_quantity_objects(quantities)
|
||||||
|
|
||||||
|
return super().serialize(field, cstruct, **kw)
|
||||||
|
|
||||||
|
|
||||||
|
class FarmOSUnitRefWidget(Widget):
|
||||||
|
"""
|
||||||
|
Widget to display a "Units" field for a quantity.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def serialize(self, field, cstruct, **kw):
|
||||||
|
readonly = kw.get("readonly", self.readonly)
|
||||||
|
if readonly:
|
||||||
|
if cstruct in (colander.null, None):
|
||||||
|
return HTML.tag("span")
|
||||||
|
|
||||||
|
unit = json.loads(cstruct)
|
||||||
|
return unit["name"]
|
||||||
|
|
||||||
|
return super().serialize(field, cstruct, **kw)
|
||||||
|
|
||||||
|
|
||||||
class AnimalTypeWidget(Widget):
|
class AnimalTypeWidget(Widget):
|
||||||
"""
|
"""
|
||||||
Widget to display an "animal type" field.
|
Widget to display an "animal type" field.
|
||||||
|
|
|
||||||
|
|
@ -35,29 +35,48 @@ class WuttaFarmMenuHandler(base.MenuHandler):
|
||||||
enum = self.app.enum
|
enum = self.app.enum
|
||||||
mode = self.app.get_farmos_integration_mode()
|
mode = self.app.get_farmos_integration_mode()
|
||||||
|
|
||||||
|
quick_menu = self.make_quick_menu(request)
|
||||||
|
admin_menu = self.make_admin_menu(request, include_people=True)
|
||||||
|
|
||||||
if mode == enum.FARMOS_INTEGRATION_MODE_WRAPPER:
|
if mode == enum.FARMOS_INTEGRATION_MODE_WRAPPER:
|
||||||
return [
|
return [
|
||||||
|
quick_menu,
|
||||||
self.make_farmos_asset_menu(request),
|
self.make_farmos_asset_menu(request),
|
||||||
self.make_farmos_log_menu(request),
|
self.make_farmos_log_menu(request),
|
||||||
self.make_farmos_other_menu(request),
|
self.make_farmos_other_menu(request),
|
||||||
self.make_admin_menu(request, include_people=True),
|
admin_menu,
|
||||||
]
|
]
|
||||||
|
|
||||||
elif mode == enum.FARMOS_INTEGRATION_MODE_MIRROR:
|
elif mode == enum.FARMOS_INTEGRATION_MODE_MIRROR:
|
||||||
return [
|
return [
|
||||||
|
quick_menu,
|
||||||
self.make_asset_menu(request),
|
self.make_asset_menu(request),
|
||||||
self.make_log_menu(request),
|
self.make_log_menu(request),
|
||||||
self.make_farmos_full_menu(request),
|
self.make_farmos_full_menu(request),
|
||||||
self.make_admin_menu(request, include_people=True),
|
admin_menu,
|
||||||
]
|
]
|
||||||
|
|
||||||
else: # FARMOS_INTEGRATION_MODE_NONE
|
else: # FARMOS_INTEGRATION_MODE_NONE
|
||||||
return [
|
return [
|
||||||
|
quick_menu,
|
||||||
self.make_asset_menu(request),
|
self.make_asset_menu(request),
|
||||||
self.make_log_menu(request),
|
self.make_log_menu(request),
|
||||||
self.make_admin_menu(request, include_people=True),
|
admin_menu,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def make_quick_menu(self, request):
|
||||||
|
return {
|
||||||
|
"title": "Quick",
|
||||||
|
"type": "menu",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"title": "Eggs",
|
||||||
|
"route": "quick.eggs",
|
||||||
|
# "perm": "assets.list",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
def make_asset_menu(self, request):
|
def make_asset_menu(self, request):
|
||||||
return {
|
return {
|
||||||
"title": "Assets",
|
"title": "Assets",
|
||||||
|
|
|
||||||
14
src/wuttafarm/web/templates/quick/form.mako
Normal file
14
src/wuttafarm/web/templates/quick/form.mako
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
<%inherit file="/form.mako" />
|
||||||
|
|
||||||
|
<%def name="title()">${index_title} » ${form_title}</%def>
|
||||||
|
|
||||||
|
<%def name="content_title()">${form_title}</%def>
|
||||||
|
|
||||||
|
<%def name="render_form_tag()">
|
||||||
|
|
||||||
|
<p class="block">
|
||||||
|
${help_text}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
${parent.render_form_tag()}
|
||||||
|
</%def>
|
||||||
|
|
@ -23,6 +23,8 @@
|
||||||
Misc. utilities for web app
|
Misc. utilities for web app
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from webhelpers2.html import HTML
|
||||||
|
|
||||||
|
|
||||||
def save_farmos_oauth2_token(request, token):
|
def save_farmos_oauth2_token(request, token):
|
||||||
"""
|
"""
|
||||||
|
|
@ -42,3 +44,18 @@ def save_farmos_oauth2_token(request, token):
|
||||||
|
|
||||||
def use_farmos_style_grid_links(config):
|
def use_farmos_style_grid_links(config):
|
||||||
return config.get_bool(f"{config.appname}.farmos_style_grid_links", default=True)
|
return config.get_bool(f"{config.appname}.farmos_style_grid_links", default=True)
|
||||||
|
|
||||||
|
|
||||||
|
def render_quantity_objects(quantities):
|
||||||
|
items = []
|
||||||
|
for quantity in quantities:
|
||||||
|
text = render_quantity_object(quantity)
|
||||||
|
items.append(HTML.tag("li", c=text))
|
||||||
|
return HTML.tag("ul", c=items)
|
||||||
|
|
||||||
|
|
||||||
|
def render_quantity_object(quantity):
|
||||||
|
measure = quantity["measure_name"]
|
||||||
|
value = quantity["value_decimal"]
|
||||||
|
unit = quantity["unit_name"]
|
||||||
|
return f"( {measure} ) {value} {unit}"
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,10 @@ def includeme(config):
|
||||||
config.include("wuttafarm.web.views.logs_medical")
|
config.include("wuttafarm.web.views.logs_medical")
|
||||||
config.include("wuttafarm.web.views.logs_observation")
|
config.include("wuttafarm.web.views.logs_observation")
|
||||||
|
|
||||||
|
# quick form views
|
||||||
|
# (nb. these work with all integration modes)
|
||||||
|
config.include("wuttafarm.web.views.quick")
|
||||||
|
|
||||||
# views for farmOS
|
# views for farmOS
|
||||||
if mode != enum.FARMOS_INTEGRATION_MODE_NONE:
|
if mode != enum.FARMOS_INTEGRATION_MODE_NONE:
|
||||||
config.include("wuttafarm.web.views.farmos")
|
config.include("wuttafarm.web.views.farmos")
|
||||||
|
|
|
||||||
|
|
@ -119,16 +119,6 @@ class AssetMasterView(FarmOSMasterView):
|
||||||
return tags.image(url, f"thumbnail for {self.get_model_title()}")
|
return tags.image(url, f"thumbnail for {self.get_model_title()}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def render_owners_for_grid(self, asset, field, value):
|
|
||||||
owners = []
|
|
||||||
for user in value:
|
|
||||||
if self.farmos_style_grid_links:
|
|
||||||
url = self.request.route_url("farmos_users.view", uuid=user["uuid"])
|
|
||||||
owners.append(tags.link_to(user["name"], url))
|
|
||||||
else:
|
|
||||||
owners.append(user["name"])
|
|
||||||
return ", ".join(owners)
|
|
||||||
|
|
||||||
def render_locations_for_grid(self, asset, field, value):
|
def render_locations_for_grid(self, asset, field, value):
|
||||||
locations = []
|
locations = []
|
||||||
for location in value:
|
for location in value:
|
||||||
|
|
@ -151,15 +141,14 @@ class AssetMasterView(FarmOSMasterView):
|
||||||
return {"asset_type", "location", "owner", "image"}
|
return {"asset_type", "location", "owner", "image"}
|
||||||
|
|
||||||
def get_instance(self):
|
def get_instance(self):
|
||||||
asset = self.farmos_client.resource.get_id(
|
result = self.farmos_client.asset.get_id(
|
||||||
"asset",
|
|
||||||
self.farmos_asset_type,
|
self.farmos_asset_type,
|
||||||
self.request.matchdict["uuid"],
|
self.request.matchdict["uuid"],
|
||||||
params={"include": ",".join(self.get_farmos_api_includes())},
|
params={"include": ",".join(self.get_farmos_api_includes())},
|
||||||
)
|
)
|
||||||
self.raw_json = asset
|
self.raw_json = result
|
||||||
included = {obj["id"]: obj for obj in asset.get("included", [])}
|
included = {obj["id"]: obj for obj in result.get("included", [])}
|
||||||
return self.normalize_asset(asset["data"], included)
|
return self.normalize_asset(result["data"], included)
|
||||||
|
|
||||||
def get_instance_title(self, asset):
|
def get_instance_title(self, asset):
|
||||||
return asset["name"]
|
return asset["name"]
|
||||||
|
|
|
||||||
|
|
@ -26,11 +26,27 @@ View for farmOS Harvest Logs
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
import colander
|
import colander
|
||||||
|
from webhelpers2.html import tags
|
||||||
|
|
||||||
from wuttaweb.forms.schema import WuttaDateTime
|
from wuttaweb.forms.schema import WuttaDateTime, WuttaDictEnum
|
||||||
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
|
||||||
|
from wuttafarm.web.grids import (
|
||||||
|
ResourceData,
|
||||||
|
SimpleSorter,
|
||||||
|
StringFilter,
|
||||||
|
IntegerFilter,
|
||||||
|
DateTimeFilter,
|
||||||
|
NullableBooleanFilter,
|
||||||
|
)
|
||||||
|
from wuttafarm.web.forms.schema import (
|
||||||
|
FarmOSQuantityRefs,
|
||||||
|
FarmOSAssetRefs,
|
||||||
|
FarmOSRefs,
|
||||||
|
LogQuick,
|
||||||
|
)
|
||||||
|
from wuttafarm.web.util import render_quantity_objects
|
||||||
|
|
||||||
|
|
||||||
class LogMasterView(FarmOSMasterView):
|
class LogMasterView(FarmOSMasterView):
|
||||||
|
|
@ -39,48 +55,183 @@ class LogMasterView(FarmOSMasterView):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
farmos_log_type = None
|
farmos_log_type = None
|
||||||
|
filterable = True
|
||||||
|
sort_on_backend = True
|
||||||
|
|
||||||
|
_farmos_units = None
|
||||||
|
_farmos_measures = None
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
"name": "Log Name",
|
||||||
|
"log_type_name": "Log Type",
|
||||||
|
"quantities": "Quantity",
|
||||||
|
}
|
||||||
|
|
||||||
grid_columns = [
|
grid_columns = [
|
||||||
"name",
|
|
||||||
"timestamp",
|
|
||||||
"status",
|
"status",
|
||||||
|
"drupal_id",
|
||||||
|
"timestamp",
|
||||||
|
"name",
|
||||||
|
"assets",
|
||||||
|
"quantities",
|
||||||
|
"is_group_assignment",
|
||||||
|
"owners",
|
||||||
]
|
]
|
||||||
|
|
||||||
sort_defaults = ("timestamp", "desc")
|
sort_defaults = ("timestamp", "desc")
|
||||||
|
|
||||||
|
filter_defaults = {
|
||||||
|
"name": {"active": True, "verb": "contains"},
|
||||||
|
"status": {"active": True, "verb": "not_equal", "value": "abandoned"},
|
||||||
|
}
|
||||||
|
|
||||||
form_fields = [
|
form_fields = [
|
||||||
"name",
|
"name",
|
||||||
"timestamp",
|
"timestamp",
|
||||||
"status",
|
"assets",
|
||||||
|
"quantities",
|
||||||
"notes",
|
"notes",
|
||||||
|
"status",
|
||||||
|
"log_type_name",
|
||||||
|
"owners",
|
||||||
|
"quick",
|
||||||
|
"drupal_id",
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_grid_data(self, columns=None, session=None):
|
def get_farmos_api_includes(self):
|
||||||
result = self.farmos_client.log.get(self.farmos_log_type)
|
return {"log_type", "quantity", "asset", "owner"}
|
||||||
return [self.normalize_log(l) for l in result["data"]]
|
|
||||||
|
def get_grid_data(self, **kwargs):
|
||||||
|
return ResourceData(
|
||||||
|
self.config,
|
||||||
|
self.farmos_client,
|
||||||
|
f"log--{self.farmos_log_type}",
|
||||||
|
include=",".join(self.get_farmos_api_includes()),
|
||||||
|
normalizer=self.normalize_log,
|
||||||
|
)
|
||||||
|
|
||||||
def configure_grid(self, grid):
|
def configure_grid(self, grid):
|
||||||
g = grid
|
g = grid
|
||||||
super().configure_grid(g)
|
super().configure_grid(g)
|
||||||
|
enum = self.app.enum
|
||||||
|
|
||||||
|
# status
|
||||||
|
g.set_enum("status", enum.LOG_STATUS)
|
||||||
|
g.set_sorter("status", SimpleSorter("status"))
|
||||||
|
g.set_filter(
|
||||||
|
"status",
|
||||||
|
StringFilter,
|
||||||
|
choices=enum.LOG_STATUS,
|
||||||
|
verbs=["equal", "not_equal"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# drupal_id
|
||||||
|
g.set_label("drupal_id", "ID", column_only=True)
|
||||||
|
g.set_sorter("drupal_id", SimpleSorter("drupal_internal__id"))
|
||||||
|
g.set_filter("drupal_id", IntegerFilter, path="drupal_internal__id")
|
||||||
|
|
||||||
|
# timestamp
|
||||||
|
g.set_renderer("timestamp", "date")
|
||||||
|
g.set_link("timestamp")
|
||||||
|
g.set_sorter("timestamp", SimpleSorter("timestamp"))
|
||||||
|
g.set_filter("timestamp", DateTimeFilter)
|
||||||
|
|
||||||
# name
|
# name
|
||||||
g.set_link("name")
|
g.set_link("name")
|
||||||
g.set_searchable("name")
|
g.set_sorter("name", SimpleSorter("name"))
|
||||||
|
g.set_filter("name", StringFilter)
|
||||||
|
|
||||||
# timestamp
|
# assets
|
||||||
g.set_renderer("timestamp", "datetime")
|
g.set_renderer("assets", 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", SimpleSorter("is_group_assignment"))
|
||||||
|
g.set_filter("is_group_assignment", NullableBooleanFilter)
|
||||||
|
|
||||||
|
# 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 = []
|
||||||
|
for asset in value:
|
||||||
|
if self.farmos_style_grid_links:
|
||||||
|
url = self.request.route_url(
|
||||||
|
f"farmos_{asset['asset_type']}_assets.view", uuid=asset["uuid"]
|
||||||
|
)
|
||||||
|
assets.append(tags.link_to(asset["name"], url))
|
||||||
|
else:
|
||||||
|
assets.append(asset["name"])
|
||||||
|
return ", ".join(assets)
|
||||||
|
|
||||||
|
def render_quantities_for_grid(self, log, field, value):
|
||||||
|
if not value:
|
||||||
|
return None
|
||||||
|
return render_quantity_objects(value)
|
||||||
|
|
||||||
|
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 get_instance(self):
|
def get_instance(self):
|
||||||
log = self.farmos_client.log.get_id(
|
result = self.farmos_client.log.get_id(
|
||||||
self.farmos_log_type, self.request.matchdict["uuid"]
|
self.farmos_log_type,
|
||||||
|
self.request.matchdict["uuid"],
|
||||||
|
params={"include": ",".join(self.get_farmos_api_includes())},
|
||||||
)
|
)
|
||||||
self.raw_json = log
|
self.raw_json = result
|
||||||
return self.normalize_log(log["data"])
|
included = {obj["id"]: obj for obj in result.get("included", [])}
|
||||||
|
return self.normalize_log(result["data"], included)
|
||||||
|
|
||||||
def get_instance_title(self, log):
|
def get_instance_title(self, log):
|
||||||
return log["name"]
|
return log["name"]
|
||||||
|
|
||||||
def normalize_log(self, log):
|
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):
|
||||||
|
|
||||||
if timestamp := log["attributes"]["timestamp"]:
|
if timestamp := log["attributes"]["timestamp"]:
|
||||||
timestamp = datetime.datetime.fromisoformat(timestamp)
|
timestamp = datetime.datetime.fromisoformat(timestamp)
|
||||||
|
|
@ -89,26 +240,126 @@ class LogMasterView(FarmOSMasterView):
|
||||||
if notes := log["attributes"]["notes"]:
|
if notes := log["attributes"]["notes"]:
|
||||||
notes = notes["value"]
|
notes = notes["value"]
|
||||||
|
|
||||||
|
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 {
|
return {
|
||||||
"uuid": log["id"],
|
"uuid": log["id"],
|
||||||
"drupal_id": log["attributes"]["drupal_internal__id"],
|
"drupal_id": log["attributes"]["drupal_internal__id"],
|
||||||
|
"log_type": log_type_object,
|
||||||
|
"log_type_name": log_type_name,
|
||||||
"name": log["attributes"]["name"],
|
"name": log["attributes"]["name"],
|
||||||
"timestamp": timestamp,
|
"timestamp": timestamp,
|
||||||
|
"assets": asset_objects,
|
||||||
|
"quantities": quantity_objects,
|
||||||
|
"is_group_assignment": log["attributes"]["is_group_assignment"],
|
||||||
|
"quick": log["attributes"]["quick"],
|
||||||
"status": log["attributes"]["status"],
|
"status": log["attributes"]["status"],
|
||||||
"notes": notes or colander.null,
|
"notes": notes or colander.null,
|
||||||
|
"owners": owner_objects,
|
||||||
}
|
}
|
||||||
|
|
||||||
def configure_form(self, form):
|
def configure_form(self, form):
|
||||||
f = form
|
f = form
|
||||||
super().configure_form(f)
|
super().configure_form(f)
|
||||||
|
enum = self.app.enum
|
||||||
|
log = f.model_instance
|
||||||
|
|
||||||
# timestamp
|
# timestamp
|
||||||
f.set_node("timestamp", WuttaDateTime())
|
f.set_node("timestamp", WuttaDateTime())
|
||||||
f.set_widget("timestamp", WuttaDateTimeWidget(self.request))
|
f.set_widget("timestamp", WuttaDateTimeWidget(self.request))
|
||||||
|
|
||||||
|
# assets
|
||||||
|
f.set_node("assets", FarmOSAssetRefs(self.request))
|
||||||
|
|
||||||
|
# quantities
|
||||||
|
f.set_node("quantities", FarmOSQuantityRefs(self.request))
|
||||||
|
|
||||||
# notes
|
# notes
|
||||||
f.set_widget("notes", "notes")
|
f.set_widget("notes", "notes")
|
||||||
|
|
||||||
|
# status
|
||||||
|
f.set_node("status", WuttaDictEnum(self.request, enum.LOG_STATUS))
|
||||||
|
|
||||||
|
# owners
|
||||||
|
if self.creating or self.editing:
|
||||||
|
f.remove("owners") # TODO
|
||||||
|
else:
|
||||||
|
f.set_node("owners", FarmOSRefs(self.request, "farmos_users"))
|
||||||
|
|
||||||
|
# quick
|
||||||
|
f.set_node("quick", LogQuick(self.request))
|
||||||
|
|
||||||
def get_xref_buttons(self, log):
|
def get_xref_buttons(self, log):
|
||||||
model = self.app.model
|
model = self.app.model
|
||||||
session = self.Session()
|
session = self.Session()
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,17 @@ class HarvestLogView(LogMasterView):
|
||||||
farmos_log_type = "harvest"
|
farmos_log_type = "harvest"
|
||||||
farmos_refurl_path = "/logs/harvest"
|
farmos_refurl_path = "/logs/harvest"
|
||||||
|
|
||||||
|
grid_columns = [
|
||||||
|
"status",
|
||||||
|
"drupal_id",
|
||||||
|
"timestamp",
|
||||||
|
"name",
|
||||||
|
"assets",
|
||||||
|
"quantities",
|
||||||
|
"is_group_assignment",
|
||||||
|
"owners",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def defaults(config, **kwargs):
|
def defaults(config, **kwargs):
|
||||||
base = globals()
|
base = globals()
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import json
|
||||||
|
|
||||||
import colander
|
import colander
|
||||||
import markdown
|
import markdown
|
||||||
|
from webhelpers2.html import tags
|
||||||
|
|
||||||
from wuttaweb.views import MasterView
|
from wuttaweb.views import MasterView
|
||||||
from wuttaweb.forms.schema import WuttaDateTime
|
from wuttaweb.forms.schema import WuttaDateTime
|
||||||
|
|
@ -99,6 +100,16 @@ class FarmOSMasterView(MasterView):
|
||||||
|
|
||||||
return templates
|
return templates
|
||||||
|
|
||||||
|
def render_owners_for_grid(self, obj, field, value):
|
||||||
|
owners = []
|
||||||
|
for user in value:
|
||||||
|
if self.farmos_style_grid_links:
|
||||||
|
url = self.request.route_url("farmos_users.view", uuid=user["uuid"])
|
||||||
|
owners.append(tags.link_to(user["name"], url))
|
||||||
|
else:
|
||||||
|
owners.append(user["name"])
|
||||||
|
return ", ".join(owners)
|
||||||
|
|
||||||
def get_template_context(self, context):
|
def get_template_context(self, context):
|
||||||
|
|
||||||
if self.listing and self.farmos_refurl_path:
|
if self.listing and self.farmos_refurl_path:
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ from wuttaweb.forms.schema import WuttaDateTime
|
||||||
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
|
||||||
from wuttafarm.web.forms.schema import FarmOSRef
|
from wuttafarm.web.forms.schema import FarmOSUnitRef
|
||||||
|
|
||||||
|
|
||||||
class QuantityTypeView(FarmOSMasterView):
|
class QuantityTypeView(FarmOSMasterView):
|
||||||
|
|
@ -220,7 +220,7 @@ class QuantityMasterView(FarmOSMasterView):
|
||||||
f.set_widget("changed", WuttaDateTimeWidget(self.request))
|
f.set_widget("changed", WuttaDateTimeWidget(self.request))
|
||||||
|
|
||||||
# units
|
# units
|
||||||
f.set_node("units", FarmOSRef(self.request, "farmos_units"))
|
f.set_node("units", FarmOSUnitRef())
|
||||||
|
|
||||||
|
|
||||||
class StandardQuantityView(QuantityMasterView):
|
class StandardQuantityView(QuantityMasterView):
|
||||||
|
|
|
||||||
|
|
@ -175,15 +175,21 @@ class LogMasterView(WuttaFarmMasterView):
|
||||||
Base class for Asset master views
|
Base class for Asset master views
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
"message": "Log Name",
|
||||||
|
"owners": "Owner",
|
||||||
|
}
|
||||||
|
|
||||||
grid_columns = [
|
grid_columns = [
|
||||||
"status",
|
"status",
|
||||||
"drupal_id",
|
"drupal_id",
|
||||||
"timestamp",
|
"timestamp",
|
||||||
"message",
|
"message",
|
||||||
"assets",
|
"assets",
|
||||||
"location",
|
# "location",
|
||||||
"quantity",
|
"quantity",
|
||||||
"is_group_assignment",
|
"is_group_assignment",
|
||||||
|
"owners",
|
||||||
]
|
]
|
||||||
|
|
||||||
sort_defaults = ("timestamp", "desc")
|
sort_defaults = ("timestamp", "desc")
|
||||||
|
|
|
||||||
30
src/wuttafarm/web/views/quick/__init__.py
Normal file
30
src/wuttafarm/web/views/quick/__init__.py
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
# -*- 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 views for farmOS
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .base import QuickFormView
|
||||||
|
|
||||||
|
|
||||||
|
def includeme(config):
|
||||||
|
config.include("wuttafarm.web.views.quick.eggs")
|
||||||
155
src/wuttafarm/web/views/quick/base.py
Normal file
155
src/wuttafarm/web/views/quick/base.py
Normal file
|
|
@ -0,0 +1,155 @@
|
||||||
|
# -*- 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 class for Quick Form views
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from pyramid.renderers import render_to_response
|
||||||
|
|
||||||
|
from wuttaweb.views import View
|
||||||
|
|
||||||
|
from wuttafarm.web.util import save_farmos_oauth2_token
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class QuickFormView(View):
|
||||||
|
"""
|
||||||
|
Base class for quick form views.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, request, context=None):
|
||||||
|
super().__init__(request, context=context)
|
||||||
|
self.farmos_client = self.get_farmos_client()
|
||||||
|
self.farmos_4x = self.app.is_farmos_4x(self.farmos_client)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_route_slug(cls):
|
||||||
|
return cls.route_slug
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_url_slug(cls):
|
||||||
|
return cls.url_slug
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_form_title(cls):
|
||||||
|
return cls.form_title
|
||||||
|
|
||||||
|
def __call__(self):
|
||||||
|
form = self.make_quick_form()
|
||||||
|
|
||||||
|
if form.validate():
|
||||||
|
try:
|
||||||
|
result = self.save_quick_form(form)
|
||||||
|
except Exception as err:
|
||||||
|
log.warning("failed to save 'edit' form", exc_info=True)
|
||||||
|
self.request.session.flash(
|
||||||
|
f"Save failed: {self.app.render_error(err)}", "error"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return self.redirect_after_save(result)
|
||||||
|
|
||||||
|
return self.render_to_response({"form": form})
|
||||||
|
|
||||||
|
def make_quick_form(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def save_quick_form(self, form):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def redirect_after_save(self, result):
|
||||||
|
return self.redirect(self.request.current_route_url())
|
||||||
|
|
||||||
|
def render_to_response(self, context):
|
||||||
|
|
||||||
|
defaults = {
|
||||||
|
"index_title": "Quick Form",
|
||||||
|
"form_title": self.get_form_title(),
|
||||||
|
"help_text": self.__doc__.strip(),
|
||||||
|
}
|
||||||
|
|
||||||
|
defaults.update(context)
|
||||||
|
context = defaults
|
||||||
|
|
||||||
|
# supplement context further if needed
|
||||||
|
context = self.get_template_context(context)
|
||||||
|
|
||||||
|
page_templates = self.get_page_templates()
|
||||||
|
mako_path = page_templates[0]
|
||||||
|
try:
|
||||||
|
render_to_response(mako_path, context, request=self.request)
|
||||||
|
except IOError:
|
||||||
|
|
||||||
|
# try one or more fallback templates
|
||||||
|
for fallback in page_templates[1:]:
|
||||||
|
try:
|
||||||
|
return render_to_response(fallback, context, request=self.request)
|
||||||
|
except IOError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# if we made it all the way here, then we found no
|
||||||
|
# templates at all, in which case re-attempt the first and
|
||||||
|
# let that error raise on up
|
||||||
|
return render_to_response(mako_path, context, request=self.request)
|
||||||
|
|
||||||
|
def get_page_templates(self):
|
||||||
|
route_slug = self.get_route_slug()
|
||||||
|
page_templates = [f"/quick/{route_slug}.mako"]
|
||||||
|
page_templates.extend(self.get_fallback_templates())
|
||||||
|
return page_templates
|
||||||
|
|
||||||
|
def get_fallback_templates(self):
|
||||||
|
return ["/quick/form.mako"]
|
||||||
|
|
||||||
|
def get_template_context(self, context):
|
||||||
|
return context
|
||||||
|
|
||||||
|
def get_farmos_client(self):
|
||||||
|
token = self.request.session.get("farmos.oauth2.token")
|
||||||
|
if not token:
|
||||||
|
raise self.forbidden()
|
||||||
|
|
||||||
|
# nb. must give a *copy* of the token to farmOS client, since
|
||||||
|
# it will mutate it in-place and we don't want that to happen
|
||||||
|
# for our original copy in the user session. (otherwise the
|
||||||
|
# auto-refresh will not work correctly for subsequent calls.)
|
||||||
|
token = dict(token)
|
||||||
|
|
||||||
|
def token_updater(token):
|
||||||
|
save_farmos_oauth2_token(self.request, token)
|
||||||
|
|
||||||
|
return self.app.get_farmos_client(token=token, token_updater=token_updater)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def defaults(cls, config):
|
||||||
|
cls._defaults(config)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _defaults(cls, config):
|
||||||
|
route_slug = cls.get_route_slug()
|
||||||
|
url_slug = cls.get_url_slug()
|
||||||
|
|
||||||
|
config.add_route(f"quick.{route_slug}", f"/quick/{url_slug}")
|
||||||
|
config.add_view(cls, route_name=f"quick.{route_slug}")
|
||||||
232
src/wuttafarm/web/views/quick/eggs.py
Normal file
232
src/wuttafarm/web/views/quick/eggs.py
Normal file
|
|
@ -0,0 +1,232 @@
|
||||||
|
# -*- 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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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"]])
|
||||||
|
|
||||||
|
assets.sort(key=lambda a: a["name"])
|
||||||
|
self._layer_assets = assets
|
||||||
|
return assets
|
||||||
|
|
||||||
|
def save_quick_form(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 = "egg(s)"
|
||||||
|
|
||||||
|
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}",
|
||||||
|
"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)
|
||||||
|
result = json.loads(response["create-log#body{0}"]["body"])
|
||||||
|
return result
|
||||||
|
|
||||||
|
def redirect_after_save(self, result):
|
||||||
|
return self.redirect(
|
||||||
|
self.request.route_url(
|
||||||
|
"farmos_logs_harvest.view", uuid=result["data"]["id"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def includeme(config):
|
||||||
|
EggsQuickForm.defaults(config)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue