feat: add view for farmOS users

This commit is contained in:
Lance Edgar 2026-02-07 14:26:49 -06:00
parent f0a2308bd9
commit 7415504926
7 changed files with 239 additions and 8 deletions

View file

@ -0,0 +1,47 @@
# -*- 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/>.
#
################################################################################
"""
Custom form widgets for WuttaFarm
"""
import json
import colander
class UsersType(colander.SchemaType):
def __init__(self, request, *args, **kwargs):
super().__init__(*args, **kwargs)
self.request = request
def serialize(self, node, appstruct):
if appstruct is colander.null:
return colander.null
return json.dumps(appstruct)
def widget_maker(self, **kwargs): # pylint: disable=empty-docstring
""" """
from wuttafarm.web.forms.widgets import UsersWidget
return UsersWidget(self.request, **kwargs)

View file

@ -23,6 +23,8 @@
Custom form widgets for WuttaFarm Custom form widgets for WuttaFarm
""" """
import json
import colander import colander
from deform.widget import Widget from deform.widget import Widget
from webhelpers2.html import HTML, tags from webhelpers2.html import HTML, tags
@ -45,3 +47,31 @@ class AnimalImage(Widget):
return tags.image(cstruct, "animal image", **kw) return tags.image(cstruct, "animal image", **kw)
return super().serialize(field, cstruct, **kw) return super().serialize(field, cstruct, **kw)
class UsersWidget(Widget):
"""
Widget to display the list of owners for an asset etc.
"""
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")
items = []
for user in json.loads(cstruct):
link = tags.link_to(
user["display_name"],
self.request.route_url("farmos_users.view", uuid=user["uuid"]),
)
items.append(HTML.tag("li", c=link))
return HTML.tag("ul", c=items)
return super().serialize(field, cstruct, **kw)

View file

@ -47,5 +47,11 @@ class WuttaFarmMenuHandler(base.MenuHandler):
"route": "farmos_animals", "route": "farmos_animals",
"perm": "farmos_animals.list", "perm": "farmos_animals.list",
}, },
{"type": "sep"},
{
"title": "Users",
"route": "farmos_users",
"perm": "farmos_users.list",
},
], ],
} }

View file

@ -50,6 +50,8 @@ class CommonView(base.CommonView):
site_admin_perms = [ site_admin_perms = [
"farmos_animals.list", "farmos_animals.list",
"farmos_animals.view", "farmos_animals.view",
"farmos_users.list",
"farmos_users.view",
] ]
for perm in site_admin_perms: for perm in site_admin_perms:
auth.grant_permission(site_admin, perm) auth.grant_permission(site_admin, perm)

View file

@ -27,4 +27,5 @@ from .master import FarmOSMasterView
def includeme(config): def includeme(config):
config.include("wuttafarm.web.views.farmos.users")
config.include("wuttafarm.web.views.farmos.animals") config.include("wuttafarm.web.views.farmos.animals")

View file

@ -28,6 +28,8 @@ import datetime
import colander import colander
from wuttafarm.web.views.farmos import FarmOSMasterView from wuttafarm.web.views.farmos import FarmOSMasterView
from wuttafarm.web.forms.schema import UsersType
from wuttafarm.web.forms.widgets import AnimalImage from wuttafarm.web.forms.widgets import AnimalImage
@ -123,16 +125,17 @@ class AnimalView(FarmOSMasterView):
# add owners # add owners
if owner := relationships.get("owner"): if owner := relationships.get("owner"):
owners = [] data["owners"] = []
for owner_data in owner["data"]: for owner_data in owner["data"]:
owners.append( owner = self.farmos_client.resource.get_id(
self.farmos_client.resource.get_id( "user", "user", owner_data["id"]
"user", "user", owner_data["id"] )
) data["owners"].append(
{
"uuid": owner["data"]["id"],
"display_name": owner["data"]["attributes"]["display_name"],
}
) )
data["owners"] = ", ".join(
[o["data"]["attributes"]["display_name"] for o in owners]
)
# add image urls # add image urls
if image := relationships.get("image"): if image := relationships.get("image"):
@ -184,6 +187,9 @@ class AnimalView(FarmOSMasterView):
# is_castrated # is_castrated
f.set_node("is_castrated", colander.Boolean()) f.set_node("is_castrated", colander.Boolean())
# owners
f.set_node("owners", UsersType(self.request))
# notes # notes
f.set_widget("notes", "notes") f.set_widget("notes", "notes")

View file

@ -0,0 +1,139 @@
# -*- 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/>.
#
################################################################################
"""
View for farmOS Users
"""
import datetime
import colander
from wuttaweb.forms.schema import WuttaDateTime
from wuttafarm.web.views.farmos import FarmOSMasterView
class UserView(FarmOSMasterView):
"""
Master view for Farm Animals
"""
model_name = "farmos_user"
model_title = "farmOS User"
model_title_plural = "farmOS Users"
route_prefix = "farmos_users"
url_prefix = "/farmOS/users"
farmos_refurl_path = "/people"
grid_columns = [
"display_name",
]
sort_defaults = "display_name"
form_fields = [
"display_name",
"name",
"mail",
"timezone",
"created",
"changed",
]
def get_grid_data(self, columns=None, session=None):
users = self.farmos_client.resource.get("user", "user")
return [self.normalize_user(u) for u in users["data"]]
def configure_grid(self, grid):
g = grid
super().configure_grid(g)
# display_name
g.set_link("display_name")
g.set_searchable("display_name")
def get_instance(self):
user = self.farmos_client.resource.get_id(
"user", "user", self.request.matchdict["uuid"]
)
return self.normalize_user(user["data"])
def get_instance_title(self, user):
return user["display_name"]
def normalize_user(self, user):
if created := user["attributes"].get("created"):
created = datetime.datetime.fromisoformat(created)
created = self.app.localtime(created)
if changed := user["attributes"].get("changed"):
changed = datetime.datetime.fromisoformat(changed)
changed = self.app.localtime(changed)
return {
"uuid": user["id"],
"drupal_internal_id": user["attributes"].get("drupal_internal__uid"),
"display_name": user["attributes"]["display_name"],
"name": user["attributes"].get("name") or colander.null,
"mail": user["attributes"].get("mail") or colander.null,
"timezone": user["attributes"].get("timezone") or colander.null,
"created": created,
"changed": changed,
}
def configure_form(self, form):
f = form
super().configure_form(f)
user = f.model_instance
# created
f.set_node("created", WuttaDateTime())
# changed
f.set_node("changed", WuttaDateTime())
def get_xref_buttons(self, user):
if drupal_id := user["drupal_internal_id"]:
return [
self.make_button(
"View in farmOS",
primary=True,
url=self.app.get_farmos_url(f"/user/{drupal_id}"),
target="_blank",
icon_left="external-link-alt",
),
]
return None
def defaults(config, **kwargs):
base = globals()
UserView = kwargs.get("UserView", base["UserView"])
UserView.defaults(config)
def includeme(config):
defaults(config)