# -*- 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 farmOS master views
"""
import datetime
import json
import colander
import markdown
from webhelpers2.html import tags
from wuttaweb.views import MasterView
from wuttaweb.forms.schema import WuttaDateTime
from wuttaweb.forms.widgets import WuttaDateTimeWidget
from wuttafarm.web.util import get_farmos_client_for_user, use_farmos_style_grid_links
from wuttafarm.web.grids import (
ResourceData,
StringFilter,
NullableStringFilter,
DateTimeFilter,
SimpleSorter,
)
[docs]
class FarmOSMasterView(MasterView):
"""
Base class for farmOS master views
"""
model_key = "uuid"
creatable = False
editable = False
deletable = False
filterable = False
sort_on_backend = False
paginate_on_backend = False
farmos_refurl_path = None
labels = {
"drupal_id": "Drupal ID",
"raw_image_url": "Raw Image URL",
"large_image_url": "Large Image URL",
"thumbnail_image_url": "Thumbnail Image URL",
}
def __init__(self, request, context=None):
super().__init__(request, context=context)
self.farmos_client = get_farmos_client_for_user(self.request)
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.farmos_style_grid_links = use_farmos_style_grid_links(self.config)
def get_fallback_templates(self, template):
""" """
templates = super().get_fallback_templates(template)
if template == "view":
templates.insert(0, "/farmos/master/view.mako")
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)
[docs]
def get_template_context(self, context):
if self.listing and self.farmos_refurl_path:
context["farmos_refurl"] = self.app.get_farmos_url(self.farmos_refurl_path)
if self.viewing and self.raw_json:
context["raw_json"] = self.raw_json
code = "```json\n" + json.dumps(self.raw_json, indent=2) + "\n```"
# TODO: this does not seem to be adding syntax highlight
context["rendered_json"] = markdown.markdown(
code, extensions=["fenced_code", "codehilite"]
)
return context
[docs]
class TaxonomyMasterView(FarmOSMasterView):
"""
Base class for farmOS "taxonomy term" views
"""
farmos_taxonomy_type = None
creatable = True
editable = True
deletable = True
filterable = True
sort_on_backend = True
grid_columns = [
"name",
"description",
"changed",
]
sort_defaults = "name"
filter_defaults = {
"name": {"active": True, "verb": "contains"},
}
form_fields = [
"name",
"description",
"changed",
]
[docs]
def get_grid_data(self, columns=None, session=None):
return ResourceData(
self.config,
self.farmos_client,
f"taxonomy_term--{self.farmos_taxonomy_type}",
normalizer=self.normalize_taxonomy_term,
)
def normalize_taxonomy_term(self, term, included):
if changed := term["attributes"]["changed"]:
changed = datetime.datetime.fromisoformat(changed)
changed = self.app.localtime(changed)
if description := term["attributes"]["description"]:
description = description["value"]
return {
"uuid": term["id"],
"drupal_id": term["attributes"]["drupal_internal__tid"],
"name": term["attributes"]["name"],
"description": description or colander.null,
"changed": changed,
}
[docs]
def get_instance(self):
result = self.farmos_client.resource.get_id(
"taxonomy_term", self.farmos_taxonomy_type, self.request.matchdict["uuid"]
)
self.raw_json = result
return self.normalize_taxonomy_term(result["data"], {})
[docs]
def get_instance_title(self, term):
return term["name"]
def get_api_payload(self, term):
attrs = {
"name": term["name"],
}
if description := term["description"]:
attrs["description"] = {"value": description}
else:
attrs["description"] = None
return {"attributes": attrs}
[docs]
def persist(self, term, session=None):
payload = self.get_api_payload(term)
if self.editing:
payload["id"] = term["uuid"]
result = self.farmos_client.resource.send(
"taxonomy_term", self.farmos_taxonomy_type, payload
)
if self.creating:
term["uuid"] = result["data"]["id"]
[docs]
def delete_instance(self, term):
self.farmos_client.resource.delete(
"taxonomy_term", self.farmos_taxonomy_type, term["uuid"]
)