feat: initial basic app to prove display of API (animal) data
This commit is contained in:
parent
54ff9d5d83
commit
e0f91417cb
23 changed files with 950 additions and 40 deletions
|
|
@ -6,7 +6,7 @@ build-backend = "hatchling.build"
|
|||
[project]
|
||||
name = "WuttaFarm"
|
||||
version = "0.0.0"
|
||||
description = "Web app to expose / extend farmOS"
|
||||
description = "Web app to integrate with and extend farmOS"
|
||||
readme = "README.md"
|
||||
authors = [
|
||||
{name = "Lance Edgar", email = "lance@wuttaproject.org"}
|
||||
|
|
@ -29,8 +29,9 @@ classifiers = [
|
|||
]
|
||||
license = {text = "GNU General Public License v3"}
|
||||
dependencies = [
|
||||
"psycopg2",
|
||||
"WuttaWeb[continuum]",
|
||||
"farmOS",
|
||||
"psycopg2",
|
||||
"WuttaWeb[continuum]",
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
26
src/wuttafarm/__init__.py
Normal file
26
src/wuttafarm/__init__.py
Normal 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__
|
||||
9
src/wuttafarm/_version.py
Normal file
9
src/wuttafarm/_version.py
Normal 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
57
src/wuttafarm/app.py
Normal 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
117
src/wuttafarm/auth.py
Normal 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
|
||||
|
|
@ -1,4 +1,24 @@
|
|||
# -*- 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
|
||||
"""
|
||||
|
|
@ -9,22 +29,23 @@ from wuttjamaican.cli import make_typer
|
|||
|
||||
|
||||
wuttafarm_typer = make_typer(
|
||||
name='wuttafarm',
|
||||
help="WuttaFarm -- Web app to expose / extend farmOS"
|
||||
name="wuttafarm", help="WuttaFarm -- Web app to integrate with and extend farmOS"
|
||||
)
|
||||
|
||||
|
||||
@wuttafarm_typer.command()
|
||||
def install(
|
||||
ctx: typer.Context,
|
||||
ctx: typer.Context,
|
||||
):
|
||||
"""
|
||||
Install the WuttaFarm app
|
||||
"""
|
||||
config = ctx.parent.wutta_config
|
||||
app = config.get_app()
|
||||
install = app.get_install_handler(pkg_name='wuttafarm',
|
||||
app_title="WuttaFarm",
|
||||
pypi_name='WuttaFarm',
|
||||
egg_name='WuttaFarm')
|
||||
install = app.get_install_handler(
|
||||
pkg_name="wuttafarm",
|
||||
app_title="WuttaFarm",
|
||||
pypi_name="WuttaFarm",
|
||||
egg_name="WuttaFarm",
|
||||
)
|
||||
install.run()
|
||||
|
|
|
|||
|
|
@ -1,4 +1,24 @@
|
|||
# -*- 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
|
||||
"""
|
||||
|
|
@ -10,20 +30,28 @@ class WuttaFarmConfig(WuttaConfigExtension):
|
|||
"""
|
||||
Config extension for WuttaFarm
|
||||
"""
|
||||
key = 'wuttafarm'
|
||||
|
||||
key = "wuttafarm"
|
||||
|
||||
def configure(self, config):
|
||||
|
||||
# app info
|
||||
config.setdefault(f'{config.appname}.app_title', "WuttaFarm")
|
||||
config.setdefault(f'{config.appname}.app_dist', "WuttaFarm")
|
||||
config.setdefault(f"{config.appname}.app_title", "WuttaFarm")
|
||||
config.setdefault(f"{config.appname}.app_dist", "WuttaFarm")
|
||||
|
||||
# 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
|
||||
config.setdefault(f'{config.appname}.web.menus.handler.spec',
|
||||
'wuttafarm.web.menus:WuttaFarmMenuHandler')
|
||||
config.setdefault(
|
||||
f"{config.appname}.web.menus.handler.spec",
|
||||
"wuttafarm.web.menus:WuttaFarmMenuHandler",
|
||||
)
|
||||
|
||||
# web app libcache
|
||||
#config.setdefault('wuttaweb.static_libcache.module', 'wuttafarm.web.static')
|
||||
# config.setdefault('wuttaweb.static_libcache.module', 'wuttafarm.web.static')
|
||||
|
|
|
|||
|
|
@ -1,4 +1,24 @@
|
|||
# -*- 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
|
||||
"""
|
||||
|
|
|
|||
57
src/wuttafarm/farmos.py
Normal file
57
src/wuttafarm/farmos.py
Normal 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
|
||||
|
|
@ -1,4 +1,24 @@
|
|||
# -*- 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
|
||||
"""
|
||||
|
|
|
|||
0
src/wuttafarm/web/forms/__init__.py
Normal file
0
src/wuttafarm/web/forms/__init__.py
Normal file
47
src/wuttafarm/web/forms/widgets.py
Normal file
47
src/wuttafarm/web/forms/widgets.py
Normal 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)
|
||||
|
|
@ -1,4 +1,24 @@
|
|||
# -*- 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
|
||||
"""
|
||||
|
|
@ -12,30 +32,20 @@ class WuttaFarmMenuHandler(base.MenuHandler):
|
|||
"""
|
||||
|
||||
def make_menus(self, request, **kwargs):
|
||||
|
||||
# nb. the products menu is just an example; you should
|
||||
# replace it and add more as needed
|
||||
|
||||
return [
|
||||
self.make_products_menu(request),
|
||||
self.make_farmos_menu(request),
|
||||
self.make_admin_menu(request, include_people=True),
|
||||
]
|
||||
|
||||
def make_products_menu(self, request):
|
||||
def make_farmos_menu(self, request):
|
||||
return {
|
||||
"title": "Products",
|
||||
"title": "farmOS",
|
||||
"type": "menu",
|
||||
"items": [
|
||||
{
|
||||
"title": "Products",
|
||||
"route": "products",
|
||||
"perm": "products.list",
|
||||
},
|
||||
{'type': 'sep'},
|
||||
{
|
||||
"title": "Vendors",
|
||||
"route": "vendors",
|
||||
"perm": "vendors.list",
|
||||
"title": "Animals",
|
||||
"route": "farmos_animals",
|
||||
"perm": "farmos_animals.list",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,24 @@
|
|||
# -*- 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
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,4 +1,24 @@
|
|||
# -*- 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
|
||||
"""
|
||||
|
|
@ -8,9 +28,9 @@ import wuttafarm
|
|||
|
||||
def add_wuttafarm_to_context(event):
|
||||
renderer_globals = event
|
||||
renderer_globals['wuttafarm'] = wuttafarm
|
||||
renderer_globals["wuttafarm"] = wuttafarm
|
||||
|
||||
|
||||
def includeme(config):
|
||||
config.include('wuttaweb.subscribers')
|
||||
config.add_subscriber(add_wuttafarm_to_context, 'pyramid.events.BeforeRender')
|
||||
config.include("wuttaweb.subscribers")
|
||||
config.add_subscriber(add_wuttafarm_to_context, "pyramid.events.BeforeRender")
|
||||
|
|
|
|||
16
src/wuttafarm/web/templates/base.mako
Normal file
16
src/wuttafarm/web/templates/base.mako
Normal 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>
|
||||
|
|
@ -1,13 +1,41 @@
|
|||
# -*- 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
|
||||
"""
|
||||
|
||||
from wuttaweb.views import essential
|
||||
|
||||
|
||||
def includeme(config):
|
||||
|
||||
# core views for wuttaweb
|
||||
config.include("wuttaweb.views.essential")
|
||||
# wuttaweb core
|
||||
essential.defaults(
|
||||
config,
|
||||
**{
|
||||
"wuttaweb.views.auth": "wuttafarm.web.views.auth",
|
||||
"wuttaweb.views.common": "wuttafarm.web.views.common",
|
||||
}
|
||||
)
|
||||
|
||||
# TODO: include your own views here
|
||||
# config.include('wuttafarm.web.views.widgets')
|
||||
# views for farmOS
|
||||
config.include("wuttafarm.web.views.farmos")
|
||||
|
|
|
|||
58
src/wuttafarm/web/views/auth.py
Normal file
58
src/wuttafarm/web/views/auth.py
Normal 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)
|
||||
65
src/wuttafarm/web/views/common.py
Normal file
65
src/wuttafarm/web/views/common.py
Normal 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)
|
||||
30
src/wuttafarm/web/views/farmos/__init__.py
Normal file
30
src/wuttafarm/web/views/farmos/__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/>.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
Views to expose farmOS data directly
|
||||
"""
|
||||
|
||||
from .master import FarmOSMasterView
|
||||
|
||||
|
||||
def includeme(config):
|
||||
config.include("wuttafarm.web.views.farmos.animals")
|
||||
171
src/wuttafarm/web/views/farmos/animals.py
Normal file
171
src/wuttafarm/web/views/farmos/animals.py
Normal 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)
|
||||
65
src/wuttafarm/web/views/farmos/master.py
Normal file
65
src/wuttafarm/web/views/farmos/master.py
Normal 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
24
tasks.py
Normal 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/*")
|
||||
Loading…
Add table
Add a link
Reference in a new issue