feat: initial basic app to prove display of API (animal) data

This commit is contained in:
Lance Edgar 2026-02-03 19:34:35 -06:00
parent 54ff9d5d83
commit e0f91417cb
23 changed files with 950 additions and 40 deletions

View file

@ -6,7 +6,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "WuttaFarm" name = "WuttaFarm"
version = "0.0.0" version = "0.0.0"
description = "Web app to expose / extend farmOS" description = "Web app to integrate with and extend farmOS"
readme = "README.md" readme = "README.md"
authors = [ authors = [
{name = "Lance Edgar", email = "lance@wuttaproject.org"} {name = "Lance Edgar", email = "lance@wuttaproject.org"}
@ -29,8 +29,9 @@ classifiers = [
] ]
license = {text = "GNU General Public License v3"} license = {text = "GNU General Public License v3"}
dependencies = [ dependencies = [
"psycopg2", "farmOS",
"WuttaWeb[continuum]", "psycopg2",
"WuttaWeb[continuum]",
] ]

26
src/wuttafarm/__init__.py Normal file
View file

@ -0,0 +1,26 @@
# -*- 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/>.
#
################################################################################
"""
package root
"""
from ._version import __version__

View file

@ -0,0 +1,9 @@
# -*- coding: utf-8; -*-
"""
Package Version
"""
from importlib.metadata import version
__version__ = version("WuttaFarm")

57
src/wuttafarm/app.py Normal file
View file

@ -0,0 +1,57 @@
# -*- 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 app handler for WuttaFarm
"""
from wuttjamaican import app as base
class WuttaFarmAppHandler(base.AppHandler):
"""
Custom :term:`app handler` for WuttaFarm.
"""
default_auth_handler_spec = "wuttafarm.auth:WuttaFarmAuthHandler"
def get_farmos_handler(self):
"""
Get the configured farmOS integration handler.
:rtype: :class:`~wuttafarm.farmos.FarmOSHandler`
"""
if "farmos" not in self.handlers:
spec = self.config.get(
f"{self.appname}.farmos_handler",
default="wuttafarm.farmos:FarmOSHandler",
)
factory = self.load_object(spec)
self.handlers["farmos"] = factory(self.config)
return self.handlers["farmos"]
def get_farmos_url(self, *args, **kwargs):
"""
Get a farmOS URL. This is a convenience wrapper around
:meth:`~wuttafarm.farmos.FarmOSHandler.get_farmos_url()`.
"""
handler = self.get_farmos_handler()
return handler.get_farmos_url(*args, **kwargs)

117
src/wuttafarm/auth.py Normal file
View file

@ -0,0 +1,117 @@
# -*- 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/>.
#
################################################################################
"""
Auth handler for use with farmOS
"""
from uuid import UUID
from farmOS import farmOS
from oauthlib.oauth2.rfc6749.errors import InvalidGrantError
from sqlalchemy import orm
from wuttjamaican.auth import AuthHandler
class WuttaFarmAuthHandler(AuthHandler):
"""
Custom auth handler for WuttaFarm. This adds some magic around
the user login process.
It can attempt authentication against the configured farmOS
instance, auto-creating native users within the app DB as needed.
"""
def get_role_farm_manager(self, session):
"""
Returns the special "Farm Manager" role.
"""
return self._special_role(
session, UUID("06979646-b1b9-723b-8000-b86161427f9f"), "Farm Manager"
)
def get_role_farm_worker(self, session):
"""
Returns the special "Farm Worker" role.
"""
return self._special_role(
session, UUID("06979648-9bd6-7b61-8000-ee8818052ee8"), "Farm Worker"
)
def get_role_farm_viewer(self, session):
"""
Returns the special "Farm Viewer" role.
"""
return self._special_role(
session, UUID("06979649-ed32-7e97-8000-113ddf0ab5f3"), "Farm Viewer"
)
def authenticate_user(self, session, username, password):
"""
When authentication is attempted, this first will check
credentials against the app DB per normal logic. If that
succeeds, the result is no different from the typical
behavior.
If default logic fails, this will try to obtain an OAuth2
token from the farmOS site. If that succeeds, then a lookup
is done in the app DB for a matching user. If the user does
not yet exist it will be created automatically.
"""
if user := super().authenticate_user(session, username, password):
return user
if token := self.get_farmos_oauth2_token(username, password):
if user := self.get_or_make_farmos_user(session, username):
user.__dict__["farmos_oauth2_token"] = token
return user
return None
def get_farmos_oauth2_token(self, username, password):
url = self.app.get_farmos_url()
client = farmOS(url)
try:
return client.authorize(username=username, password=password)
except InvalidGrantError:
return None
def get_or_make_farmos_user(self, session, username):
model = self.app.model
try:
user = (
session.query(model.User).filter(model.User.username == username).one()
)
except orm.exc.NoResultFound:
pass
else:
return user if user.active else None
# nb. prevent edit for farmOS mirrored user accounts
user = self.make_user(session, username=username, prevent_edit=True)
# TODO
manager = self.get_role_farm_manager(session)
user.roles.append(manager)
return user

View file

@ -1,4 +1,24 @@
# -*- coding: utf-8; -*- # -*- 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/>.
#
################################################################################
""" """
WuttaFarm CLI WuttaFarm CLI
""" """
@ -9,22 +29,23 @@ from wuttjamaican.cli import make_typer
wuttafarm_typer = make_typer( wuttafarm_typer = make_typer(
name='wuttafarm', name="wuttafarm", help="WuttaFarm -- Web app to integrate with and extend farmOS"
help="WuttaFarm -- Web app to expose / extend farmOS"
) )
@wuttafarm_typer.command() @wuttafarm_typer.command()
def install( def install(
ctx: typer.Context, ctx: typer.Context,
): ):
""" """
Install the WuttaFarm app Install the WuttaFarm app
""" """
config = ctx.parent.wutta_config config = ctx.parent.wutta_config
app = config.get_app() app = config.get_app()
install = app.get_install_handler(pkg_name='wuttafarm', install = app.get_install_handler(
app_title="WuttaFarm", pkg_name="wuttafarm",
pypi_name='WuttaFarm', app_title="WuttaFarm",
egg_name='WuttaFarm') pypi_name="WuttaFarm",
egg_name="WuttaFarm",
)
install.run() install.run()

View file

@ -1,4 +1,24 @@
# -*- coding: utf-8; -*- # -*- 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/>.
#
################################################################################
""" """
WuttaFarm config extensions WuttaFarm config extensions
""" """
@ -10,20 +30,28 @@ class WuttaFarmConfig(WuttaConfigExtension):
""" """
Config extension for WuttaFarm Config extension for WuttaFarm
""" """
key = 'wuttafarm'
key = "wuttafarm"
def configure(self, config): def configure(self, config):
# app info # app info
config.setdefault(f'{config.appname}.app_title', "WuttaFarm") config.setdefault(f"{config.appname}.app_title", "WuttaFarm")
config.setdefault(f'{config.appname}.app_dist', "WuttaFarm") config.setdefault(f"{config.appname}.app_dist", "WuttaFarm")
# app model # app model
config.setdefault(f'{config.appname}.model_spec', 'wuttafarm.db.model') config.setdefault(f"{config.appname}.model_spec", "wuttafarm.db.model")
# app handler
config.setdefault(
f"{config.appname}.app.handler", "wuttafarm.app:WuttaFarmAppHandler"
)
# web app menu # web app menu
config.setdefault(f'{config.appname}.web.menus.handler.spec', config.setdefault(
'wuttafarm.web.menus:WuttaFarmMenuHandler') f"{config.appname}.web.menus.handler.spec",
"wuttafarm.web.menus:WuttaFarmMenuHandler",
)
# web app libcache # web app libcache
#config.setdefault('wuttaweb.static_libcache.module', 'wuttafarm.web.static') # config.setdefault('wuttaweb.static_libcache.module', 'wuttafarm.web.static')

View file

@ -1,4 +1,24 @@
# -*- coding: utf-8; -*- # -*- 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/>.
#
################################################################################
""" """
WuttaFarm data models WuttaFarm data models
""" """

57
src/wuttafarm/farmos.py Normal file
View file

@ -0,0 +1,57 @@
# -*- 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/>.
#
################################################################################
"""
farmOS integration handler
"""
from wuttjamaican.app import GenericHandler
class FarmOSHandler(GenericHandler):
"""
Base class and default implementation for the farmOS integration
:term:`handler`.
"""
def get_farmos_url(self, path=None, require=True):
"""
Returns the base URL for farmOS, or one with ``path`` appended.
Note that if no path is provided, the final slash will be
stripped from the base URL.
:param path: Optional path to append to the base URL.
:param require: If true, an error is raised when base URL
cannot be determined.
:returns: URL string
"""
base = self.config.get("farmos.url.base", require=require)
if base:
base = base.rstrip("/")
if path:
path = path.lstrip("/")
return f"{base}/{path}"
return base

View file

@ -1,4 +1,24 @@
# -*- coding: utf-8; -*- # -*- 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/>.
#
################################################################################
""" """
WuttaFarm web app WuttaFarm web app
""" """

View file

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 colander
from deform.widget import Widget
from webhelpers2.html import HTML, tags
class AnimalImage(Widget):
"""
Widget to display an image URL for an animal.
TODO: this should be refactored to a more general name, once more
types of images need to be supported.
"""
def serialize(self, field, cstruct, **kw):
readonly = kw.get("readonly", self.readonly)
if readonly:
if cstruct in (colander.null, None):
return HTML.tag("span")
return tags.image(cstruct, "animal image", **kw)
return super().serialize(field, cstruct, **kw)

View file

@ -1,4 +1,24 @@
# -*- coding: utf-8; -*- # -*- 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/>.
#
################################################################################
""" """
WuttaFarm Menu WuttaFarm Menu
""" """
@ -12,30 +32,20 @@ class WuttaFarmMenuHandler(base.MenuHandler):
""" """
def make_menus(self, request, **kwargs): def make_menus(self, request, **kwargs):
# nb. the products menu is just an example; you should
# replace it and add more as needed
return [ return [
self.make_products_menu(request), self.make_farmos_menu(request),
self.make_admin_menu(request, include_people=True), self.make_admin_menu(request, include_people=True),
] ]
def make_products_menu(self, request): def make_farmos_menu(self, request):
return { return {
"title": "Products", "title": "farmOS",
"type": "menu", "type": "menu",
"items": [ "items": [
{ {
"title": "Products", "title": "Animals",
"route": "products", "route": "farmos_animals",
"perm": "products.list", "perm": "farmos_animals.list",
},
{'type': 'sep'},
{
"title": "Vendors",
"route": "vendors",
"perm": "vendors.list",
}, },
], ],
} }

View file

@ -1,4 +1,24 @@
# -*- coding: utf-8; -*- # -*- 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/>.
#
################################################################################
""" """
Static assets Static assets
""" """

View file

@ -1,4 +1,24 @@
# -*- coding: utf-8; -*- # -*- 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/>.
#
################################################################################
""" """
Pyramid event subscribers Pyramid event subscribers
""" """
@ -8,9 +28,9 @@ import wuttafarm
def add_wuttafarm_to_context(event): def add_wuttafarm_to_context(event):
renderer_globals = event renderer_globals = event
renderer_globals['wuttafarm'] = wuttafarm renderer_globals["wuttafarm"] = wuttafarm
def includeme(config): def includeme(config):
config.include('wuttaweb.subscribers') config.include("wuttaweb.subscribers")
config.add_subscriber(add_wuttafarm_to_context, 'pyramid.events.BeforeRender') config.add_subscriber(add_wuttafarm_to_context, "pyramid.events.BeforeRender")

View file

@ -0,0 +1,16 @@
<%inherit file="wuttaweb:templates/base.mako" />
<%def name="index_title_controls()">
${parent.index_title_controls()}
% if master is not Undefined and master.listing and farmos_refurl is not Undefined:
<b-button type="is-primary"
tag="a" target="_blank"
href="${farmos_refurl}"
icon-pack="fas"
icon-left="external-link-alt">
View in farmOS
</b-button>
% endif
</%def>

View file

@ -1,13 +1,41 @@
# -*- coding: utf-8; -*- # -*- 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/>.
#
################################################################################
""" """
WuttaFarm Views WuttaFarm Views
""" """
from wuttaweb.views import essential
def includeme(config): def includeme(config):
# core views for wuttaweb # wuttaweb core
config.include("wuttaweb.views.essential") essential.defaults(
config,
**{
"wuttaweb.views.auth": "wuttafarm.web.views.auth",
"wuttaweb.views.common": "wuttafarm.web.views.common",
}
)
# TODO: include your own views here # views for farmOS
# config.include('wuttafarm.web.views.widgets') config.include("wuttafarm.web.views.farmos")

View file

@ -0,0 +1,58 @@
# -*- 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/>.
#
################################################################################
"""
Auth views
"""
from wuttaweb.views import auth as base
class AuthView(base.AuthView):
"""
Auth views
"""
def authenticate_user(self, session, username, password):
if user := super().authenticate_user(session, username, password):
# nb. auth handler will stash the farmOS oauth2 token in
# the user object, if applicable (i.e. unless the user
# authenticated normally via the app DB). when a token is
# involved we need to put that in the user's web session.
if "farmos_oauth2_token" in user.__dict__:
self.request.session["farmos.oauth2.token"] = user.__dict__.pop(
"farmos_oauth2_token"
)
return user
return None
def defaults(config, **kwargs):
local = globals()
AuthView = kwargs.get("AuthView", local["AuthView"])
base.defaults(config, **{"AuthView": AuthView})
def includeme(config):
defaults(config)

View file

@ -0,0 +1,65 @@
# -*- 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/>.
#
################################################################################
"""
Common views
"""
from wuttaweb.views import common as base
class CommonView(base.CommonView):
"""
Common views
"""
def setup_enhance_admin_user(self, user):
""" """
model = self.app.model
session = self.app.get_session(user)
auth = self.app.get_auth_handler()
# initialize built-in roles
farm_manager = auth.get_role_farm_manager(session)
farm_manager.notes = "this is meant to mirror the corresponding role in farmOS"
farm_worker = auth.get_role_farm_worker(session)
farm_worker.notes = "this is meant to mirror the corresponding role in farmOS"
farm_viewer = auth.get_role_farm_viewer(session)
farm_viewer.notes = "this is meant to mirror the corresponding role in farmOS"
site_admin = session.query(model.Role).filter_by(name="Site Admin").first()
if site_admin:
site_admin_perms = [
"farmos_animals.list",
"farmos_animals.view",
]
for perm in site_admin_perms:
auth.grant_permission(site_admin, perm)
def defaults(config, **kwargs):
local = globals()
CommonView = kwargs.get("CommonView", local["CommonView"])
base.defaults(config, **{"CommonView": CommonView})
def includeme(config):
defaults(config)

View 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/>.
#
################################################################################
"""
Views to expose farmOS data directly
"""
from .master import FarmOSMasterView
def includeme(config):
config.include("wuttafarm.web.views.farmos.animals")

View file

@ -0,0 +1,171 @@
# -*- 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 Farm Animals
"""
import datetime
from farmOS import farmOS
from wuttafarm.web.views.farmos import FarmOSMasterView
from wuttafarm.web.forms.widgets import AnimalImage
class AnimalView(FarmOSMasterView):
"""
Master view for Farm Animals
"""
model_name = "farmos_animal"
model_title = "farmOS Animal"
model_title_plural = "farmOS Animals"
route_prefix = "farmos_animals"
url_prefix = "/farmOS/animals"
farmos_refurl_path = "/assets/animal"
labels = {
"species_breed": "Species / Breed",
"raw_image_url": "Raw Image URL",
"large_image_url": "Large Image URL",
"thumbnail_image_url": "Thumbnail Image URL",
}
grid_columns = [
"name",
"species_breed",
"birthdate",
"sex",
"location",
]
sort_defaults = "name"
form_fields = [
"name",
"species_breed",
"birthdate",
"sex",
"location",
"raw_image_url",
"large_image_url",
"thumbnail_image_url",
"image",
]
def get_grid_data(self, columns=None, session=None):
animals = self.farmos_client.resource.get("asset", "animal")
return [self.normalize_animal(a) for a in animals["data"]]
def configure_grid(self, grid):
g = grid
super().configure_grid(g)
# name
g.set_link("name")
g.set_searchable("name")
# birthdate
g.set_renderer("birthdate", "date")
def get_instance(self):
animal = self.farmos_client.resource.get_id(
"asset", "animal", self.request.matchdict["uuid"]
)
# instance data
data = self.normalize_animal(animal["data"])
# add image_url
if relationships := animal["data"].get("relationships"):
if image := relationships.get("image"):
if image["data"]:
image = self.farmos_client.resource.get_id(
"file", "file", image["data"][0]["id"]
)
data["raw_image_url"] = self.app.get_farmos_url(
image["data"]["attributes"]["uri"]["url"]
)
# nb. other styles available: medium, wide
data["large_image_url"] = image["data"]["attributes"][
"image_style_uri"
]["large"]
data["thumbnail_image_url"] = image["data"]["attributes"][
"image_style_uri"
]["thumbnail"]
return data
def get_instance_title(self, animal):
return animal["name"]
def normalize_animal(self, animal):
birthdate = animal["attributes"]["birthdate"]
if birthdate:
birthdate = datetime.datetime.fromisoformat(birthdate)
birthdate = self.app.localtime(birthdate)
return {
"uuid": animal["id"],
"drupal_internal_id": animal["attributes"]["drupal_internal__id"],
"name": animal["attributes"]["name"],
"species_breed": "", # TODO
"birthdate": birthdate,
"sex": animal["attributes"]["sex"],
"location": "", # TODO
}
def configure_form(self, form):
f = form
super().configure_form(f)
animal = f.model_instance
# image
if url := animal.get("large_image_url"):
f.set_widget("image", AnimalImage())
f.set_default("image", url)
def get_xref_buttons(self, animal):
return [
self.make_button(
"View in farmOS",
primary=True,
url=self.app.get_farmos_url(f"/asset/{animal['drupal_internal_id']}"),
target="_blank",
icon_left="external-link-alt",
),
]
def defaults(config, **kwargs):
base = globals()
AnimalView = kwargs.get("AnimalView", base["AnimalView"])
AnimalView.defaults(config)
def includeme(config):
defaults(config)

View file

@ -0,0 +1,65 @@
# -*- 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
"""
from farmOS import farmOS
from wuttaweb.views import MasterView
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
def __init__(self, request, context=None):
super().__init__(request, context=context)
self.farmos_client = self.get_farmos_client()
def get_farmos_client(self):
token = self.request.session.get("farmos.oauth2.token")
if not token:
raise self.forbidden()
url = self.app.get_farmos_url()
return farmOS(url, token=token)
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)
return context

24
tasks.py Normal file
View file

@ -0,0 +1,24 @@
# -*- coding: utf-8; -*-
"""
Tasks for WuttaFarm
"""
import os
import shutil
from invoke import task
@task
def release(c, skip_tests=False):
"""
Release a new version of WuttaFarm
"""
if not skip_tests:
c.run("pytest")
if os.path.exists("dist"):
shutil.rmtree("dist")
c.run("python -m build --sdist")
c.run("twine upload dist/*")