483 lines
14 KiB
Python
483 lines
14 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/>.
|
|
#
|
|
################################################################################
|
|
"""
|
|
Master view for Plants
|
|
"""
|
|
|
|
from webhelpers2.html import tags
|
|
|
|
from wuttaweb.forms.schema import WuttaDictEnum
|
|
from wuttaweb.util import get_form_data
|
|
|
|
from wuttafarm.db.model import PlantType, Season, PlantAsset
|
|
from wuttafarm.web.views.assets import AssetTypeMasterView, AssetMasterView
|
|
from wuttafarm.web.forms.schema import PlantTypeRefs, SeasonRefs
|
|
from wuttafarm.web.forms.widgets import ImageWidget
|
|
from wuttafarm.web.util import get_farmos_client_for_user
|
|
|
|
|
|
class PlantTypeView(AssetTypeMasterView):
|
|
"""
|
|
Master view for Plant Types
|
|
"""
|
|
|
|
model_class = PlantType
|
|
route_prefix = "plant_types"
|
|
url_prefix = "/plant-types"
|
|
|
|
farmos_entity_type = "taxonomy_term"
|
|
farmos_bundle = "plant_type"
|
|
farmos_refurl_path = "/admin/structure/taxonomy/manage/plant_type/overview"
|
|
|
|
grid_columns = [
|
|
"name",
|
|
"description",
|
|
]
|
|
|
|
sort_defaults = "name"
|
|
|
|
filter_defaults = {
|
|
"name": {"active": True, "verb": "contains"},
|
|
}
|
|
|
|
form_fields = [
|
|
"name",
|
|
"description",
|
|
"drupal_id",
|
|
"farmos_uuid",
|
|
]
|
|
|
|
has_rows = True
|
|
row_model_class = PlantAsset
|
|
rows_viewable = True
|
|
|
|
row_grid_columns = [
|
|
"asset_name",
|
|
"archived",
|
|
]
|
|
|
|
rows_sort_defaults = "asset_name"
|
|
|
|
def configure_grid(self, grid):
|
|
g = grid
|
|
super().configure_grid(g)
|
|
|
|
# name
|
|
g.set_link("name")
|
|
|
|
def get_farmos_url(self, plant_type):
|
|
return self.app.get_farmos_url(f"/taxonomy/term/{plant_type.drupal_id}")
|
|
|
|
def get_xref_buttons(self, plant_type):
|
|
buttons = super().get_xref_buttons(plant_type)
|
|
|
|
if plant_type.farmos_uuid:
|
|
buttons.append(
|
|
self.make_button(
|
|
"View farmOS record",
|
|
primary=True,
|
|
url=self.request.route_url(
|
|
"farmos_plant_types.view", uuid=plant_type.farmos_uuid
|
|
),
|
|
icon_left="eye",
|
|
)
|
|
)
|
|
|
|
return buttons
|
|
|
|
def delete(self):
|
|
plant_type = self.get_instance()
|
|
|
|
if plant_type._plant_assets:
|
|
self.request.session.flash(
|
|
"Cannot delete plant type which is still referenced by plant assets.",
|
|
"warning",
|
|
)
|
|
url = self.get_action_url("view", plant_type)
|
|
return self.redirect(self.request.get_referrer(default=url))
|
|
|
|
return super().delete()
|
|
|
|
def get_row_grid_data(self, plant_type):
|
|
model = self.app.model
|
|
session = self.Session()
|
|
return (
|
|
session.query(model.PlantAsset)
|
|
.join(model.Asset)
|
|
.outerjoin(model.PlantAssetPlantType)
|
|
.filter(model.PlantAssetPlantType.plant_type == plant_type)
|
|
)
|
|
|
|
def configure_row_grid(self, grid):
|
|
g = grid
|
|
super().configure_row_grid(g)
|
|
model = self.app.model
|
|
|
|
# asset_name
|
|
g.set_link("asset_name")
|
|
g.set_sorter("asset_name", model.Asset.asset_name)
|
|
g.set_filter("asset_name", model.Asset.asset_name)
|
|
|
|
# archived
|
|
g.set_renderer("archived", "boolean")
|
|
g.set_sorter("archived", model.Asset.archived)
|
|
g.set_filter("archived", model.Asset.archived)
|
|
|
|
def get_row_action_url_view(self, plant, i):
|
|
return self.request.route_url("plant_assets.view", uuid=plant.uuid)
|
|
|
|
def ajax_create(self):
|
|
"""
|
|
AJAX view to create a new plant type.
|
|
"""
|
|
model = self.app.model
|
|
session = self.Session()
|
|
data = get_form_data(self.request)
|
|
|
|
name = data.get("name")
|
|
if not name:
|
|
return {"error": "Name is required"}
|
|
|
|
plant_type = model.PlantType(name=name)
|
|
session.add(plant_type)
|
|
session.flush()
|
|
|
|
if self.app.is_farmos_mirror():
|
|
client = get_farmos_client_for_user(self.request)
|
|
self.app.auto_sync_to_farmos(plant_type, client=client)
|
|
|
|
return {
|
|
"uuid": plant_type.uuid.hex,
|
|
"name": plant_type.name,
|
|
"farmos_uuid": plant_type.farmos_uuid.hex,
|
|
"drupal_id": plant_type.drupal_id,
|
|
}
|
|
|
|
@classmethod
|
|
def defaults(cls, config):
|
|
""" """
|
|
cls._defaults(config)
|
|
cls._plant_type_defaults(config)
|
|
|
|
@classmethod
|
|
def _plant_type_defaults(cls, config):
|
|
route_prefix = cls.get_route_prefix()
|
|
permission_prefix = cls.get_permission_prefix()
|
|
url_prefix = cls.get_url_prefix()
|
|
|
|
# ajax_create
|
|
config.add_route(f"{route_prefix}.ajax_create", f"{url_prefix}/ajax/new")
|
|
config.add_view(
|
|
cls,
|
|
attr="ajax_create",
|
|
route_name=f"{route_prefix}.ajax_create",
|
|
permission=f"{permission_prefix}.create",
|
|
renderer="json",
|
|
)
|
|
|
|
|
|
class SeasonView(AssetTypeMasterView):
|
|
"""
|
|
Master view for Seasons
|
|
"""
|
|
|
|
model_class = Season
|
|
route_prefix = "seasons"
|
|
url_prefix = "/seasons"
|
|
|
|
farmos_entity_type = "taxonomy_term"
|
|
farmos_bundle = "season"
|
|
farmos_refurl_path = "/admin/structure/taxonomy/manage/season/overview"
|
|
|
|
grid_columns = [
|
|
"name",
|
|
"description",
|
|
]
|
|
|
|
sort_defaults = "name"
|
|
|
|
filter_defaults = {
|
|
"name": {"active": True, "verb": "contains"},
|
|
}
|
|
|
|
form_fields = [
|
|
"name",
|
|
"description",
|
|
"drupal_id",
|
|
"farmos_uuid",
|
|
]
|
|
|
|
has_rows = True
|
|
row_model_class = PlantAsset
|
|
rows_viewable = True
|
|
|
|
row_grid_columns = [
|
|
"asset_name",
|
|
"archived",
|
|
]
|
|
|
|
rows_sort_defaults = "asset_name"
|
|
|
|
def configure_grid(self, grid):
|
|
g = grid
|
|
super().configure_grid(g)
|
|
|
|
# name
|
|
g.set_link("name")
|
|
|
|
def get_farmos_url(self, season):
|
|
return self.app.get_farmos_url(f"/taxonomy/term/{season.drupal_id}")
|
|
|
|
def get_xref_buttons(self, season):
|
|
buttons = super().get_xref_buttons(season)
|
|
|
|
if season.farmos_uuid:
|
|
buttons.append(
|
|
self.make_button(
|
|
"View farmOS record",
|
|
primary=True,
|
|
url=self.request.route_url(
|
|
"farmos_seasons.view", uuid=season.farmos_uuid
|
|
),
|
|
icon_left="eye",
|
|
)
|
|
)
|
|
|
|
return buttons
|
|
|
|
def delete(self):
|
|
season = self.get_instance()
|
|
|
|
if season._plant_assets:
|
|
self.request.session.flash(
|
|
"Cannot delete season which is still referenced by plant assets.",
|
|
"warning",
|
|
)
|
|
url = self.get_action_url("view", season)
|
|
return self.redirect(self.request.get_referrer(default=url))
|
|
|
|
return super().delete()
|
|
|
|
def get_row_grid_data(self, season):
|
|
model = self.app.model
|
|
session = self.Session()
|
|
return (
|
|
session.query(model.PlantAsset)
|
|
.join(model.Asset)
|
|
.outerjoin(model.PlantAssetSeason)
|
|
.filter(model.PlantAssetSeason.season == season)
|
|
)
|
|
|
|
def configure_row_grid(self, grid):
|
|
g = grid
|
|
super().configure_row_grid(g)
|
|
model = self.app.model
|
|
|
|
# asset_name
|
|
g.set_link("asset_name")
|
|
g.set_sorter("asset_name", model.Asset.asset_name)
|
|
g.set_filter("asset_name", model.Asset.asset_name)
|
|
|
|
# archived
|
|
g.set_renderer("archived", "boolean")
|
|
g.set_sorter("archived", model.Asset.archived)
|
|
g.set_filter("archived", model.Asset.archived)
|
|
|
|
def get_row_action_url_view(self, plant, i):
|
|
return self.request.route_url("plant_assets.view", uuid=plant.uuid)
|
|
|
|
def ajax_create(self):
|
|
"""
|
|
AJAX view to create a new season.
|
|
"""
|
|
model = self.app.model
|
|
session = self.Session()
|
|
data = get_form_data(self.request)
|
|
|
|
name = data.get("name")
|
|
if not name:
|
|
return {"error": "Name is required"}
|
|
|
|
season = model.Season(name=name)
|
|
session.add(season)
|
|
session.flush()
|
|
|
|
if self.app.is_farmos_mirror():
|
|
client = get_farmos_client_for_user(self.request)
|
|
self.app.auto_sync_to_farmos(season, client=client)
|
|
|
|
return {
|
|
"uuid": season.uuid.hex,
|
|
"name": season.name,
|
|
"farmos_uuid": season.farmos_uuid.hex,
|
|
"drupal_id": season.drupal_id,
|
|
}
|
|
|
|
@classmethod
|
|
def defaults(cls, config):
|
|
""" """
|
|
cls._defaults(config)
|
|
cls._season_defaults(config)
|
|
|
|
@classmethod
|
|
def _season_defaults(cls, config):
|
|
route_prefix = cls.get_route_prefix()
|
|
permission_prefix = cls.get_permission_prefix()
|
|
url_prefix = cls.get_url_prefix()
|
|
|
|
# ajax_create
|
|
config.add_route(f"{route_prefix}.ajax_create", f"{url_prefix}/ajax/new")
|
|
config.add_view(
|
|
cls,
|
|
attr="ajax_create",
|
|
route_name=f"{route_prefix}.ajax_create",
|
|
permission=f"{permission_prefix}.create",
|
|
renderer="json",
|
|
)
|
|
|
|
|
|
class PlantAssetView(AssetMasterView):
|
|
"""
|
|
Master view for Plant Assets
|
|
"""
|
|
|
|
model_class = PlantAsset
|
|
route_prefix = "plant_assets"
|
|
url_prefix = "/assets/plant"
|
|
|
|
farmos_bundle = "plant"
|
|
farmos_refurl_path = "/assets/plant"
|
|
|
|
labels = {
|
|
"plant_types": "Crop/Variety",
|
|
"seasons": "Season",
|
|
}
|
|
|
|
grid_columns = [
|
|
"thumbnail",
|
|
"drupal_id",
|
|
"asset_name",
|
|
"plant_types",
|
|
"season",
|
|
"archived",
|
|
]
|
|
|
|
def configure_grid(self, grid):
|
|
g = grid
|
|
super().configure_grid(g)
|
|
|
|
# plant_types
|
|
g.set_renderer("plant_types", self.render_plant_types_for_grid)
|
|
|
|
def render_plant_types_for_grid(self, plant, field, value):
|
|
plant_types = plant._plant_types
|
|
|
|
if self.farmos_style_grid_links:
|
|
links = []
|
|
for plant_type in plant_types:
|
|
plant_type = plant_type.plant_type
|
|
url = self.request.route_url("plant_types.view", uuid=plant_type.uuid)
|
|
links.append(tags.link_to(str(plant_type), url))
|
|
return ", ".join(links)
|
|
|
|
return ", ".join([str(pt.plant_type) for pt in plant_types])
|
|
|
|
def configure_form(self, form):
|
|
f = form
|
|
super().configure_form(f)
|
|
enum = self.app.enum
|
|
plant = f.model_instance
|
|
|
|
# plant_types
|
|
f.fields.insert_after("asset_name", "plant_types")
|
|
f.set_node("plant_types", PlantTypeRefs(self.request))
|
|
if not self.creating:
|
|
# nb. must explcitly declare value for non-standard field
|
|
f.set_default("plant_types", [pt.uuid for pt in plant.plant_types])
|
|
|
|
# season
|
|
f.fields.insert_after("plant_types", "seasons")
|
|
f.set_node("seasons", SeasonRefs(self.request))
|
|
f.set_required("seasons", False)
|
|
if not self.creating:
|
|
# nb. must explcitly declare value for non-standard field
|
|
f.set_default("seasons", plant.seasons)
|
|
|
|
def objectify(self, form):
|
|
plant = super().objectify(form)
|
|
data = form.validated
|
|
|
|
self.set_plant_types(plant, data["plant_types"])
|
|
self.set_seasons(plant, data["seasons"])
|
|
|
|
return plant
|
|
|
|
def set_plant_types(self, plant, desired):
|
|
model = self.app.model
|
|
session = self.Session()
|
|
current = [pt.uuid for pt in plant.plant_types]
|
|
|
|
for uuid in desired:
|
|
if uuid not in current:
|
|
plant_type = session.get(model.PlantType, uuid)
|
|
assert plant_type
|
|
plant.plant_types.append(plant_type)
|
|
|
|
for uuid in current:
|
|
if uuid not in desired:
|
|
plant_type = session.get(model.PlantType, uuid)
|
|
assert plant_type
|
|
plant.plant_types.remove(plant_type)
|
|
|
|
def set_seasons(self, plant, desired):
|
|
model = self.app.model
|
|
session = self.Session()
|
|
current = [s.uuid for s in plant.seasons]
|
|
|
|
for uuid in desired:
|
|
if uuid not in current:
|
|
season = session.get(model.Season, uuid)
|
|
assert season
|
|
plant.seasons.append(season)
|
|
|
|
for uuid in current:
|
|
if uuid not in desired:
|
|
season = session.get(model.Season, uuid)
|
|
assert season
|
|
plant.seasons.remove(season)
|
|
|
|
|
|
def defaults(config, **kwargs):
|
|
base = globals()
|
|
|
|
PlantTypeView = kwargs.get("PlantTypeView", base["PlantTypeView"])
|
|
PlantTypeView.defaults(config)
|
|
|
|
SeasonView = kwargs.get("SeasonView", base["SeasonView"])
|
|
SeasonView.defaults(config)
|
|
|
|
PlantAssetView = kwargs.get("PlantAssetView", base["PlantAssetView"])
|
|
PlantAssetView.defaults(config)
|
|
|
|
|
|
def includeme(config):
|
|
defaults(config)
|