Compare commits
19 commits
9cc7237bfb
...
34cb6b210d
| Author | SHA1 | Date | |
|---|---|---|---|
| 34cb6b210d | |||
| 061dac39f9 | |||
| be64b4959a | |||
| 311a2c328b | |||
| 935c64464a | |||
| 1dbf14f3bb | |||
| ed768a83d0 | |||
| f4e4c3efb3 | |||
| 81daa5d913 | |||
| 3e5ca3483e | |||
| c38d00a7cc | |||
| 1d898cb580 | |||
| 6204db8ae3 | |||
| 5189c12f43 | |||
| b573ae459e | |||
| 10666de488 | |||
| fd2f09fcf3 | |||
| 4a517bf7bf | |||
| 09042747a0 |
69 changed files with 5095 additions and 46 deletions
24
CHANGELOG.md
24
CHANGELOG.md
|
|
@ -5,6 +5,30 @@ All notable changes to WuttaFarm will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
||||||
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## v0.3.0 (2026-02-13)
|
||||||
|
|
||||||
|
### Feat
|
||||||
|
|
||||||
|
- add native table for Activity Logs; import from farmOS API
|
||||||
|
- add native table for Groups; import from farmOS API
|
||||||
|
- add native table for Animals; import from farmOS API
|
||||||
|
- add native table for Structures; import from farmOS API
|
||||||
|
- add native table for Land Assets; import from farmOS API
|
||||||
|
- add native table for Log Types; import from farmOS API
|
||||||
|
- add native table for Structure Types; import from farmOS API
|
||||||
|
- add native table for Land Types; import from farmOS API
|
||||||
|
- add native table for Asset Types; import from farmOS API
|
||||||
|
- add extension table for Users; import from farmOS API
|
||||||
|
- add native table for Animal Types; import from farmOS API
|
||||||
|
- add "See raw JSON data" button for farmOS API views
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
|
||||||
|
- always make 'farmos' system user in app setup
|
||||||
|
- avoid error for Create User form
|
||||||
|
- add more perms to Site Admin role in app setup
|
||||||
|
- rename `drupal_internal_id` => `drupal_id`
|
||||||
|
|
||||||
## v0.2.3 (2026-02-08)
|
## v0.2.3 (2026-02-08)
|
||||||
|
|
||||||
### Fix
|
### Fix
|
||||||
|
|
|
||||||
6
docs/api/wuttafarm.cli.base.rst
Normal file
6
docs/api/wuttafarm.cli.base.rst
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
``wuttafarm.cli.base``
|
||||||
|
======================
|
||||||
|
|
||||||
|
.. automodule:: wuttafarm.cli.base
|
||||||
|
:members:
|
||||||
6
docs/api/wuttafarm.cli.import_farmos.rst
Normal file
6
docs/api/wuttafarm.cli.import_farmos.rst
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
``wuttafarm.cli.import_farmos``
|
||||||
|
===============================
|
||||||
|
|
||||||
|
.. automodule:: wuttafarm.cli.import_farmos
|
||||||
|
:members:
|
||||||
6
docs/api/wuttafarm.cli.install.rst
Normal file
6
docs/api/wuttafarm.cli.install.rst
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
``wuttafarm.cli.install``
|
||||||
|
=========================
|
||||||
|
|
||||||
|
.. automodule:: wuttafarm.cli.install
|
||||||
|
:members:
|
||||||
6
docs/api/wuttafarm.importing.farmos.rst
Normal file
6
docs/api/wuttafarm.importing.farmos.rst
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
``wuttafarm.importing.farmos``
|
||||||
|
==============================
|
||||||
|
|
||||||
|
.. automodule:: wuttafarm.importing.farmos
|
||||||
|
:members:
|
||||||
6
docs/api/wuttafarm.importing.rst
Normal file
6
docs/api/wuttafarm.importing.rst
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
``wuttafarm.importing``
|
||||||
|
=======================
|
||||||
|
|
||||||
|
.. automodule:: wuttafarm.importing
|
||||||
|
:members:
|
||||||
|
|
@ -21,6 +21,7 @@ extensions = [
|
||||||
"sphinx.ext.intersphinx",
|
"sphinx.ext.intersphinx",
|
||||||
"sphinx.ext.viewcode",
|
"sphinx.ext.viewcode",
|
||||||
"sphinx.ext.todo",
|
"sphinx.ext.todo",
|
||||||
|
"sphinxcontrib.programoutput",
|
||||||
]
|
]
|
||||||
|
|
||||||
templates_path = ["_templates"]
|
templates_path = ["_templates"]
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,6 @@ and extend `farmOS`_.
|
||||||
.. _WuttaWeb: https://wuttaproject.org
|
.. _WuttaWeb: https://wuttaproject.org
|
||||||
.. _farmOS: https://farmos.org
|
.. _farmOS: https://farmos.org
|
||||||
|
|
||||||
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
|
||||||
:target: https://github.com/psf/black
|
|
||||||
|
|
||||||
It is just an experiment so far; the ideas I hope to play with
|
It is just an experiment so far; the ideas I hope to play with
|
||||||
include:
|
include:
|
||||||
|
|
||||||
|
|
@ -19,6 +16,9 @@ include:
|
||||||
- possibly add more schema / extra features
|
- possibly add more schema / extra features
|
||||||
- possibly sync data back to farmOS
|
- possibly sync data back to farmOS
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
||||||
|
:target: https://github.com/psf/black
|
||||||
|
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
@ -27,6 +27,7 @@ include:
|
||||||
narr/install
|
narr/install
|
||||||
narr/auth
|
narr/auth
|
||||||
narr/features
|
narr/features
|
||||||
|
narr/cli
|
||||||
|
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
|
|
@ -37,11 +38,16 @@ include:
|
||||||
api/wuttafarm.app
|
api/wuttafarm.app
|
||||||
api/wuttafarm.auth
|
api/wuttafarm.auth
|
||||||
api/wuttafarm.cli
|
api/wuttafarm.cli
|
||||||
|
api/wuttafarm.cli.base
|
||||||
|
api/wuttafarm.cli.import_farmos
|
||||||
|
api/wuttafarm.cli.install
|
||||||
api/wuttafarm.config
|
api/wuttafarm.config
|
||||||
api/wuttafarm.db
|
api/wuttafarm.db
|
||||||
api/wuttafarm.db.model
|
api/wuttafarm.db.model
|
||||||
api/wuttafarm.farmos
|
api/wuttafarm.farmos
|
||||||
api/wuttafarm.farmos.handler
|
api/wuttafarm.farmos.handler
|
||||||
|
api/wuttafarm.importing
|
||||||
|
api/wuttafarm.importing.farmos
|
||||||
api/wuttafarm.install
|
api/wuttafarm.install
|
||||||
api/wuttafarm.web
|
api/wuttafarm.web
|
||||||
api/wuttafarm.web.app
|
api/wuttafarm.web.app
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,13 @@ browse farmOS data within the WuttaFarm views.
|
||||||
|
|
||||||
If you login to WuttaFarm directly with username/password, then
|
If you login to WuttaFarm directly with username/password, then
|
||||||
your user session will not have a farmOS access token and so the
|
your user session will not have a farmOS access token and so the
|
||||||
farmOS data views in WuttaFarm will not work.
|
farmOS data views in WuttaFarm will not work (i.e. anything under
|
||||||
|
the **farmOS** menu).
|
||||||
|
|
||||||
|
(However this does not affect the "native" data views for
|
||||||
|
WuttaFarm. Users can see data which was already imported from
|
||||||
|
farmOS without an access token - if they have appropriate
|
||||||
|
permissions in WuttaFarm.)
|
||||||
|
|
||||||
On the login page, click the "Login via farmOS / OAuth2" button. This
|
On the login page, click the "Login via farmOS / OAuth2" button. This
|
||||||
will initiate the OAuth2 workflow, at which point you may be asked to
|
will initiate the OAuth2 workflow, at which point you may be asked to
|
||||||
|
|
|
||||||
39
docs/narr/cli.rst
Normal file
39
docs/narr/cli.rst
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
|
||||||
|
========================
|
||||||
|
Command Line Interface
|
||||||
|
========================
|
||||||
|
|
||||||
|
WuttaFarm ships with the following commands.
|
||||||
|
|
||||||
|
For more general info about CLI see
|
||||||
|
:doc:`wuttjamaican:narr/cli/index`.
|
||||||
|
|
||||||
|
|
||||||
|
.. _wuttafarm-install:
|
||||||
|
|
||||||
|
``wuttafarm install``
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Run the WuttaFarm app installer.
|
||||||
|
|
||||||
|
This will create the :term:`app dir` and initial config files, and
|
||||||
|
create the schema within the :term:`app database`.
|
||||||
|
|
||||||
|
Defined in: :mod:`wuttafarm.cli.install`
|
||||||
|
|
||||||
|
.. program-output:: wuttafarm install --help
|
||||||
|
|
||||||
|
|
||||||
|
.. _wuttafarm-import-farmos:
|
||||||
|
|
||||||
|
``wuttafarm import-farmos``
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
Import data from the farmOS API into the WuttaFarm :term:`app
|
||||||
|
database`.
|
||||||
|
|
||||||
|
Defined in: :mod:`wuttafarm.cli.import_farmos`
|
||||||
|
|
||||||
|
.. program-output:: wuttafarm import-farmos --help
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -14,6 +14,10 @@ Here is the list of features currently supported:
|
||||||
* performance isn't bad, but data is not very "complete"
|
* performance isn't bad, but data is not very "complete"
|
||||||
* more data could be fetched, but not sure this is the best way..?
|
* more data could be fetched, but not sure this is the best way..?
|
||||||
|
|
||||||
|
* import some data from farmOS
|
||||||
|
* limited data is imported from farmOS API into native app tables
|
||||||
|
* this data is exposed in views, similar to direct farmOS views (above)
|
||||||
|
|
||||||
|
|
||||||
Screenshots
|
Screenshots
|
||||||
-----------
|
-----------
|
||||||
|
|
|
||||||
|
|
@ -60,3 +60,93 @@ are encouraged to enable it anyway.
|
||||||
When the installer completes it will output a command you can then use
|
When the installer completes it will output a command you can then use
|
||||||
to run the web app. Do that and you can then view the app in a
|
to run the web app. Do that and you can then view the app in a
|
||||||
browser at http://localhost:9080
|
browser at http://localhost:9080
|
||||||
|
|
||||||
|
|
||||||
|
OAuth2 Setup
|
||||||
|
------------
|
||||||
|
|
||||||
|
At this point the web app should be ready for OAuth2 login; however
|
||||||
|
the OAuth2 provider in farmOS needs some more config before it will
|
||||||
|
work.
|
||||||
|
|
||||||
|
WuttaFarm uses the default ``farm`` consumer, so the only thing you
|
||||||
|
should have to do here is edit that to add your redirect URL. This
|
||||||
|
will vary based on your WuttaFarm site name, e.g.
|
||||||
|
|
||||||
|
.. code-block:: none
|
||||||
|
|
||||||
|
https://wuttafarm.example.com/farmos/oauth/callback
|
||||||
|
|
||||||
|
With that in place you should be able to login via OAuth2; see also
|
||||||
|
:doc:`/narr/auth`.
|
||||||
|
|
||||||
|
However while you're there, you should also do some setup for the sake
|
||||||
|
of the farmOS → WuttaFarm data import. This import will also use the
|
||||||
|
farmOS API and therefore also needs an oauth2 access token; however it
|
||||||
|
uses the Client Credentials workflow instead of the Authorization Code
|
||||||
|
workflow. Therefore you must create a new *user* and a new OAuth2
|
||||||
|
*consumer* for it.
|
||||||
|
|
||||||
|
First add a new user in farmOS, named ``wuttafarm``. It should
|
||||||
|
probably be given the Manager role, since WuttaFarm will eventually
|
||||||
|
also support "exporting" data back to farmOS.
|
||||||
|
|
||||||
|
Then add a new OAuth2 consumer (aka. client) with these attributes:
|
||||||
|
|
||||||
|
* **Label:** WuttaFarm
|
||||||
|
* **Client ID:** wuttafarm
|
||||||
|
* **New Secret:** (put something in here, to be used as client secret)
|
||||||
|
* **Grant Types:** Client Credentials, Refresh Token (maybe more?)
|
||||||
|
* **User:** wuttafarm
|
||||||
|
* **3rd Party?** yes
|
||||||
|
* **Confidential?** yes
|
||||||
|
* **Access Token Expiration Time:** maybe set to 3600? or maybe 300
|
||||||
|
default is okay?
|
||||||
|
* **Allowed Origins:** put your oauth callback URL here (same as for
|
||||||
|
default ``farm`` consumer)
|
||||||
|
|
||||||
|
WuttaFarm also needs to know the client secret for sake of running the
|
||||||
|
import; so add this to your ``app/wutta.conf`` file. Of course
|
||||||
|
replace the value with whatever client secret you gave the new
|
||||||
|
consumer:
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[farmos.oauth2]
|
||||||
|
importing.client_secret = you_cant_guess_me
|
||||||
|
|
||||||
|
|
||||||
|
Import Data from farmOS
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
You must have done all the OAuth2 setup (previous section) before the
|
||||||
|
import will work.
|
||||||
|
|
||||||
|
But now that you did all that, importing should be quick and easy.
|
||||||
|
|
||||||
|
The very first import will be limited and "special" to account for any
|
||||||
|
users which were already created in WuttaFarm. This command will
|
||||||
|
ensure WuttaFarm gets *all* user accounts and each is appropriately
|
||||||
|
mapped to the farmOS account:
|
||||||
|
|
||||||
|
.. code-block:: sh
|
||||||
|
|
||||||
|
./venv/bin/wuttafarm --runas farmos import-farmos User --key username
|
||||||
|
|
||||||
|
Note also the ``--runas farmos`` arg which helps the WuttaFarm data
|
||||||
|
versioning know "who" is responsible for the changes. We use a
|
||||||
|
dedicated ``farmos`` user account in WuttaFarm, to represent the
|
||||||
|
farmOS system as a whole.
|
||||||
|
|
||||||
|
From now on you can run the "full" import normally:
|
||||||
|
|
||||||
|
.. code-block:: sh
|
||||||
|
|
||||||
|
./venv/bin/wuttafarm --runas farmos import-farmos
|
||||||
|
|
||||||
|
And it can sometimes be helpful to "double-check" in order to make
|
||||||
|
sure all data is fully synced:
|
||||||
|
|
||||||
|
.. code-block:: sh
|
||||||
|
|
||||||
|
./venv/bin/wuttafarm --runas farmos import-farmos --delete --dry-run -W
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ build-backend = "hatchling.build"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "WuttaFarm"
|
name = "WuttaFarm"
|
||||||
version = "0.2.3"
|
version = "0.3.0"
|
||||||
description = "Web app to integrate with and extend farmOS"
|
description = "Web app to integrate with and extend farmOS"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
authors = [
|
authors = [
|
||||||
|
|
@ -33,12 +33,13 @@ dependencies = [
|
||||||
"psycopg2",
|
"psycopg2",
|
||||||
"pyramid_exclog",
|
"pyramid_exclog",
|
||||||
"uvicorn[standard]",
|
"uvicorn[standard]",
|
||||||
|
"WuttaSync",
|
||||||
"WuttaWeb[continuum]>=0.27.4",
|
"WuttaWeb[continuum]>=0.27.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
docs = ["Sphinx", "furo"]
|
docs = ["Sphinx", "furo", "sphinxcontrib-programoutput"]
|
||||||
|
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
|
|
@ -47,12 +48,18 @@ docs = ["Sphinx", "furo"]
|
||||||
[project.entry-points."paste.app_factory"]
|
[project.entry-points."paste.app_factory"]
|
||||||
"main" = "wuttafarm.web.app:main"
|
"main" = "wuttafarm.web.app:main"
|
||||||
|
|
||||||
|
[project.entry-points."wutta.app.providers"]
|
||||||
|
wuttafarm = "wuttafarm.app:WuttaFarmAppProvider"
|
||||||
|
|
||||||
[project.entry-points."wutta.config.extensions"]
|
[project.entry-points."wutta.config.extensions"]
|
||||||
"wuttafarm" = "wuttafarm.config:WuttaFarmConfig"
|
"wuttafarm" = "wuttafarm.config:WuttaFarmConfig"
|
||||||
|
|
||||||
[project.entry-points."wutta.web.menus"]
|
[project.entry-points."wutta.web.menus"]
|
||||||
"wuttafarm" = "wuttafarm.web.menus:WuttaFarmMenuHandler"
|
"wuttafarm" = "wuttafarm.web.menus:WuttaFarmMenuHandler"
|
||||||
|
|
||||||
|
[project.entry-points."wuttasync.importing"]
|
||||||
|
"import.to_wuttafarm.from_farmos" = "wuttafarm.importing.farmos:FromFarmOSToWuttaFarm"
|
||||||
|
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
Homepage = "https://forgejo.wuttaproject.org/wutta/wuttafarm"
|
Homepage = "https://forgejo.wuttaproject.org/wutta/wuttafarm"
|
||||||
|
|
|
||||||
|
|
@ -64,3 +64,11 @@ class WuttaFarmAppHandler(base.AppHandler):
|
||||||
"""
|
"""
|
||||||
handler = self.get_farmos_handler()
|
handler = self.get_farmos_handler()
|
||||||
return handler.get_farmos_client(*args, **kwargs)
|
return handler.get_farmos_client(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class WuttaFarmAppProvider(base.AppProvider):
|
||||||
|
"""
|
||||||
|
The :term:`app provider` for WuttaFarm.
|
||||||
|
"""
|
||||||
|
|
||||||
|
email_modules = ["wuttafarm.emails"]
|
||||||
|
|
|
||||||
30
src/wuttafarm/cli/__init__.py
Normal file
30
src/wuttafarm/cli/__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/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
WuttaFarm CLI
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .base import wuttafarm_typer
|
||||||
|
|
||||||
|
# nb. must bring in all modules for discovery to work
|
||||||
|
from . import import_farmos
|
||||||
|
from . import install
|
||||||
31
src/wuttafarm/cli/base.py
Normal file
31
src/wuttafarm/cli/base.py
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
# -*- 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 - base Typer instance
|
||||||
|
"""
|
||||||
|
|
||||||
|
from wuttjamaican.cli import make_typer
|
||||||
|
|
||||||
|
|
||||||
|
wuttafarm_typer = make_typer(
|
||||||
|
name="wuttafarm", help="WuttaFarm -- Web app to integrate with and extend farmOS"
|
||||||
|
)
|
||||||
41
src/wuttafarm/cli/import_farmos.py
Normal file
41
src/wuttafarm/cli/import_farmos.py
Normal file
|
|
@ -0,0 +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/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
See also: :ref:`wuttafarm-import-farmos`
|
||||||
|
"""
|
||||||
|
|
||||||
|
import typer
|
||||||
|
|
||||||
|
from wuttasync.cli import import_command, ImportCommandHandler
|
||||||
|
|
||||||
|
from wuttafarm.cli import wuttafarm_typer
|
||||||
|
|
||||||
|
|
||||||
|
@wuttafarm_typer.command()
|
||||||
|
@import_command
|
||||||
|
def import_farmos(ctx: typer.Context, **kwargs):
|
||||||
|
"""
|
||||||
|
Import data from farmOS API to WuttaFarm
|
||||||
|
"""
|
||||||
|
config = ctx.parent.wutta_config
|
||||||
|
handler = ImportCommandHandler(config, key="import.to_wuttafarm.from_farmos")
|
||||||
|
handler.run(ctx)
|
||||||
|
|
@ -25,12 +25,7 @@ WuttaFarm CLI
|
||||||
|
|
||||||
import typer
|
import typer
|
||||||
|
|
||||||
from wuttjamaican.cli import make_typer
|
from wuttafarm.cli import wuttafarm_typer
|
||||||
|
|
||||||
|
|
||||||
wuttafarm_typer = make_typer(
|
|
||||||
name="wuttafarm", help="WuttaFarm -- Web app to integrate with and extend farmOS"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@wuttafarm_typer.command()
|
@wuttafarm_typer.command()
|
||||||
127
src/wuttafarm/db/alembic/versions/1b2d3224e5dc_add_animals.py
Normal file
127
src/wuttafarm/db/alembic/versions/1b2d3224e5dc_add_animals.py
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
"""add Animals
|
||||||
|
|
||||||
|
Revision ID: 1b2d3224e5dc
|
||||||
|
Revises: 4dbba8aeb1e5
|
||||||
|
Create Date: 2026-02-13 11:55:19.564221
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
import wuttjamaican.db.util
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = "1b2d3224e5dc"
|
||||||
|
down_revision: Union[str, None] = "4dbba8aeb1e5"
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
|
||||||
|
# animal
|
||||||
|
op.create_table(
|
||||||
|
"animal",
|
||||||
|
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
|
||||||
|
sa.Column("name", sa.String(length=100), nullable=False),
|
||||||
|
sa.Column("animal_type_uuid", wuttjamaican.db.util.UUID(), nullable=False),
|
||||||
|
sa.Column("birthdate", sa.DateTime(), nullable=True),
|
||||||
|
sa.Column("sex", sa.String(length=1), nullable=True),
|
||||||
|
sa.Column("is_sterile", sa.Boolean(), nullable=True),
|
||||||
|
sa.Column("active", sa.Boolean(), nullable=False),
|
||||||
|
sa.Column("notes", sa.Text(), nullable=True),
|
||||||
|
sa.Column("image_url", sa.String(length=255), nullable=True),
|
||||||
|
sa.Column("farmos_uuid", wuttjamaican.db.util.UUID(), nullable=True),
|
||||||
|
sa.Column("drupal_id", sa.Integer(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["animal_type_uuid"],
|
||||||
|
["animal_type.uuid"],
|
||||||
|
name=op.f("fk_animal_animal_type_uuid_animal_type"),
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_animal")),
|
||||||
|
sa.UniqueConstraint("drupal_id", name=op.f("uq_animal_drupal_id")),
|
||||||
|
sa.UniqueConstraint("farmos_uuid", name=op.f("uq_animal_farmos_uuid")),
|
||||||
|
)
|
||||||
|
op.create_table(
|
||||||
|
"animal_version",
|
||||||
|
sa.Column(
|
||||||
|
"uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
|
||||||
|
),
|
||||||
|
sa.Column("name", sa.String(length=100), autoincrement=False, nullable=True),
|
||||||
|
sa.Column(
|
||||||
|
"animal_type_uuid",
|
||||||
|
wuttjamaican.db.util.UUID(),
|
||||||
|
autoincrement=False,
|
||||||
|
nullable=True,
|
||||||
|
),
|
||||||
|
sa.Column("birthdate", sa.DateTime(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column("sex", sa.String(length=1), autoincrement=False, nullable=True),
|
||||||
|
sa.Column("is_sterile", sa.Boolean(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column("active", sa.Boolean(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column("notes", sa.Text(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column(
|
||||||
|
"image_url", sa.String(length=255), autoincrement=False, nullable=True
|
||||||
|
),
|
||||||
|
sa.Column(
|
||||||
|
"farmos_uuid",
|
||||||
|
wuttjamaican.db.util.UUID(),
|
||||||
|
autoincrement=False,
|
||||||
|
nullable=True,
|
||||||
|
),
|
||||||
|
sa.Column("drupal_id", sa.Integer(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column(
|
||||||
|
"transaction_id", sa.BigInteger(), autoincrement=False, nullable=False
|
||||||
|
),
|
||||||
|
sa.Column("end_transaction_id", sa.BigInteger(), nullable=True),
|
||||||
|
sa.Column("operation_type", sa.SmallInteger(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint(
|
||||||
|
"uuid", "transaction_id", name=op.f("pk_animal_version")
|
||||||
|
),
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_animal_version_end_transaction_id"),
|
||||||
|
"animal_version",
|
||||||
|
["end_transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_animal_version_operation_type"),
|
||||||
|
"animal_version",
|
||||||
|
["operation_type"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
"ix_animal_version_pk_transaction_id",
|
||||||
|
"animal_version",
|
||||||
|
["uuid", sa.literal_column("transaction_id DESC")],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
"ix_animal_version_pk_validity",
|
||||||
|
"animal_version",
|
||||||
|
["uuid", "transaction_id", "end_transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_animal_version_transaction_id"),
|
||||||
|
"animal_version",
|
||||||
|
["transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
|
||||||
|
# animal
|
||||||
|
op.drop_index(op.f("ix_animal_version_transaction_id"), table_name="animal_version")
|
||||||
|
op.drop_index("ix_animal_version_pk_validity", table_name="animal_version")
|
||||||
|
op.drop_index("ix_animal_version_pk_transaction_id", table_name="animal_version")
|
||||||
|
op.drop_index(op.f("ix_animal_version_operation_type"), table_name="animal_version")
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_animal_version_end_transaction_id"), table_name="animal_version"
|
||||||
|
)
|
||||||
|
op.drop_table("animal_version")
|
||||||
|
op.drop_table("animal")
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
"""add Animal Types
|
||||||
|
|
||||||
|
Revision ID: 2b6385d0fa17
|
||||||
|
Revises:
|
||||||
|
Create Date: 2026-02-08 14:55:42.236918
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
import wuttjamaican.db.util
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = "2b6385d0fa17"
|
||||||
|
down_revision: Union[str, None] = None
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = ("wuttafarm",)
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
|
||||||
|
# animal_type
|
||||||
|
op.create_table(
|
||||||
|
"animal_type",
|
||||||
|
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
|
||||||
|
sa.Column("name", sa.String(length=100), nullable=False),
|
||||||
|
sa.Column("description", sa.String(length=255), nullable=True),
|
||||||
|
sa.Column("changed", sa.DateTime(), nullable=True),
|
||||||
|
sa.Column("farmos_uuid", wuttjamaican.db.util.UUID(), nullable=True),
|
||||||
|
sa.Column("drupal_id", sa.Integer(), nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_animal_type")),
|
||||||
|
sa.UniqueConstraint("drupal_id", name=op.f("uq_animal_type_drupal_id")),
|
||||||
|
sa.UniqueConstraint("farmos_uuid", name=op.f("uq_animal_type_farmos_uuid")),
|
||||||
|
sa.UniqueConstraint("name", name=op.f("uq_animal_type_name")),
|
||||||
|
)
|
||||||
|
op.create_table(
|
||||||
|
"animal_type_version",
|
||||||
|
sa.Column(
|
||||||
|
"uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
|
||||||
|
),
|
||||||
|
sa.Column("name", sa.String(length=100), autoincrement=False, nullable=True),
|
||||||
|
sa.Column(
|
||||||
|
"description", sa.String(length=255), autoincrement=False, nullable=True
|
||||||
|
),
|
||||||
|
sa.Column(
|
||||||
|
"farmos_uuid",
|
||||||
|
wuttjamaican.db.util.UUID(),
|
||||||
|
autoincrement=False,
|
||||||
|
nullable=True,
|
||||||
|
),
|
||||||
|
sa.Column("drupal_id", sa.Integer(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column(
|
||||||
|
"transaction_id", sa.BigInteger(), autoincrement=False, nullable=False
|
||||||
|
),
|
||||||
|
sa.Column("end_transaction_id", sa.BigInteger(), nullable=True),
|
||||||
|
sa.Column("operation_type", sa.SmallInteger(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint(
|
||||||
|
"uuid", "transaction_id", name=op.f("pk_animal_type_version")
|
||||||
|
),
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_animal_type_version_end_transaction_id"),
|
||||||
|
"animal_type_version",
|
||||||
|
["end_transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_animal_type_version_operation_type"),
|
||||||
|
"animal_type_version",
|
||||||
|
["operation_type"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
"ix_animal_type_version_pk_transaction_id",
|
||||||
|
"animal_type_version",
|
||||||
|
["uuid", sa.literal_column("transaction_id DESC")],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
"ix_animal_type_version_pk_validity",
|
||||||
|
"animal_type_version",
|
||||||
|
["uuid", "transaction_id", "end_transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_animal_type_version_transaction_id"),
|
||||||
|
"animal_type_version",
|
||||||
|
["transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
|
||||||
|
# animal_type
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_animal_type_version_transaction_id"), table_name="animal_type_version"
|
||||||
|
)
|
||||||
|
op.drop_index(
|
||||||
|
"ix_animal_type_version_pk_validity", table_name="animal_type_version"
|
||||||
|
)
|
||||||
|
op.drop_index(
|
||||||
|
"ix_animal_type_version_pk_transaction_id", table_name="animal_type_version"
|
||||||
|
)
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_animal_type_version_operation_type"), table_name="animal_type_version"
|
||||||
|
)
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_animal_type_version_end_transaction_id"),
|
||||||
|
table_name="animal_type_version",
|
||||||
|
)
|
||||||
|
op.drop_table("animal_type_version")
|
||||||
|
op.drop_table("animal_type")
|
||||||
|
|
@ -0,0 +1,118 @@
|
||||||
|
"""add Activity Logs
|
||||||
|
|
||||||
|
Revision ID: 3e2ef02bf264
|
||||||
|
Revises: 92b813360b99
|
||||||
|
Create Date: 2026-02-13 14:36:47.191922
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
import wuttjamaican.db.util
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = "3e2ef02bf264"
|
||||||
|
down_revision: Union[str, None] = "92b813360b99"
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
|
||||||
|
# log_activity
|
||||||
|
op.create_table(
|
||||||
|
"log_activity",
|
||||||
|
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
|
||||||
|
sa.Column("message", sa.String(length=255), nullable=False),
|
||||||
|
sa.Column("timestamp", sa.DateTime(), nullable=False),
|
||||||
|
sa.Column("status", sa.String(length=20), nullable=False),
|
||||||
|
sa.Column("notes", sa.Text(), nullable=True),
|
||||||
|
sa.Column("farmos_uuid", wuttjamaican.db.util.UUID(), nullable=True),
|
||||||
|
sa.Column("drupal_id", sa.Integer(), nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_log_activity")),
|
||||||
|
sa.UniqueConstraint("drupal_id", name=op.f("uq_log_activity_drupal_id")),
|
||||||
|
sa.UniqueConstraint("farmos_uuid", name=op.f("uq_log_activity_farmos_uuid")),
|
||||||
|
)
|
||||||
|
op.create_table(
|
||||||
|
"log_activity_version",
|
||||||
|
sa.Column(
|
||||||
|
"uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
|
||||||
|
),
|
||||||
|
sa.Column("message", sa.String(length=255), autoincrement=False, nullable=True),
|
||||||
|
sa.Column("timestamp", sa.DateTime(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column("status", sa.String(length=20), autoincrement=False, nullable=True),
|
||||||
|
sa.Column("notes", sa.Text(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column(
|
||||||
|
"farmos_uuid",
|
||||||
|
wuttjamaican.db.util.UUID(),
|
||||||
|
autoincrement=False,
|
||||||
|
nullable=True,
|
||||||
|
),
|
||||||
|
sa.Column("drupal_id", sa.Integer(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column(
|
||||||
|
"transaction_id", sa.BigInteger(), autoincrement=False, nullable=False
|
||||||
|
),
|
||||||
|
sa.Column("end_transaction_id", sa.BigInteger(), nullable=True),
|
||||||
|
sa.Column("operation_type", sa.SmallInteger(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint(
|
||||||
|
"uuid", "transaction_id", name=op.f("pk_log_activity_version")
|
||||||
|
),
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_log_activity_version_end_transaction_id"),
|
||||||
|
"log_activity_version",
|
||||||
|
["end_transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_log_activity_version_operation_type"),
|
||||||
|
"log_activity_version",
|
||||||
|
["operation_type"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
"ix_log_activity_version_pk_transaction_id",
|
||||||
|
"log_activity_version",
|
||||||
|
["uuid", sa.literal_column("transaction_id DESC")],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
"ix_log_activity_version_pk_validity",
|
||||||
|
"log_activity_version",
|
||||||
|
["uuid", "transaction_id", "end_transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_log_activity_version_transaction_id"),
|
||||||
|
"log_activity_version",
|
||||||
|
["transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
|
||||||
|
# log_activity
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_log_activity_version_transaction_id"),
|
||||||
|
table_name="log_activity_version",
|
||||||
|
)
|
||||||
|
op.drop_index(
|
||||||
|
"ix_log_activity_version_pk_validity", table_name="log_activity_version"
|
||||||
|
)
|
||||||
|
op.drop_index(
|
||||||
|
"ix_log_activity_version_pk_transaction_id", table_name="log_activity_version"
|
||||||
|
)
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_log_activity_version_operation_type"),
|
||||||
|
table_name="log_activity_version",
|
||||||
|
)
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_log_activity_version_end_transaction_id"),
|
||||||
|
table_name="log_activity_version",
|
||||||
|
)
|
||||||
|
op.drop_table("log_activity_version")
|
||||||
|
op.drop_table("log_activity")
|
||||||
132
src/wuttafarm/db/alembic/versions/4dbba8aeb1e5_add_structures.py
Normal file
132
src/wuttafarm/db/alembic/versions/4dbba8aeb1e5_add_structures.py
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
"""add Structures
|
||||||
|
|
||||||
|
Revision ID: 4dbba8aeb1e5
|
||||||
|
Revises: e416b96467fc
|
||||||
|
Create Date: 2026-02-13 10:17:15.179202
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
import wuttjamaican.db.util
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = "4dbba8aeb1e5"
|
||||||
|
down_revision: Union[str, None] = "e416b96467fc"
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
|
||||||
|
# structure
|
||||||
|
op.create_table(
|
||||||
|
"structure",
|
||||||
|
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
|
||||||
|
sa.Column("name", sa.String(length=100), nullable=False),
|
||||||
|
sa.Column("active", sa.Boolean(), nullable=False),
|
||||||
|
sa.Column("structure_type_uuid", wuttjamaican.db.util.UUID(), nullable=False),
|
||||||
|
sa.Column("is_location", sa.Boolean(), nullable=False),
|
||||||
|
sa.Column("is_fixed", sa.Boolean(), nullable=False),
|
||||||
|
sa.Column("notes", sa.Text(), nullable=True),
|
||||||
|
sa.Column("image_url", sa.String(length=255), nullable=True),
|
||||||
|
sa.Column("farmos_uuid", wuttjamaican.db.util.UUID(), nullable=True),
|
||||||
|
sa.Column("drupal_id", sa.Integer(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["structure_type_uuid"],
|
||||||
|
["structure_type.uuid"],
|
||||||
|
name=op.f("fk_structure_structure_type_uuid_structure_type"),
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_structure")),
|
||||||
|
sa.UniqueConstraint("drupal_id", name=op.f("uq_structure_drupal_id")),
|
||||||
|
sa.UniqueConstraint("farmos_uuid", name=op.f("uq_structure_farmos_uuid")),
|
||||||
|
sa.UniqueConstraint("name", name=op.f("uq_structure_name")),
|
||||||
|
)
|
||||||
|
op.create_table(
|
||||||
|
"structure_version",
|
||||||
|
sa.Column(
|
||||||
|
"uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
|
||||||
|
),
|
||||||
|
sa.Column("name", sa.String(length=100), autoincrement=False, nullable=True),
|
||||||
|
sa.Column("active", sa.Boolean(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column(
|
||||||
|
"structure_type_uuid",
|
||||||
|
wuttjamaican.db.util.UUID(),
|
||||||
|
autoincrement=False,
|
||||||
|
nullable=True,
|
||||||
|
),
|
||||||
|
sa.Column("is_location", sa.Boolean(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column("is_fixed", sa.Boolean(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column("notes", sa.Text(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column(
|
||||||
|
"image_url", sa.String(length=255), autoincrement=False, nullable=True
|
||||||
|
),
|
||||||
|
sa.Column(
|
||||||
|
"farmos_uuid",
|
||||||
|
wuttjamaican.db.util.UUID(),
|
||||||
|
autoincrement=False,
|
||||||
|
nullable=True,
|
||||||
|
),
|
||||||
|
sa.Column("drupal_id", sa.Integer(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column(
|
||||||
|
"transaction_id", sa.BigInteger(), autoincrement=False, nullable=False
|
||||||
|
),
|
||||||
|
sa.Column("end_transaction_id", sa.BigInteger(), nullable=True),
|
||||||
|
sa.Column("operation_type", sa.SmallInteger(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint(
|
||||||
|
"uuid", "transaction_id", name=op.f("pk_structure_version")
|
||||||
|
),
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_structure_version_end_transaction_id"),
|
||||||
|
"structure_version",
|
||||||
|
["end_transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_structure_version_operation_type"),
|
||||||
|
"structure_version",
|
||||||
|
["operation_type"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
"ix_structure_version_pk_transaction_id",
|
||||||
|
"structure_version",
|
||||||
|
["uuid", sa.literal_column("transaction_id DESC")],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
"ix_structure_version_pk_validity",
|
||||||
|
"structure_version",
|
||||||
|
["uuid", "transaction_id", "end_transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_structure_version_transaction_id"),
|
||||||
|
"structure_version",
|
||||||
|
["transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
|
||||||
|
# structure
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_structure_version_transaction_id"), table_name="structure_version"
|
||||||
|
)
|
||||||
|
op.drop_index("ix_structure_version_pk_validity", table_name="structure_version")
|
||||||
|
op.drop_index(
|
||||||
|
"ix_structure_version_pk_transaction_id", table_name="structure_version"
|
||||||
|
)
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_structure_version_operation_type"), table_name="structure_version"
|
||||||
|
)
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_structure_version_end_transaction_id"), table_name="structure_version"
|
||||||
|
)
|
||||||
|
op.drop_table("structure_version")
|
||||||
|
op.drop_table("structure")
|
||||||
|
|
@ -0,0 +1,112 @@
|
||||||
|
"""add WuttaFarmUser
|
||||||
|
|
||||||
|
Revision ID: 6c56bcd1c028
|
||||||
|
Revises: 2b6385d0fa17
|
||||||
|
Create Date: 2026-02-09 20:46:20.995903
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
import wuttjamaican.db.util
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = "6c56bcd1c028"
|
||||||
|
down_revision: Union[str, None] = "2b6385d0fa17"
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
|
||||||
|
# wuttafarm_user
|
||||||
|
op.create_table(
|
||||||
|
"wuttafarm_user",
|
||||||
|
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
|
||||||
|
sa.Column("farmos_uuid", wuttjamaican.db.util.UUID(), nullable=True),
|
||||||
|
sa.Column("drupal_id", sa.Integer(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["uuid"], ["user.uuid"], name=op.f("fk_wuttafarm_user_uuid_user")
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_wuttafarm_user")),
|
||||||
|
)
|
||||||
|
op.create_table(
|
||||||
|
"wuttafarm_user_version",
|
||||||
|
sa.Column(
|
||||||
|
"uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
|
||||||
|
),
|
||||||
|
sa.Column(
|
||||||
|
"farmos_uuid",
|
||||||
|
wuttjamaican.db.util.UUID(),
|
||||||
|
autoincrement=False,
|
||||||
|
nullable=True,
|
||||||
|
),
|
||||||
|
sa.Column("drupal_id", sa.Integer(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column(
|
||||||
|
"transaction_id", sa.BigInteger(), autoincrement=False, nullable=False
|
||||||
|
),
|
||||||
|
sa.Column("end_transaction_id", sa.BigInteger(), nullable=True),
|
||||||
|
sa.Column("operation_type", sa.SmallInteger(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint(
|
||||||
|
"uuid", "transaction_id", name=op.f("pk_wuttafarm_user_version")
|
||||||
|
),
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_wuttafarm_user_version_end_transaction_id"),
|
||||||
|
"wuttafarm_user_version",
|
||||||
|
["end_transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_wuttafarm_user_version_operation_type"),
|
||||||
|
"wuttafarm_user_version",
|
||||||
|
["operation_type"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
"ix_wuttafarm_user_version_pk_transaction_id",
|
||||||
|
"wuttafarm_user_version",
|
||||||
|
["uuid", sa.literal_column("transaction_id DESC")],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
"ix_wuttafarm_user_version_pk_validity",
|
||||||
|
"wuttafarm_user_version",
|
||||||
|
["uuid", "transaction_id", "end_transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_wuttafarm_user_version_transaction_id"),
|
||||||
|
"wuttafarm_user_version",
|
||||||
|
["transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
|
||||||
|
# wuttafarm_user
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_wuttafarm_user_version_transaction_id"),
|
||||||
|
table_name="wuttafarm_user_version",
|
||||||
|
)
|
||||||
|
op.drop_index(
|
||||||
|
"ix_wuttafarm_user_version_pk_validity", table_name="wuttafarm_user_version"
|
||||||
|
)
|
||||||
|
op.drop_index(
|
||||||
|
"ix_wuttafarm_user_version_pk_transaction_id",
|
||||||
|
table_name="wuttafarm_user_version",
|
||||||
|
)
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_wuttafarm_user_version_operation_type"),
|
||||||
|
table_name="wuttafarm_user_version",
|
||||||
|
)
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_wuttafarm_user_version_end_transaction_id"),
|
||||||
|
table_name="wuttafarm_user_version",
|
||||||
|
)
|
||||||
|
op.drop_table("wuttafarm_user_version")
|
||||||
|
op.drop_table("wuttafarm_user")
|
||||||
110
src/wuttafarm/db/alembic/versions/92b813360b99_add_groups.py
Normal file
110
src/wuttafarm/db/alembic/versions/92b813360b99_add_groups.py
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
"""add Groups
|
||||||
|
|
||||||
|
Revision ID: 92b813360b99
|
||||||
|
Revises: 1b2d3224e5dc
|
||||||
|
Create Date: 2026-02-13 13:09:48.718064
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
import wuttjamaican.db.util
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = "92b813360b99"
|
||||||
|
down_revision: Union[str, None] = "1b2d3224e5dc"
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
|
||||||
|
# group
|
||||||
|
op.create_table(
|
||||||
|
"group",
|
||||||
|
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
|
||||||
|
sa.Column("name", sa.String(length=100), nullable=False),
|
||||||
|
sa.Column("is_location", sa.Boolean(), nullable=False),
|
||||||
|
sa.Column("is_fixed", sa.Boolean(), nullable=False),
|
||||||
|
sa.Column("active", sa.Boolean(), nullable=False),
|
||||||
|
sa.Column("notes", sa.Text(), nullable=True),
|
||||||
|
sa.Column("farmos_uuid", wuttjamaican.db.util.UUID(), nullable=True),
|
||||||
|
sa.Column("drupal_id", sa.Integer(), nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_group")),
|
||||||
|
sa.UniqueConstraint("drupal_id", name=op.f("uq_group_drupal_id")),
|
||||||
|
sa.UniqueConstraint("farmos_uuid", name=op.f("uq_group_farmos_uuid")),
|
||||||
|
sa.UniqueConstraint("name", name=op.f("uq_group_name")),
|
||||||
|
)
|
||||||
|
op.create_table(
|
||||||
|
"group_version",
|
||||||
|
sa.Column(
|
||||||
|
"uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
|
||||||
|
),
|
||||||
|
sa.Column("name", sa.String(length=100), autoincrement=False, nullable=True),
|
||||||
|
sa.Column("is_location", sa.Boolean(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column("is_fixed", sa.Boolean(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column("active", sa.Boolean(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column("notes", sa.Text(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column(
|
||||||
|
"farmos_uuid",
|
||||||
|
wuttjamaican.db.util.UUID(),
|
||||||
|
autoincrement=False,
|
||||||
|
nullable=True,
|
||||||
|
),
|
||||||
|
sa.Column("drupal_id", sa.Integer(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column(
|
||||||
|
"transaction_id", sa.BigInteger(), autoincrement=False, nullable=False
|
||||||
|
),
|
||||||
|
sa.Column("end_transaction_id", sa.BigInteger(), nullable=True),
|
||||||
|
sa.Column("operation_type", sa.SmallInteger(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint(
|
||||||
|
"uuid", "transaction_id", name=op.f("pk_group_version")
|
||||||
|
),
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_group_version_end_transaction_id"),
|
||||||
|
"group_version",
|
||||||
|
["end_transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_group_version_operation_type"),
|
||||||
|
"group_version",
|
||||||
|
["operation_type"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
"ix_group_version_pk_transaction_id",
|
||||||
|
"group_version",
|
||||||
|
["uuid", sa.literal_column("transaction_id DESC")],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
"ix_group_version_pk_validity",
|
||||||
|
"group_version",
|
||||||
|
["uuid", "transaction_id", "end_transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_group_version_transaction_id"),
|
||||||
|
"group_version",
|
||||||
|
["transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
|
||||||
|
# group
|
||||||
|
op.drop_index(op.f("ix_group_version_transaction_id"), table_name="group_version")
|
||||||
|
op.drop_index("ix_group_version_pk_validity", table_name="group_version")
|
||||||
|
op.drop_index("ix_group_version_pk_transaction_id", table_name="group_version")
|
||||||
|
op.drop_index(op.f("ix_group_version_operation_type"), table_name="group_version")
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_group_version_end_transaction_id"), table_name="group_version"
|
||||||
|
)
|
||||||
|
op.drop_table("group_version")
|
||||||
|
op.drop_table("group")
|
||||||
110
src/wuttafarm/db/alembic/versions/9f2243df9566_add_land_types.py
Normal file
110
src/wuttafarm/db/alembic/versions/9f2243df9566_add_land_types.py
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
"""add Land Types
|
||||||
|
|
||||||
|
Revision ID: 9f2243df9566
|
||||||
|
Revises: cf3f8f46d8bc
|
||||||
|
Create Date: 2026-02-10 19:10:02.851756
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
import wuttjamaican.db.util
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = "9f2243df9566"
|
||||||
|
down_revision: Union[str, None] = "cf3f8f46d8bc"
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
|
||||||
|
# land_type
|
||||||
|
op.create_table(
|
||||||
|
"land_type",
|
||||||
|
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
|
||||||
|
sa.Column("name", sa.String(length=100), nullable=False),
|
||||||
|
sa.Column("farmos_uuid", wuttjamaican.db.util.UUID(), nullable=True),
|
||||||
|
sa.Column("drupal_id", sa.String(length=50), nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_land_type")),
|
||||||
|
sa.UniqueConstraint("drupal_id", name=op.f("uq_land_type_drupal_id")),
|
||||||
|
sa.UniqueConstraint("farmos_uuid", name=op.f("uq_land_type_farmos_uuid")),
|
||||||
|
sa.UniqueConstraint("name", name=op.f("uq_land_type_name")),
|
||||||
|
)
|
||||||
|
op.create_table(
|
||||||
|
"land_type_version",
|
||||||
|
sa.Column(
|
||||||
|
"uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
|
||||||
|
),
|
||||||
|
sa.Column("name", sa.String(length=100), autoincrement=False, nullable=True),
|
||||||
|
sa.Column(
|
||||||
|
"farmos_uuid",
|
||||||
|
wuttjamaican.db.util.UUID(),
|
||||||
|
autoincrement=False,
|
||||||
|
nullable=True,
|
||||||
|
),
|
||||||
|
sa.Column(
|
||||||
|
"drupal_id", sa.String(length=50), autoincrement=False, nullable=True
|
||||||
|
),
|
||||||
|
sa.Column(
|
||||||
|
"transaction_id", sa.BigInteger(), autoincrement=False, nullable=False
|
||||||
|
),
|
||||||
|
sa.Column("end_transaction_id", sa.BigInteger(), nullable=True),
|
||||||
|
sa.Column("operation_type", sa.SmallInteger(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint(
|
||||||
|
"uuid", "transaction_id", name=op.f("pk_land_type_version")
|
||||||
|
),
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_land_type_version_end_transaction_id"),
|
||||||
|
"land_type_version",
|
||||||
|
["end_transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_land_type_version_operation_type"),
|
||||||
|
"land_type_version",
|
||||||
|
["operation_type"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
"ix_land_type_version_pk_transaction_id",
|
||||||
|
"land_type_version",
|
||||||
|
["uuid", sa.literal_column("transaction_id DESC")],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
"ix_land_type_version_pk_validity",
|
||||||
|
"land_type_version",
|
||||||
|
["uuid", "transaction_id", "end_transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_land_type_version_transaction_id"),
|
||||||
|
"land_type_version",
|
||||||
|
["transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
|
||||||
|
# land_type
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_land_type_version_transaction_id"), table_name="land_type_version"
|
||||||
|
)
|
||||||
|
op.drop_index("ix_land_type_version_pk_validity", table_name="land_type_version")
|
||||||
|
op.drop_index(
|
||||||
|
"ix_land_type_version_pk_transaction_id", table_name="land_type_version"
|
||||||
|
)
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_land_type_version_operation_type"), table_name="land_type_version"
|
||||||
|
)
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_land_type_version_end_transaction_id"), table_name="land_type_version"
|
||||||
|
)
|
||||||
|
op.drop_table("land_type_version")
|
||||||
|
op.drop_table("land_type")
|
||||||
|
|
@ -0,0 +1,115 @@
|
||||||
|
"""add Asset Types
|
||||||
|
|
||||||
|
Revision ID: cf3f8f46d8bc
|
||||||
|
Revises: 6c56bcd1c028
|
||||||
|
Create Date: 2026-02-10 18:42:24.560312
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
import wuttjamaican.db.util
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = "cf3f8f46d8bc"
|
||||||
|
down_revision: Union[str, None] = "6c56bcd1c028"
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
|
||||||
|
# asset_type
|
||||||
|
op.create_table(
|
||||||
|
"asset_type",
|
||||||
|
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
|
||||||
|
sa.Column("name", sa.String(length=100), nullable=False),
|
||||||
|
sa.Column("description", sa.String(length=255), nullable=True),
|
||||||
|
sa.Column("farmos_uuid", wuttjamaican.db.util.UUID(), nullable=True),
|
||||||
|
sa.Column("drupal_id", sa.String(length=50), nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_asset_type")),
|
||||||
|
sa.UniqueConstraint("drupal_id", name=op.f("uq_asset_type_drupal_id")),
|
||||||
|
sa.UniqueConstraint("farmos_uuid", name=op.f("uq_asset_type_farmos_uuid")),
|
||||||
|
sa.UniqueConstraint("name", name=op.f("uq_asset_type_name")),
|
||||||
|
)
|
||||||
|
op.create_table(
|
||||||
|
"asset_type_version",
|
||||||
|
sa.Column(
|
||||||
|
"uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
|
||||||
|
),
|
||||||
|
sa.Column("name", sa.String(length=100), autoincrement=False, nullable=True),
|
||||||
|
sa.Column(
|
||||||
|
"description", sa.String(length=255), autoincrement=False, nullable=True
|
||||||
|
),
|
||||||
|
sa.Column(
|
||||||
|
"farmos_uuid",
|
||||||
|
wuttjamaican.db.util.UUID(),
|
||||||
|
autoincrement=False,
|
||||||
|
nullable=True,
|
||||||
|
),
|
||||||
|
sa.Column(
|
||||||
|
"drupal_id", sa.String(length=50), autoincrement=False, nullable=True
|
||||||
|
),
|
||||||
|
sa.Column(
|
||||||
|
"transaction_id", sa.BigInteger(), autoincrement=False, nullable=False
|
||||||
|
),
|
||||||
|
sa.Column("end_transaction_id", sa.BigInteger(), nullable=True),
|
||||||
|
sa.Column("operation_type", sa.SmallInteger(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint(
|
||||||
|
"uuid", "transaction_id", name=op.f("pk_asset_type_version")
|
||||||
|
),
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_asset_type_version_end_transaction_id"),
|
||||||
|
"asset_type_version",
|
||||||
|
["end_transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_asset_type_version_operation_type"),
|
||||||
|
"asset_type_version",
|
||||||
|
["operation_type"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
"ix_asset_type_version_pk_transaction_id",
|
||||||
|
"asset_type_version",
|
||||||
|
["uuid", sa.literal_column("transaction_id DESC")],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
"ix_asset_type_version_pk_validity",
|
||||||
|
"asset_type_version",
|
||||||
|
["uuid", "transaction_id", "end_transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_asset_type_version_transaction_id"),
|
||||||
|
"asset_type_version",
|
||||||
|
["transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
|
||||||
|
# asset_type
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_asset_type_version_transaction_id"), table_name="asset_type_version"
|
||||||
|
)
|
||||||
|
op.drop_index("ix_asset_type_version_pk_validity", table_name="asset_type_version")
|
||||||
|
op.drop_index(
|
||||||
|
"ix_asset_type_version_pk_transaction_id", table_name="asset_type_version"
|
||||||
|
)
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_asset_type_version_operation_type"), table_name="asset_type_version"
|
||||||
|
)
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_asset_type_version_end_transaction_id"),
|
||||||
|
table_name="asset_type_version",
|
||||||
|
)
|
||||||
|
op.drop_table("asset_type_version")
|
||||||
|
op.drop_table("asset_type")
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
"""add Structure Types
|
||||||
|
|
||||||
|
Revision ID: d7479d7161a8
|
||||||
|
Revises: 9f2243df9566
|
||||||
|
Create Date: 2026-02-10 19:24:20.249826
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
import wuttjamaican.db.util
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = "d7479d7161a8"
|
||||||
|
down_revision: Union[str, None] = "9f2243df9566"
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
|
||||||
|
# structure_type
|
||||||
|
op.create_table(
|
||||||
|
"structure_type",
|
||||||
|
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
|
||||||
|
sa.Column("name", sa.String(length=100), nullable=False),
|
||||||
|
sa.Column("farmos_uuid", wuttjamaican.db.util.UUID(), nullable=True),
|
||||||
|
sa.Column("drupal_id", sa.String(length=50), nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_structure_type")),
|
||||||
|
sa.UniqueConstraint("drupal_id", name=op.f("uq_structure_type_drupal_id")),
|
||||||
|
sa.UniqueConstraint("farmos_uuid", name=op.f("uq_structure_type_farmos_uuid")),
|
||||||
|
sa.UniqueConstraint("name", name=op.f("uq_structure_type_name")),
|
||||||
|
)
|
||||||
|
op.create_table(
|
||||||
|
"structure_type_version",
|
||||||
|
sa.Column(
|
||||||
|
"uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
|
||||||
|
),
|
||||||
|
sa.Column("name", sa.String(length=100), autoincrement=False, nullable=True),
|
||||||
|
sa.Column(
|
||||||
|
"farmos_uuid",
|
||||||
|
wuttjamaican.db.util.UUID(),
|
||||||
|
autoincrement=False,
|
||||||
|
nullable=True,
|
||||||
|
),
|
||||||
|
sa.Column(
|
||||||
|
"drupal_id", sa.String(length=50), autoincrement=False, nullable=True
|
||||||
|
),
|
||||||
|
sa.Column(
|
||||||
|
"transaction_id", sa.BigInteger(), autoincrement=False, nullable=False
|
||||||
|
),
|
||||||
|
sa.Column("end_transaction_id", sa.BigInteger(), nullable=True),
|
||||||
|
sa.Column("operation_type", sa.SmallInteger(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint(
|
||||||
|
"uuid", "transaction_id", name=op.f("pk_structure_type_version")
|
||||||
|
),
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_structure_type_version_end_transaction_id"),
|
||||||
|
"structure_type_version",
|
||||||
|
["end_transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_structure_type_version_operation_type"),
|
||||||
|
"structure_type_version",
|
||||||
|
["operation_type"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
"ix_structure_type_version_pk_transaction_id",
|
||||||
|
"structure_type_version",
|
||||||
|
["uuid", sa.literal_column("transaction_id DESC")],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
"ix_structure_type_version_pk_validity",
|
||||||
|
"structure_type_version",
|
||||||
|
["uuid", "transaction_id", "end_transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_structure_type_version_transaction_id"),
|
||||||
|
"structure_type_version",
|
||||||
|
["transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
|
||||||
|
# structure_type
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_structure_type_version_transaction_id"),
|
||||||
|
table_name="structure_type_version",
|
||||||
|
)
|
||||||
|
op.drop_index(
|
||||||
|
"ix_structure_type_version_pk_validity", table_name="structure_type_version"
|
||||||
|
)
|
||||||
|
op.drop_index(
|
||||||
|
"ix_structure_type_version_pk_transaction_id",
|
||||||
|
table_name="structure_type_version",
|
||||||
|
)
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_structure_type_version_operation_type"),
|
||||||
|
table_name="structure_type_version",
|
||||||
|
)
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_structure_type_version_end_transaction_id"),
|
||||||
|
table_name="structure_type_version",
|
||||||
|
)
|
||||||
|
op.drop_table("structure_type_version")
|
||||||
|
op.drop_table("structure_type")
|
||||||
114
src/wuttafarm/db/alembic/versions/e0d9f72575d6_add_log_types.py
Normal file
114
src/wuttafarm/db/alembic/versions/e0d9f72575d6_add_log_types.py
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
"""add Log Types
|
||||||
|
|
||||||
|
Revision ID: e0d9f72575d6
|
||||||
|
Revises: d7479d7161a8
|
||||||
|
Create Date: 2026-02-10 19:35:06.631814
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
import wuttjamaican.db.util
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = "e0d9f72575d6"
|
||||||
|
down_revision: Union[str, None] = "d7479d7161a8"
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
|
||||||
|
# log_type
|
||||||
|
op.create_table(
|
||||||
|
"log_type",
|
||||||
|
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
|
||||||
|
sa.Column("name", sa.String(length=100), nullable=False),
|
||||||
|
sa.Column("description", sa.String(length=255), nullable=True),
|
||||||
|
sa.Column("farmos_uuid", wuttjamaican.db.util.UUID(), nullable=True),
|
||||||
|
sa.Column("drupal_id", sa.String(length=50), nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_log_type")),
|
||||||
|
sa.UniqueConstraint("drupal_id", name=op.f("uq_log_type_drupal_id")),
|
||||||
|
sa.UniqueConstraint("farmos_uuid", name=op.f("uq_log_type_farmos_uuid")),
|
||||||
|
sa.UniqueConstraint("name", name=op.f("uq_log_type_name")),
|
||||||
|
)
|
||||||
|
op.create_table(
|
||||||
|
"log_type_version",
|
||||||
|
sa.Column(
|
||||||
|
"uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
|
||||||
|
),
|
||||||
|
sa.Column("name", sa.String(length=100), autoincrement=False, nullable=True),
|
||||||
|
sa.Column(
|
||||||
|
"description", sa.String(length=255), autoincrement=False, nullable=True
|
||||||
|
),
|
||||||
|
sa.Column(
|
||||||
|
"farmos_uuid",
|
||||||
|
wuttjamaican.db.util.UUID(),
|
||||||
|
autoincrement=False,
|
||||||
|
nullable=True,
|
||||||
|
),
|
||||||
|
sa.Column(
|
||||||
|
"drupal_id", sa.String(length=50), autoincrement=False, nullable=True
|
||||||
|
),
|
||||||
|
sa.Column(
|
||||||
|
"transaction_id", sa.BigInteger(), autoincrement=False, nullable=False
|
||||||
|
),
|
||||||
|
sa.Column("end_transaction_id", sa.BigInteger(), nullable=True),
|
||||||
|
sa.Column("operation_type", sa.SmallInteger(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint(
|
||||||
|
"uuid", "transaction_id", name=op.f("pk_log_type_version")
|
||||||
|
),
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_log_type_version_end_transaction_id"),
|
||||||
|
"log_type_version",
|
||||||
|
["end_transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_log_type_version_operation_type"),
|
||||||
|
"log_type_version",
|
||||||
|
["operation_type"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
"ix_log_type_version_pk_transaction_id",
|
||||||
|
"log_type_version",
|
||||||
|
["uuid", sa.literal_column("transaction_id DESC")],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
"ix_log_type_version_pk_validity",
|
||||||
|
"log_type_version",
|
||||||
|
["uuid", "transaction_id", "end_transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_log_type_version_transaction_id"),
|
||||||
|
"log_type_version",
|
||||||
|
["transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
|
||||||
|
# log_type
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_log_type_version_transaction_id"), table_name="log_type_version"
|
||||||
|
)
|
||||||
|
op.drop_index("ix_log_type_version_pk_validity", table_name="log_type_version")
|
||||||
|
op.drop_index(
|
||||||
|
"ix_log_type_version_pk_transaction_id", table_name="log_type_version"
|
||||||
|
)
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_log_type_version_operation_type"), table_name="log_type_version"
|
||||||
|
)
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_log_type_version_end_transaction_id"), table_name="log_type_version"
|
||||||
|
)
|
||||||
|
op.drop_table("log_type_version")
|
||||||
|
op.drop_table("log_type")
|
||||||
|
|
@ -0,0 +1,132 @@
|
||||||
|
"""add Land Assets
|
||||||
|
|
||||||
|
Revision ID: e416b96467fc
|
||||||
|
Revises: e0d9f72575d6
|
||||||
|
Create Date: 2026-02-13 09:39:31.327442
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
import wuttjamaican.db.util
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = "e416b96467fc"
|
||||||
|
down_revision: Union[str, None] = "e0d9f72575d6"
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
|
||||||
|
# land_asset
|
||||||
|
op.create_table(
|
||||||
|
"land_asset",
|
||||||
|
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
|
||||||
|
sa.Column("name", sa.String(length=100), nullable=False),
|
||||||
|
sa.Column("land_type_uuid", wuttjamaican.db.util.UUID(), nullable=False),
|
||||||
|
sa.Column("is_location", sa.Boolean(), nullable=False),
|
||||||
|
sa.Column("is_fixed", sa.Boolean(), nullable=False),
|
||||||
|
sa.Column("notes", sa.Text(), nullable=True),
|
||||||
|
sa.Column("active", sa.Boolean(), nullable=False),
|
||||||
|
sa.Column("farmos_uuid", wuttjamaican.db.util.UUID(), nullable=True),
|
||||||
|
sa.Column("drupal_id", sa.Integer(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["land_type_uuid"],
|
||||||
|
["land_type.uuid"],
|
||||||
|
name=op.f("fk_land_asset_land_type_uuid_land_type"),
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_land_asset")),
|
||||||
|
sa.UniqueConstraint("drupal_id", name=op.f("uq_land_asset_drupal_id")),
|
||||||
|
sa.UniqueConstraint("farmos_uuid", name=op.f("uq_land_asset_farmos_uuid")),
|
||||||
|
sa.UniqueConstraint(
|
||||||
|
"land_type_uuid", name=op.f("uq_land_asset_land_type_uuid")
|
||||||
|
),
|
||||||
|
sa.UniqueConstraint("name", name=op.f("uq_land_asset_name")),
|
||||||
|
)
|
||||||
|
op.create_table(
|
||||||
|
"land_asset_version",
|
||||||
|
sa.Column(
|
||||||
|
"uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
|
||||||
|
),
|
||||||
|
sa.Column("name", sa.String(length=100), autoincrement=False, nullable=True),
|
||||||
|
sa.Column(
|
||||||
|
"land_type_uuid",
|
||||||
|
wuttjamaican.db.util.UUID(),
|
||||||
|
autoincrement=False,
|
||||||
|
nullable=True,
|
||||||
|
),
|
||||||
|
sa.Column("is_location", sa.Boolean(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column("is_fixed", sa.Boolean(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column("notes", sa.Text(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column("active", sa.Boolean(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column(
|
||||||
|
"farmos_uuid",
|
||||||
|
wuttjamaican.db.util.UUID(),
|
||||||
|
autoincrement=False,
|
||||||
|
nullable=True,
|
||||||
|
),
|
||||||
|
sa.Column("drupal_id", sa.Integer(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column(
|
||||||
|
"transaction_id", sa.BigInteger(), autoincrement=False, nullable=False
|
||||||
|
),
|
||||||
|
sa.Column("end_transaction_id", sa.BigInteger(), nullable=True),
|
||||||
|
sa.Column("operation_type", sa.SmallInteger(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint(
|
||||||
|
"uuid", "transaction_id", name=op.f("pk_land_asset_version")
|
||||||
|
),
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_land_asset_version_end_transaction_id"),
|
||||||
|
"land_asset_version",
|
||||||
|
["end_transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_land_asset_version_operation_type"),
|
||||||
|
"land_asset_version",
|
||||||
|
["operation_type"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
"ix_land_asset_version_pk_transaction_id",
|
||||||
|
"land_asset_version",
|
||||||
|
["uuid", sa.literal_column("transaction_id DESC")],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
"ix_land_asset_version_pk_validity",
|
||||||
|
"land_asset_version",
|
||||||
|
["uuid", "transaction_id", "end_transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_land_asset_version_transaction_id"),
|
||||||
|
"land_asset_version",
|
||||||
|
["transaction_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
|
||||||
|
# land_asset
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_land_asset_version_transaction_id"), table_name="land_asset_version"
|
||||||
|
)
|
||||||
|
op.drop_index("ix_land_asset_version_pk_validity", table_name="land_asset_version")
|
||||||
|
op.drop_index(
|
||||||
|
"ix_land_asset_version_pk_transaction_id", table_name="land_asset_version"
|
||||||
|
)
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_land_asset_version_operation_type"), table_name="land_asset_version"
|
||||||
|
)
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_land_asset_version_end_transaction_id"),
|
||||||
|
table_name="land_asset_version",
|
||||||
|
)
|
||||||
|
op.drop_table("land_asset_version")
|
||||||
|
op.drop_table("land_asset")
|
||||||
|
|
@ -26,4 +26,13 @@ WuttaFarm data models
|
||||||
# bring in all of wutta
|
# bring in all of wutta
|
||||||
from wuttjamaican.db.model import *
|
from wuttjamaican.db.model import *
|
||||||
|
|
||||||
# TODO: import other/custom models here...
|
# wutta model extensions
|
||||||
|
from .users import WuttaFarmUser
|
||||||
|
|
||||||
|
# wuttafarm proper models
|
||||||
|
from .assets import AssetType
|
||||||
|
from .land import LandType, LandAsset
|
||||||
|
from .structures import StructureType, Structure
|
||||||
|
from .animals import AnimalType, Animal
|
||||||
|
from .groups import Group
|
||||||
|
from .logs import LogType, ActivityLog
|
||||||
|
|
|
||||||
194
src/wuttafarm/db/model/animals.py
Normal file
194
src/wuttafarm/db/model/animals.py
Normal file
|
|
@ -0,0 +1,194 @@
|
||||||
|
# -*- 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/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
Model definition for Animal Types
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import orm
|
||||||
|
|
||||||
|
from wuttjamaican.db import model
|
||||||
|
|
||||||
|
|
||||||
|
class AnimalType(model.Base):
|
||||||
|
"""
|
||||||
|
Represents an "animal type" (taxonomy term) from farmOS
|
||||||
|
"""
|
||||||
|
|
||||||
|
__tablename__ = "animal_type"
|
||||||
|
__versioned__ = {
|
||||||
|
"exclude": [
|
||||||
|
"changed",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
__wutta_hint__ = {
|
||||||
|
"model_title": "Animal Type",
|
||||||
|
"model_title_plural": "Animal Types",
|
||||||
|
}
|
||||||
|
|
||||||
|
uuid = model.uuid_column()
|
||||||
|
|
||||||
|
name = sa.Column(
|
||||||
|
sa.String(length=100),
|
||||||
|
nullable=False,
|
||||||
|
unique=True,
|
||||||
|
doc="""
|
||||||
|
Name of the animal type.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
description = sa.Column(
|
||||||
|
sa.String(length=255),
|
||||||
|
nullable=True,
|
||||||
|
doc="""
|
||||||
|
Optional description for the animal type.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
changed = sa.Column(
|
||||||
|
sa.DateTime(),
|
||||||
|
nullable=True,
|
||||||
|
doc="""
|
||||||
|
When the animal type was last changed, according to farmOS.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
farmos_uuid = sa.Column(
|
||||||
|
model.UUID(),
|
||||||
|
nullable=True,
|
||||||
|
unique=True,
|
||||||
|
doc="""
|
||||||
|
UUID for the animal type within farmOS.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
drupal_id = sa.Column(
|
||||||
|
sa.Integer(),
|
||||||
|
nullable=True,
|
||||||
|
unique=True,
|
||||||
|
doc="""
|
||||||
|
Drupal internal ID for the animal type.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name or ""
|
||||||
|
|
||||||
|
|
||||||
|
class Animal(model.Base):
|
||||||
|
"""
|
||||||
|
Represents an animal from farmOS
|
||||||
|
"""
|
||||||
|
|
||||||
|
__tablename__ = "animal"
|
||||||
|
__versioned__ = {}
|
||||||
|
__wutta_hint__ = {
|
||||||
|
"model_title": "Animal",
|
||||||
|
"model_title_plural": "Animals",
|
||||||
|
}
|
||||||
|
|
||||||
|
uuid = model.uuid_column()
|
||||||
|
|
||||||
|
name = sa.Column(
|
||||||
|
sa.String(length=100),
|
||||||
|
nullable=False,
|
||||||
|
doc="""
|
||||||
|
Name for the animal.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
animal_type_uuid = model.uuid_fk_column("animal_type.uuid", nullable=False)
|
||||||
|
animal_type = orm.relationship(
|
||||||
|
"AnimalType",
|
||||||
|
doc="""
|
||||||
|
Reference to the animal type.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
birthdate = sa.Column(
|
||||||
|
sa.DateTime(),
|
||||||
|
nullable=True,
|
||||||
|
doc="""
|
||||||
|
Birth date (and time) for the animal, if known.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
sex = sa.Column(
|
||||||
|
sa.String(length=1),
|
||||||
|
nullable=True,
|
||||||
|
doc="""
|
||||||
|
Sex of the animal.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
is_sterile = sa.Column(
|
||||||
|
sa.Boolean(),
|
||||||
|
nullable=True,
|
||||||
|
doc="""
|
||||||
|
Whether the animal is sterile (e.g. castrated).
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
active = sa.Column(
|
||||||
|
sa.Boolean(),
|
||||||
|
nullable=False,
|
||||||
|
doc="""
|
||||||
|
Whether the animal is currently active.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
notes = sa.Column(
|
||||||
|
sa.Text(),
|
||||||
|
nullable=True,
|
||||||
|
doc="""
|
||||||
|
Arbitrary notes for the animal.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
image_url = sa.Column(
|
||||||
|
sa.String(length=255),
|
||||||
|
nullable=True,
|
||||||
|
doc="""
|
||||||
|
Optional image URL for the animal.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
farmos_uuid = sa.Column(
|
||||||
|
model.UUID(),
|
||||||
|
nullable=True,
|
||||||
|
unique=True,
|
||||||
|
doc="""
|
||||||
|
UUID for the animal within farmOS.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
drupal_id = sa.Column(
|
||||||
|
sa.Integer(),
|
||||||
|
nullable=True,
|
||||||
|
unique=True,
|
||||||
|
doc="""
|
||||||
|
Drupal internal ID for the animal.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name or ""
|
||||||
82
src/wuttafarm/db/model/assets.py
Normal file
82
src/wuttafarm/db/model/assets.py
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
# -*- 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/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
Model definition for Asset Types
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import orm
|
||||||
|
|
||||||
|
from wuttjamaican.db import model
|
||||||
|
|
||||||
|
|
||||||
|
class AssetType(model.Base):
|
||||||
|
"""
|
||||||
|
Represents an "asset type" from farmOS
|
||||||
|
"""
|
||||||
|
|
||||||
|
__tablename__ = "asset_type"
|
||||||
|
__versioned__ = {}
|
||||||
|
__wutta_hint__ = {
|
||||||
|
"model_title": "Asset Type",
|
||||||
|
"model_title_plural": "Asset Types",
|
||||||
|
}
|
||||||
|
|
||||||
|
uuid = model.uuid_column()
|
||||||
|
|
||||||
|
name = sa.Column(
|
||||||
|
sa.String(length=100),
|
||||||
|
nullable=False,
|
||||||
|
unique=True,
|
||||||
|
doc="""
|
||||||
|
Name of the asset type.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
description = sa.Column(
|
||||||
|
sa.String(length=255),
|
||||||
|
nullable=True,
|
||||||
|
doc="""
|
||||||
|
Description for the asset type.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
farmos_uuid = sa.Column(
|
||||||
|
model.UUID(),
|
||||||
|
nullable=True,
|
||||||
|
unique=True,
|
||||||
|
doc="""
|
||||||
|
UUID for the asset type within farmOS.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
drupal_id = sa.Column(
|
||||||
|
sa.String(length=50),
|
||||||
|
nullable=True,
|
||||||
|
unique=True,
|
||||||
|
doc="""
|
||||||
|
Drupal internal ID for the asset type.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name or ""
|
||||||
106
src/wuttafarm/db/model/groups.py
Normal file
106
src/wuttafarm/db/model/groups.py
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
# -*- 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/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
Model definition for Groups
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import orm
|
||||||
|
|
||||||
|
from wuttjamaican.db import model
|
||||||
|
|
||||||
|
|
||||||
|
class Group(model.Base):
|
||||||
|
"""
|
||||||
|
Represents a "group" from farmOS
|
||||||
|
"""
|
||||||
|
|
||||||
|
__tablename__ = "group"
|
||||||
|
__versioned__ = {}
|
||||||
|
__wutta_hint__ = {
|
||||||
|
"model_title": "Group",
|
||||||
|
"model_title_plural": "Groups",
|
||||||
|
}
|
||||||
|
|
||||||
|
uuid = model.uuid_column()
|
||||||
|
|
||||||
|
name = sa.Column(
|
||||||
|
sa.String(length=100),
|
||||||
|
nullable=False,
|
||||||
|
unique=True,
|
||||||
|
doc="""
|
||||||
|
Name for the group.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
is_location = sa.Column(
|
||||||
|
sa.Boolean(),
|
||||||
|
nullable=False,
|
||||||
|
doc="""
|
||||||
|
Whether the group is considered to be a location.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
is_fixed = sa.Column(
|
||||||
|
sa.Boolean(),
|
||||||
|
nullable=False,
|
||||||
|
doc="""
|
||||||
|
Whether the group location is fixed.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
active = sa.Column(
|
||||||
|
sa.Boolean(),
|
||||||
|
nullable=False,
|
||||||
|
doc="""
|
||||||
|
Whether the group is active.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
notes = sa.Column(
|
||||||
|
sa.Text(),
|
||||||
|
nullable=True,
|
||||||
|
doc="""
|
||||||
|
Arbitrary notes for the group.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
farmos_uuid = sa.Column(
|
||||||
|
model.UUID(),
|
||||||
|
nullable=True,
|
||||||
|
unique=True,
|
||||||
|
doc="""
|
||||||
|
UUID for the group within farmOS.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
drupal_id = sa.Column(
|
||||||
|
sa.Integer(),
|
||||||
|
nullable=True,
|
||||||
|
unique=True,
|
||||||
|
doc="""
|
||||||
|
Drupal internal ID for the group.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name or ""
|
||||||
156
src/wuttafarm/db/model/land.py
Normal file
156
src/wuttafarm/db/model/land.py
Normal file
|
|
@ -0,0 +1,156 @@
|
||||||
|
# -*- 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/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
Model definition for Land Types
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import orm
|
||||||
|
|
||||||
|
from wuttjamaican.db import model
|
||||||
|
|
||||||
|
|
||||||
|
class LandType(model.Base):
|
||||||
|
"""
|
||||||
|
Represents a "land type" from farmOS
|
||||||
|
"""
|
||||||
|
|
||||||
|
__tablename__ = "land_type"
|
||||||
|
__versioned__ = {}
|
||||||
|
__wutta_hint__ = {
|
||||||
|
"model_title": "Land Type",
|
||||||
|
"model_title_plural": "Land Types",
|
||||||
|
}
|
||||||
|
|
||||||
|
uuid = model.uuid_column()
|
||||||
|
|
||||||
|
name = sa.Column(
|
||||||
|
sa.String(length=100),
|
||||||
|
nullable=False,
|
||||||
|
unique=True,
|
||||||
|
doc="""
|
||||||
|
Name of the land type.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
farmos_uuid = sa.Column(
|
||||||
|
model.UUID(),
|
||||||
|
nullable=True,
|
||||||
|
unique=True,
|
||||||
|
doc="""
|
||||||
|
UUID for the land type within farmOS.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
drupal_id = sa.Column(
|
||||||
|
sa.String(length=50),
|
||||||
|
nullable=True,
|
||||||
|
unique=True,
|
||||||
|
doc="""
|
||||||
|
Drupal internal ID for the land type.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
land_assets = orm.relationship("LandAsset", back_populates="land_type")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name or ""
|
||||||
|
|
||||||
|
|
||||||
|
class LandAsset(model.Base):
|
||||||
|
"""
|
||||||
|
Represents a "land asset" from farmOS
|
||||||
|
"""
|
||||||
|
|
||||||
|
__tablename__ = "land_asset"
|
||||||
|
__versioned__ = {}
|
||||||
|
__wutta_hint__ = {
|
||||||
|
"model_title": "Land Asset",
|
||||||
|
"model_title_plural": "Land Assets",
|
||||||
|
}
|
||||||
|
|
||||||
|
uuid = model.uuid_column()
|
||||||
|
|
||||||
|
name = sa.Column(
|
||||||
|
sa.String(length=100),
|
||||||
|
nullable=False,
|
||||||
|
unique=True,
|
||||||
|
doc="""
|
||||||
|
Name of the land asset.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
land_type_uuid = model.uuid_fk_column("land_type.uuid", nullable=False, unique=True)
|
||||||
|
land_type = orm.relationship(LandType, back_populates="land_assets")
|
||||||
|
|
||||||
|
is_location = sa.Column(
|
||||||
|
sa.Boolean(),
|
||||||
|
nullable=False,
|
||||||
|
doc="""
|
||||||
|
Whether the land asset should be considered a location.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
is_fixed = sa.Column(
|
||||||
|
sa.Boolean(),
|
||||||
|
nullable=False,
|
||||||
|
doc="""
|
||||||
|
Whether the land asset's location is fixed.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
notes = sa.Column(
|
||||||
|
sa.Text(),
|
||||||
|
nullable=True,
|
||||||
|
doc="""
|
||||||
|
Notes for the land asset.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
active = sa.Column(
|
||||||
|
sa.Boolean(),
|
||||||
|
nullable=False,
|
||||||
|
doc="""
|
||||||
|
Whether the land asset is currently active.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
farmos_uuid = sa.Column(
|
||||||
|
model.UUID(),
|
||||||
|
nullable=True,
|
||||||
|
unique=True,
|
||||||
|
doc="""
|
||||||
|
UUID for the land asset within farmOS.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
drupal_id = sa.Column(
|
||||||
|
sa.Integer(),
|
||||||
|
nullable=True,
|
||||||
|
unique=True,
|
||||||
|
doc="""
|
||||||
|
Drupal internal ID for the land asset.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name or ""
|
||||||
150
src/wuttafarm/db/model/logs.py
Normal file
150
src/wuttafarm/db/model/logs.py
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
# -*- 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/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
Model definition for Log Types
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import orm
|
||||||
|
|
||||||
|
from wuttjamaican.db import model
|
||||||
|
|
||||||
|
|
||||||
|
class LogType(model.Base):
|
||||||
|
"""
|
||||||
|
Represents a "log type" from farmOS
|
||||||
|
"""
|
||||||
|
|
||||||
|
__tablename__ = "log_type"
|
||||||
|
__versioned__ = {}
|
||||||
|
__wutta_hint__ = {
|
||||||
|
"model_title": "Log Type",
|
||||||
|
"model_title_plural": "Log Types",
|
||||||
|
}
|
||||||
|
|
||||||
|
uuid = model.uuid_column()
|
||||||
|
|
||||||
|
name = sa.Column(
|
||||||
|
sa.String(length=100),
|
||||||
|
nullable=False,
|
||||||
|
unique=True,
|
||||||
|
doc="""
|
||||||
|
Name of the log type.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
description = sa.Column(
|
||||||
|
sa.String(length=255),
|
||||||
|
nullable=True,
|
||||||
|
doc="""
|
||||||
|
Optional description for the log type.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
farmos_uuid = sa.Column(
|
||||||
|
model.UUID(),
|
||||||
|
nullable=True,
|
||||||
|
unique=True,
|
||||||
|
doc="""
|
||||||
|
UUID for the log type within farmOS.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
drupal_id = sa.Column(
|
||||||
|
sa.String(length=50),
|
||||||
|
nullable=True,
|
||||||
|
unique=True,
|
||||||
|
doc="""
|
||||||
|
Drupal internal ID for the log type.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name or ""
|
||||||
|
|
||||||
|
|
||||||
|
class ActivityLog(model.Base):
|
||||||
|
"""
|
||||||
|
Represents an activity log from farmOS
|
||||||
|
"""
|
||||||
|
|
||||||
|
__tablename__ = "log_activity"
|
||||||
|
__versioned__ = {}
|
||||||
|
__wutta_hint__ = {
|
||||||
|
"model_title": "Activity Log",
|
||||||
|
"model_title_plural": "Activity Logs",
|
||||||
|
}
|
||||||
|
|
||||||
|
uuid = model.uuid_column()
|
||||||
|
|
||||||
|
message = sa.Column(
|
||||||
|
sa.String(length=255),
|
||||||
|
nullable=False,
|
||||||
|
doc="""
|
||||||
|
Message text for the log.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
timestamp = sa.Column(
|
||||||
|
sa.DateTime(),
|
||||||
|
nullable=False,
|
||||||
|
doc="""
|
||||||
|
Date and time when the log event occurred / will occur.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
status = sa.Column(
|
||||||
|
sa.String(length=20),
|
||||||
|
nullable=False,
|
||||||
|
doc="""
|
||||||
|
Current status of the log event.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
notes = sa.Column(
|
||||||
|
sa.Text(),
|
||||||
|
nullable=True,
|
||||||
|
doc="""
|
||||||
|
Arbitrary notes for the log event.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
farmos_uuid = sa.Column(
|
||||||
|
model.UUID(),
|
||||||
|
nullable=True,
|
||||||
|
unique=True,
|
||||||
|
doc="""
|
||||||
|
UUID for the log within farmOS.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
drupal_id = sa.Column(
|
||||||
|
sa.Integer(),
|
||||||
|
nullable=True,
|
||||||
|
unique=True,
|
||||||
|
doc="""
|
||||||
|
Drupal internal ID for the log.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.message or ""
|
||||||
167
src/wuttafarm/db/model/structures.py
Normal file
167
src/wuttafarm/db/model/structures.py
Normal file
|
|
@ -0,0 +1,167 @@
|
||||||
|
# -*- 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/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
Model definition for Structure Types
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import orm
|
||||||
|
|
||||||
|
from wuttjamaican.db import model
|
||||||
|
|
||||||
|
|
||||||
|
class StructureType(model.Base):
|
||||||
|
"""
|
||||||
|
Represents a "structure type" from farmOS
|
||||||
|
"""
|
||||||
|
|
||||||
|
__tablename__ = "structure_type"
|
||||||
|
__versioned__ = {}
|
||||||
|
__wutta_hint__ = {
|
||||||
|
"model_title": "Structure Type",
|
||||||
|
"model_title_plural": "Structure Types",
|
||||||
|
}
|
||||||
|
|
||||||
|
uuid = model.uuid_column()
|
||||||
|
|
||||||
|
name = sa.Column(
|
||||||
|
sa.String(length=100),
|
||||||
|
nullable=False,
|
||||||
|
unique=True,
|
||||||
|
doc="""
|
||||||
|
Name of the structure type.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
farmos_uuid = sa.Column(
|
||||||
|
model.UUID(),
|
||||||
|
nullable=True,
|
||||||
|
unique=True,
|
||||||
|
doc="""
|
||||||
|
UUID for the structure type within farmOS.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
drupal_id = sa.Column(
|
||||||
|
sa.String(length=50),
|
||||||
|
nullable=True,
|
||||||
|
unique=True,
|
||||||
|
doc="""
|
||||||
|
Drupal internal ID for the structure type.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name or ""
|
||||||
|
|
||||||
|
|
||||||
|
class Structure(model.Base):
|
||||||
|
"""
|
||||||
|
Represents a structure from farmOS
|
||||||
|
"""
|
||||||
|
|
||||||
|
__tablename__ = "structure"
|
||||||
|
__versioned__ = {}
|
||||||
|
__wutta_hint__ = {
|
||||||
|
"model_title": "Structure",
|
||||||
|
"model_title_plural": "Structures",
|
||||||
|
}
|
||||||
|
|
||||||
|
uuid = model.uuid_column()
|
||||||
|
|
||||||
|
name = sa.Column(
|
||||||
|
sa.String(length=100),
|
||||||
|
nullable=False,
|
||||||
|
unique=True,
|
||||||
|
doc="""
|
||||||
|
Name for the structure.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
active = sa.Column(
|
||||||
|
sa.Boolean(),
|
||||||
|
nullable=False,
|
||||||
|
doc="""
|
||||||
|
Whether the structure is currently active.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
structure_type_uuid = model.uuid_fk_column("structure_type.uuid", nullable=False)
|
||||||
|
structure_type = orm.relationship(
|
||||||
|
"StructureType",
|
||||||
|
doc="""
|
||||||
|
Reference to the type of structure.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
is_location = sa.Column(
|
||||||
|
sa.Boolean(),
|
||||||
|
nullable=False,
|
||||||
|
doc="""
|
||||||
|
Whether the structure is considered a location.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
is_fixed = sa.Column(
|
||||||
|
sa.Boolean(),
|
||||||
|
nullable=False,
|
||||||
|
doc="""
|
||||||
|
Whether the structure location is fixed.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
notes = sa.Column(
|
||||||
|
sa.Text(),
|
||||||
|
nullable=True,
|
||||||
|
doc="""
|
||||||
|
Arbitrary notes for the structure.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
image_url = sa.Column(
|
||||||
|
sa.String(length=255),
|
||||||
|
nullable=True,
|
||||||
|
doc="""
|
||||||
|
Optional image URL for the structure.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
farmos_uuid = sa.Column(
|
||||||
|
model.UUID(),
|
||||||
|
nullable=True,
|
||||||
|
unique=True,
|
||||||
|
doc="""
|
||||||
|
UUID for the structure within farmOS.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
drupal_id = sa.Column(
|
||||||
|
sa.Integer(),
|
||||||
|
nullable=True,
|
||||||
|
unique=True,
|
||||||
|
doc="""
|
||||||
|
Drupal internal ID for the structure.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name or ""
|
||||||
80
src/wuttafarm/db/model/users.py
Normal file
80
src/wuttafarm/db/model/users.py
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
# -*- 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/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
Model definition for Users (extension)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import orm
|
||||||
|
|
||||||
|
from wuttjamaican.db import model
|
||||||
|
|
||||||
|
|
||||||
|
class WuttaFarmUser(model.Base):
|
||||||
|
"""
|
||||||
|
WuttaFarm extension for the User model.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__tablename__ = "wuttafarm_user"
|
||||||
|
__versioned__ = {}
|
||||||
|
|
||||||
|
uuid = model.uuid_column(sa.ForeignKey("user.uuid"), default=None)
|
||||||
|
|
||||||
|
user = orm.relationship(
|
||||||
|
model.User,
|
||||||
|
doc="""
|
||||||
|
Reference to the User which this record extends.
|
||||||
|
""",
|
||||||
|
backref=orm.backref(
|
||||||
|
"_wuttafarm",
|
||||||
|
uselist=False,
|
||||||
|
cascade="all, delete-orphan",
|
||||||
|
cascade_backrefs=False,
|
||||||
|
doc="""
|
||||||
|
Reference to the WuttaFarm-specific extension record for
|
||||||
|
the user.
|
||||||
|
""",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
farmos_uuid = sa.Column(
|
||||||
|
model.UUID(),
|
||||||
|
nullable=True,
|
||||||
|
doc="""
|
||||||
|
UUID for the user within farmOS
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
drupal_id = sa.Column(
|
||||||
|
sa.Integer(),
|
||||||
|
nullable=True,
|
||||||
|
doc="""
|
||||||
|
Drupal internal ID for the user.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.user or "")
|
||||||
|
|
||||||
|
|
||||||
|
WuttaFarmUser.make_proxy(model.User, "_wuttafarm", "farmos_uuid")
|
||||||
|
WuttaFarmUser.make_proxy(model.User, "_wuttafarm", "drupal_id")
|
||||||
32
src/wuttafarm/emails.py
Normal file
32
src/wuttafarm/emails.py
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
# -*- 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/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
Email sending config for WuttaFarm
|
||||||
|
"""
|
||||||
|
|
||||||
|
from wuttasync.emails import ImportExportWarning
|
||||||
|
|
||||||
|
|
||||||
|
class import_to_wuttafarm_from_farmos_warning(ImportExportWarning):
|
||||||
|
"""
|
||||||
|
Diff warning for farmOS → WuttaFarm import.
|
||||||
|
"""
|
||||||
24
src/wuttafarm/importing/__init__.py
Normal file
24
src/wuttafarm/importing/__init__.py
Normal file
|
|
@ -0,0 +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/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
Importing data to WuttaFarm
|
||||||
|
"""
|
||||||
620
src/wuttafarm/importing/farmos.py
Normal file
620
src/wuttafarm/importing/farmos.py
Normal file
|
|
@ -0,0 +1,620 @@
|
||||||
|
# -*- 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/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
Data import for farmOS -> WuttaFarm
|
||||||
|
"""
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import logging
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
from oauthlib.oauth2 import BackendApplicationClient
|
||||||
|
from requests_oauthlib import OAuth2Session
|
||||||
|
|
||||||
|
from wuttasync.importing import ImportHandler, ToWuttaHandler, Importer, ToWutta
|
||||||
|
|
||||||
|
from wuttafarm.db import model
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class FromFarmOSHandler(ImportHandler):
|
||||||
|
"""
|
||||||
|
Base class for import handler using farmOS API as data source.
|
||||||
|
"""
|
||||||
|
|
||||||
|
source_key = "farmos"
|
||||||
|
generic_source_title = "farmOS"
|
||||||
|
|
||||||
|
def begin_source_transaction(self):
|
||||||
|
"""
|
||||||
|
Establish the farmOS API client.
|
||||||
|
"""
|
||||||
|
token = self.get_farmos_oauth2_token()
|
||||||
|
self.farmos_client = self.app.get_farmos_client(token=token)
|
||||||
|
|
||||||
|
def get_farmos_oauth2_token(self):
|
||||||
|
|
||||||
|
client_id = self.config.get(
|
||||||
|
"farmos.oauth2.importing.client_id", default="wuttafarm"
|
||||||
|
)
|
||||||
|
client_secret = self.config.require("farmos.oauth2.importing.client_secret")
|
||||||
|
scope = self.config.get("farmos.oauth2.importing.scope", default="farm_manager")
|
||||||
|
|
||||||
|
client = BackendApplicationClient(client_id=client_id)
|
||||||
|
oauth = OAuth2Session(client=client)
|
||||||
|
|
||||||
|
return oauth.fetch_token(
|
||||||
|
token_url=self.app.get_farmos_url("/oauth/token"),
|
||||||
|
include_client_id=True,
|
||||||
|
client_secret=client_secret,
|
||||||
|
scope=scope,
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_importer_kwargs(self, key, **kwargs):
|
||||||
|
kwargs = super().get_importer_kwargs(key, **kwargs)
|
||||||
|
kwargs["farmos_client"] = self.farmos_client
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
|
||||||
|
class ToWuttaFarmHandler(ToWuttaHandler):
|
||||||
|
"""
|
||||||
|
Base class for import handler targeting WuttaFarm
|
||||||
|
"""
|
||||||
|
|
||||||
|
target_key = "wuttafarm"
|
||||||
|
|
||||||
|
|
||||||
|
class FromFarmOSToWuttaFarm(FromFarmOSHandler, ToWuttaFarmHandler):
|
||||||
|
"""
|
||||||
|
Handler for farmOS → WuttaFarm import.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def define_importers(self):
|
||||||
|
""" """
|
||||||
|
importers = super().define_importers()
|
||||||
|
importers["User"] = UserImporter
|
||||||
|
importers["AssetType"] = AssetTypeImporter
|
||||||
|
importers["LandType"] = LandTypeImporter
|
||||||
|
importers["LandAsset"] = LandAssetImporter
|
||||||
|
importers["StructureType"] = StructureTypeImporter
|
||||||
|
importers["Structure"] = StructureImporter
|
||||||
|
importers["AnimalType"] = AnimalTypeImporter
|
||||||
|
importers["Animal"] = AnimalImporter
|
||||||
|
importers["Group"] = GroupImporter
|
||||||
|
importers["LogType"] = LogTypeImporter
|
||||||
|
importers["ActivityLog"] = ActivityLogImporter
|
||||||
|
return importers
|
||||||
|
|
||||||
|
|
||||||
|
class FromFarmOS(Importer):
|
||||||
|
"""
|
||||||
|
Base class for importers using farmOS API as data source.
|
||||||
|
"""
|
||||||
|
|
||||||
|
key = "farmos_uuid"
|
||||||
|
|
||||||
|
def get_supported_fields(self):
|
||||||
|
"""
|
||||||
|
Auto-remove the ``uuid`` field, since we use ``farmos_uuid``
|
||||||
|
instead for the importer key.
|
||||||
|
"""
|
||||||
|
fields = list(super().get_supported_fields())
|
||||||
|
if "uuid" in fields:
|
||||||
|
fields.remove("uuid")
|
||||||
|
return fields
|
||||||
|
|
||||||
|
def normalize_datetime(self, dt):
|
||||||
|
"""
|
||||||
|
Convert a farmOS datetime value to naive UTC used by
|
||||||
|
WuttaFarm.
|
||||||
|
|
||||||
|
:param dt: Date/time string value "as-is" from the farmOS API.
|
||||||
|
|
||||||
|
:returns: Equivalent naive UTC ``datetime``
|
||||||
|
"""
|
||||||
|
dt = datetime.datetime.fromisoformat(dt)
|
||||||
|
return self.app.make_utc(dt)
|
||||||
|
|
||||||
|
|
||||||
|
class ActivityLogImporter(FromFarmOS, ToWutta):
|
||||||
|
"""
|
||||||
|
farmOS API → WuttaFarm importer for Activity Logs
|
||||||
|
"""
|
||||||
|
|
||||||
|
model_class = model.ActivityLog
|
||||||
|
|
||||||
|
supported_fields = [
|
||||||
|
"farmos_uuid",
|
||||||
|
"drupal_id",
|
||||||
|
"message",
|
||||||
|
"timestamp",
|
||||||
|
"notes",
|
||||||
|
"status",
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_source_objects(self):
|
||||||
|
""" """
|
||||||
|
logs = self.farmos_client.log.get("activity")
|
||||||
|
return logs["data"]
|
||||||
|
|
||||||
|
def normalize_source_object(self, log):
|
||||||
|
""" """
|
||||||
|
|
||||||
|
if notes := log["attributes"]["notes"]:
|
||||||
|
notes = notes["value"]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"farmos_uuid": UUID(log["id"]),
|
||||||
|
"drupal_id": log["attributes"]["drupal_internal__id"],
|
||||||
|
"message": log["attributes"]["name"],
|
||||||
|
"timestamp": self.normalize_datetime(log["attributes"]["timestamp"]),
|
||||||
|
"notes": notes,
|
||||||
|
"status": log["attributes"]["status"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class AnimalImporter(FromFarmOS, ToWutta):
|
||||||
|
"""
|
||||||
|
farmOS API → WuttaFarm importer for Animals
|
||||||
|
"""
|
||||||
|
|
||||||
|
model_class = model.Animal
|
||||||
|
|
||||||
|
supported_fields = [
|
||||||
|
"farmos_uuid",
|
||||||
|
"drupal_id",
|
||||||
|
"name",
|
||||||
|
"animal_type_uuid",
|
||||||
|
"sex",
|
||||||
|
"is_sterile",
|
||||||
|
"birthdate",
|
||||||
|
"notes",
|
||||||
|
"active",
|
||||||
|
"image_url",
|
||||||
|
]
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
super().setup()
|
||||||
|
model = self.app.model
|
||||||
|
|
||||||
|
self.animal_types_by_farmos_uuid = {}
|
||||||
|
for animal_type in self.target_session.query(model.AnimalType):
|
||||||
|
if animal_type.farmos_uuid:
|
||||||
|
self.animal_types_by_farmos_uuid[animal_type.farmos_uuid] = animal_type
|
||||||
|
|
||||||
|
def get_source_objects(self):
|
||||||
|
""" """
|
||||||
|
animals = self.farmos_client.asset.get("animal")
|
||||||
|
return animals["data"]
|
||||||
|
|
||||||
|
def normalize_source_object(self, animal):
|
||||||
|
""" """
|
||||||
|
animal_type_uuid = None
|
||||||
|
image_url = None
|
||||||
|
if relationships := animal.get("relationships"):
|
||||||
|
|
||||||
|
if animal_type := relationships.get("animal_type"):
|
||||||
|
if animal_type["data"]:
|
||||||
|
if animal_type := self.animal_types_by_farmos_uuid.get(
|
||||||
|
UUID(animal_type["data"]["id"])
|
||||||
|
):
|
||||||
|
animal_type_uuid = animal_type.uuid
|
||||||
|
|
||||||
|
if image := relationships.get("image"):
|
||||||
|
if image["data"]:
|
||||||
|
image = self.farmos_client.resource.get_id(
|
||||||
|
"file", "file", image["data"][0]["id"]
|
||||||
|
)
|
||||||
|
if image_style := image["data"]["attributes"].get(
|
||||||
|
"image_style_uri"
|
||||||
|
):
|
||||||
|
image_url = image_style["large"]
|
||||||
|
|
||||||
|
if not animal_type_uuid:
|
||||||
|
log.warning("missing/invalid animal_type for farmOS Animal: %s", animal)
|
||||||
|
return None
|
||||||
|
|
||||||
|
birthdate = animal["attributes"]["birthdate"]
|
||||||
|
if birthdate:
|
||||||
|
birthdate = datetime.datetime.fromisoformat(birthdate)
|
||||||
|
birthdate = self.app.localtime(birthdate)
|
||||||
|
birthdate = self.app.make_utc(birthdate)
|
||||||
|
|
||||||
|
if notes := animal["attributes"]["notes"]:
|
||||||
|
notes = notes["value"]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"farmos_uuid": UUID(animal["id"]),
|
||||||
|
"drupal_id": animal["attributes"]["drupal_internal__id"],
|
||||||
|
"name": animal["attributes"]["name"],
|
||||||
|
"animal_type_uuid": animal_type.uuid,
|
||||||
|
"sex": animal["attributes"]["sex"],
|
||||||
|
"is_sterile": animal["attributes"]["is_castrated"],
|
||||||
|
"birthdate": birthdate,
|
||||||
|
"active": animal["attributes"]["status"] == "active",
|
||||||
|
"notes": notes,
|
||||||
|
"image_url": image_url,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class AnimalTypeImporter(FromFarmOS, ToWutta):
|
||||||
|
"""
|
||||||
|
farmOS API → WuttaFarm importer for Animal Types
|
||||||
|
"""
|
||||||
|
|
||||||
|
model_class = model.AnimalType
|
||||||
|
|
||||||
|
supported_fields = [
|
||||||
|
"farmos_uuid",
|
||||||
|
"drupal_id",
|
||||||
|
"name",
|
||||||
|
"description",
|
||||||
|
"changed",
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_source_objects(self):
|
||||||
|
""" """
|
||||||
|
animal_types = self.farmos_client.resource.get("taxonomy_term", "animal_type")
|
||||||
|
return animal_types["data"]
|
||||||
|
|
||||||
|
def normalize_source_object(self, animal_type):
|
||||||
|
""" """
|
||||||
|
return {
|
||||||
|
"farmos_uuid": UUID(animal_type["id"]),
|
||||||
|
"drupal_id": animal_type["attributes"]["drupal_internal__tid"],
|
||||||
|
"name": animal_type["attributes"]["name"],
|
||||||
|
"description": animal_type["attributes"]["description"],
|
||||||
|
"changed": self.normalize_datetime(animal_type["attributes"]["changed"]),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class AssetTypeImporter(FromFarmOS, ToWutta):
|
||||||
|
"""
|
||||||
|
farmOS API → WuttaFarm importer for Asset Types
|
||||||
|
"""
|
||||||
|
|
||||||
|
model_class = model.AssetType
|
||||||
|
|
||||||
|
supported_fields = [
|
||||||
|
"farmos_uuid",
|
||||||
|
"drupal_id",
|
||||||
|
"name",
|
||||||
|
"description",
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_source_objects(self):
|
||||||
|
""" """
|
||||||
|
asset_types = self.farmos_client.resource.get("asset_type")
|
||||||
|
return asset_types["data"]
|
||||||
|
|
||||||
|
def normalize_source_object(self, asset_type):
|
||||||
|
""" """
|
||||||
|
return {
|
||||||
|
"farmos_uuid": UUID(asset_type["id"]),
|
||||||
|
"drupal_id": asset_type["attributes"]["drupal_internal__id"],
|
||||||
|
"name": asset_type["attributes"]["label"],
|
||||||
|
"description": asset_type["attributes"]["description"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class GroupImporter(FromFarmOS, ToWutta):
|
||||||
|
"""
|
||||||
|
farmOS API → WuttaFarm importer for Groups
|
||||||
|
"""
|
||||||
|
|
||||||
|
model_class = model.Group
|
||||||
|
|
||||||
|
supported_fields = [
|
||||||
|
"farmos_uuid",
|
||||||
|
"drupal_id",
|
||||||
|
"name",
|
||||||
|
"is_location",
|
||||||
|
"is_fixed",
|
||||||
|
"notes",
|
||||||
|
"active",
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_source_objects(self):
|
||||||
|
""" """
|
||||||
|
groups = self.farmos_client.asset.get("group")
|
||||||
|
return groups["data"]
|
||||||
|
|
||||||
|
def normalize_source_object(self, group):
|
||||||
|
""" """
|
||||||
|
if notes := group["attributes"]["notes"]:
|
||||||
|
notes = notes["value"]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"farmos_uuid": UUID(group["id"]),
|
||||||
|
"drupal_id": group["attributes"]["drupal_internal__id"],
|
||||||
|
"name": group["attributes"]["name"],
|
||||||
|
"is_location": group["attributes"]["is_location"],
|
||||||
|
"is_fixed": group["attributes"]["is_fixed"],
|
||||||
|
"active": group["attributes"]["status"] == "active",
|
||||||
|
"notes": notes,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class LandAssetImporter(FromFarmOS, ToWutta):
|
||||||
|
"""
|
||||||
|
farmOS API → WuttaFarm importer for Land Assets
|
||||||
|
"""
|
||||||
|
|
||||||
|
model_class = model.LandAsset
|
||||||
|
|
||||||
|
supported_fields = [
|
||||||
|
"farmos_uuid",
|
||||||
|
"drupal_id",
|
||||||
|
"name",
|
||||||
|
"land_type_uuid",
|
||||||
|
"is_location",
|
||||||
|
"is_fixed",
|
||||||
|
"notes",
|
||||||
|
"active",
|
||||||
|
]
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
super().setup()
|
||||||
|
model = self.app.model
|
||||||
|
|
||||||
|
self.land_types_by_id = {}
|
||||||
|
for land_type in self.target_session.query(model.LandType):
|
||||||
|
self.land_types_by_id[land_type.drupal_id] = land_type
|
||||||
|
|
||||||
|
def get_source_objects(self):
|
||||||
|
""" """
|
||||||
|
land_assets = self.farmos_client.asset.get("land")
|
||||||
|
return land_assets["data"]
|
||||||
|
|
||||||
|
def normalize_source_object(self, land):
|
||||||
|
""" """
|
||||||
|
land_type_id = land["attributes"]["land_type"]
|
||||||
|
land_type = self.land_types_by_id.get(land_type_id)
|
||||||
|
if not land_type:
|
||||||
|
log.warning(
|
||||||
|
"invalid land_type '%s' for farmOS Land Asset: %s", land_type_id, land
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
if notes := land["attributes"]["notes"]:
|
||||||
|
notes = notes["value"]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"farmos_uuid": UUID(land["id"]),
|
||||||
|
"drupal_id": land["attributes"]["drupal_internal__id"],
|
||||||
|
"name": land["attributes"]["name"],
|
||||||
|
"land_type_uuid": land_type.uuid,
|
||||||
|
"is_location": land["attributes"]["is_location"],
|
||||||
|
"is_fixed": land["attributes"]["is_fixed"],
|
||||||
|
"active": land["attributes"]["status"] == "active",
|
||||||
|
"notes": notes,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class LandTypeImporter(FromFarmOS, ToWutta):
|
||||||
|
"""
|
||||||
|
farmOS API → WuttaFarm importer for Land Types
|
||||||
|
"""
|
||||||
|
|
||||||
|
model_class = model.LandType
|
||||||
|
|
||||||
|
supported_fields = [
|
||||||
|
"farmos_uuid",
|
||||||
|
"drupal_id",
|
||||||
|
"name",
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_source_objects(self):
|
||||||
|
""" """
|
||||||
|
land_types = self.farmos_client.resource.get("land_type")
|
||||||
|
return land_types["data"]
|
||||||
|
|
||||||
|
def normalize_source_object(self, land_type):
|
||||||
|
""" """
|
||||||
|
return {
|
||||||
|
"farmos_uuid": UUID(land_type["id"]),
|
||||||
|
"drupal_id": land_type["attributes"]["drupal_internal__id"],
|
||||||
|
"name": land_type["attributes"]["label"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class LogTypeImporter(FromFarmOS, ToWutta):
|
||||||
|
"""
|
||||||
|
farmOS API → WuttaFarm importer for Log Types
|
||||||
|
"""
|
||||||
|
|
||||||
|
model_class = model.LogType
|
||||||
|
|
||||||
|
supported_fields = [
|
||||||
|
"farmos_uuid",
|
||||||
|
"drupal_id",
|
||||||
|
"name",
|
||||||
|
"description",
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_source_objects(self):
|
||||||
|
""" """
|
||||||
|
log_types = self.farmos_client.resource.get("log_type")
|
||||||
|
return log_types["data"]
|
||||||
|
|
||||||
|
def normalize_source_object(self, log_type):
|
||||||
|
""" """
|
||||||
|
return {
|
||||||
|
"farmos_uuid": UUID(log_type["id"]),
|
||||||
|
"drupal_id": log_type["attributes"]["drupal_internal__id"],
|
||||||
|
"name": log_type["attributes"]["label"],
|
||||||
|
"description": log_type["attributes"]["description"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class StructureImporter(FromFarmOS, ToWutta):
|
||||||
|
"""
|
||||||
|
farmOS API → WuttaFarm importer for Structures
|
||||||
|
"""
|
||||||
|
|
||||||
|
model_class = model.Structure
|
||||||
|
|
||||||
|
supported_fields = [
|
||||||
|
"farmos_uuid",
|
||||||
|
"drupal_id",
|
||||||
|
"name",
|
||||||
|
"structure_type_uuid",
|
||||||
|
"is_location",
|
||||||
|
"is_fixed",
|
||||||
|
"notes",
|
||||||
|
"active",
|
||||||
|
"image_url",
|
||||||
|
]
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
super().setup()
|
||||||
|
model = self.app.model
|
||||||
|
|
||||||
|
self.structure_types_by_id = {}
|
||||||
|
for structure_type in self.target_session.query(model.StructureType):
|
||||||
|
self.structure_types_by_id[structure_type.drupal_id] = structure_type
|
||||||
|
|
||||||
|
def get_source_objects(self):
|
||||||
|
""" """
|
||||||
|
structures = self.farmos_client.asset.get("structure")
|
||||||
|
return structures["data"]
|
||||||
|
|
||||||
|
def normalize_source_object(self, structure):
|
||||||
|
""" """
|
||||||
|
structure_type_id = structure["attributes"]["structure_type"]
|
||||||
|
structure_type = self.structure_types_by_id.get(structure_type_id)
|
||||||
|
if not structure_type:
|
||||||
|
log.warning(
|
||||||
|
"invalid structure_type '%s' for farmOS Structure: %s",
|
||||||
|
structure_type_id,
|
||||||
|
structure,
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
if notes := structure["attributes"]["notes"]:
|
||||||
|
notes = notes["value"]
|
||||||
|
|
||||||
|
image_url = None
|
||||||
|
if relationships := structure.get("relationships"):
|
||||||
|
if image := relationships.get("image"):
|
||||||
|
if image["data"]:
|
||||||
|
image = self.farmos_client.resource.get_id(
|
||||||
|
"file", "file", image["data"][0]["id"]
|
||||||
|
)
|
||||||
|
if image_style := image["data"]["attributes"].get(
|
||||||
|
"image_style_uri"
|
||||||
|
):
|
||||||
|
image_url = image_style["large"]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"farmos_uuid": UUID(structure["id"]),
|
||||||
|
"drupal_id": structure["attributes"]["drupal_internal__id"],
|
||||||
|
"name": structure["attributes"]["name"],
|
||||||
|
"structure_type_uuid": structure_type.uuid,
|
||||||
|
"is_location": structure["attributes"]["is_location"],
|
||||||
|
"is_fixed": structure["attributes"]["is_fixed"],
|
||||||
|
"active": structure["attributes"]["status"] == "active",
|
||||||
|
"notes": notes,
|
||||||
|
"image_url": image_url,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class StructureTypeImporter(FromFarmOS, ToWutta):
|
||||||
|
"""
|
||||||
|
farmOS API → WuttaFarm importer for Structure Types
|
||||||
|
"""
|
||||||
|
|
||||||
|
model_class = model.StructureType
|
||||||
|
|
||||||
|
supported_fields = [
|
||||||
|
"farmos_uuid",
|
||||||
|
"drupal_id",
|
||||||
|
"name",
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_source_objects(self):
|
||||||
|
""" """
|
||||||
|
structure_types = self.farmos_client.resource.get("structure_type")
|
||||||
|
return structure_types["data"]
|
||||||
|
|
||||||
|
def normalize_source_object(self, structure_type):
|
||||||
|
""" """
|
||||||
|
return {
|
||||||
|
"farmos_uuid": UUID(structure_type["id"]),
|
||||||
|
"drupal_id": structure_type["attributes"]["drupal_internal__id"],
|
||||||
|
"name": structure_type["attributes"]["label"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class UserImporter(FromFarmOS, ToWutta):
|
||||||
|
"""
|
||||||
|
farmOS API → WuttaFarm importer for Users
|
||||||
|
"""
|
||||||
|
|
||||||
|
model_class = model.User
|
||||||
|
|
||||||
|
supported_fields = [
|
||||||
|
"farmos_uuid",
|
||||||
|
"drupal_id",
|
||||||
|
"username",
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_simple_fields(self):
|
||||||
|
""" """
|
||||||
|
fields = list(super().get_simple_fields())
|
||||||
|
# nb. must explicitly declare extension fields
|
||||||
|
fields.extend(
|
||||||
|
[
|
||||||
|
"farmos_uuid",
|
||||||
|
"drupal_id",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
return fields
|
||||||
|
|
||||||
|
def get_source_objects(self):
|
||||||
|
""" """
|
||||||
|
users = self.farmos_client.resource.get("user")
|
||||||
|
return users["data"]
|
||||||
|
|
||||||
|
def normalize_source_object(self, user):
|
||||||
|
""" """
|
||||||
|
|
||||||
|
# nb. skip Anonymous user which does not have drupal id
|
||||||
|
drupal_id = user["attributes"].get("drupal_internal__uid")
|
||||||
|
if not drupal_id:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return {
|
||||||
|
"farmos_uuid": UUID(user["id"]),
|
||||||
|
"drupal_id": drupal_id,
|
||||||
|
"username": user["attributes"]["name"],
|
||||||
|
}
|
||||||
|
|
||||||
|
def can_delete_object(self, user, data=None):
|
||||||
|
"""
|
||||||
|
Prevent delete for users which do not exist in farmOS.
|
||||||
|
"""
|
||||||
|
if not user.farmos_uuid:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
@ -27,6 +27,33 @@ import json
|
||||||
|
|
||||||
import colander
|
import colander
|
||||||
|
|
||||||
|
from wuttaweb.forms.schema import ObjectRef
|
||||||
|
|
||||||
|
|
||||||
|
class AnimalTypeRef(ObjectRef):
|
||||||
|
"""
|
||||||
|
Custom schema type for a
|
||||||
|
:class:`~wuttafarm.db.model.animals.AnimalType` reference field.
|
||||||
|
|
||||||
|
This is a subclass of
|
||||||
|
:class:`~wuttaweb:wuttaweb.forms.schema.ObjectRef`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def model_class(self): # pylint: disable=empty-docstring
|
||||||
|
""" """
|
||||||
|
model = self.app.model
|
||||||
|
return model.AnimalType
|
||||||
|
|
||||||
|
def sort_query(self, query): # pylint: disable=empty-docstring
|
||||||
|
""" """
|
||||||
|
return query.order_by(self.model_class.name)
|
||||||
|
|
||||||
|
def get_object_url(self, obj): # pylint: disable=empty-docstring
|
||||||
|
""" """
|
||||||
|
animal_type = obj
|
||||||
|
return self.request.route_url("animal_types.view", uuid=animal_type.uuid)
|
||||||
|
|
||||||
|
|
||||||
class AnimalTypeType(colander.SchemaType):
|
class AnimalTypeType(colander.SchemaType):
|
||||||
|
|
||||||
|
|
@ -47,6 +74,31 @@ class AnimalTypeType(colander.SchemaType):
|
||||||
return AnimalTypeWidget(self.request, **kwargs)
|
return AnimalTypeWidget(self.request, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class LandTypeRef(ObjectRef):
|
||||||
|
"""
|
||||||
|
Custom schema type for a
|
||||||
|
:class:`~wuttafarm.db.model.land.LandType` reference field.
|
||||||
|
|
||||||
|
This is a subclass of
|
||||||
|
:class:`~wuttaweb:wuttaweb.forms.schema.ObjectRef`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def model_class(self): # pylint: disable=empty-docstring
|
||||||
|
""" """
|
||||||
|
model = self.app.model
|
||||||
|
return model.LandType
|
||||||
|
|
||||||
|
def sort_query(self, query): # pylint: disable=empty-docstring
|
||||||
|
""" """
|
||||||
|
return query.order_by(self.model_class.name)
|
||||||
|
|
||||||
|
def get_object_url(self, obj): # pylint: disable=empty-docstring
|
||||||
|
""" """
|
||||||
|
land_type = obj
|
||||||
|
return self.request.route_url("land_types.view", uuid=land_type.uuid)
|
||||||
|
|
||||||
|
|
||||||
class StructureType(colander.SchemaType):
|
class StructureType(colander.SchemaType):
|
||||||
|
|
||||||
def __init__(self, request, *args, **kwargs):
|
def __init__(self, request, *args, **kwargs):
|
||||||
|
|
@ -66,6 +118,31 @@ class StructureType(colander.SchemaType):
|
||||||
return StructureWidget(self.request, **kwargs)
|
return StructureWidget(self.request, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class StructureTypeRef(ObjectRef):
|
||||||
|
"""
|
||||||
|
Custom schema type for a
|
||||||
|
:class:`~wuttafarm.db.model.structures.Structure` reference field.
|
||||||
|
|
||||||
|
This is a subclass of
|
||||||
|
:class:`~wuttaweb:wuttaweb.forms.schema.ObjectRef`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def model_class(self): # pylint: disable=empty-docstring
|
||||||
|
""" """
|
||||||
|
model = self.app.model
|
||||||
|
return model.StructureType
|
||||||
|
|
||||||
|
def sort_query(self, query): # pylint: disable=empty-docstring
|
||||||
|
""" """
|
||||||
|
return query.order_by(self.model_class.name)
|
||||||
|
|
||||||
|
def get_object_url(self, obj): # pylint: disable=empty-docstring
|
||||||
|
""" """
|
||||||
|
structure_type = obj
|
||||||
|
return self.request.route_url("structure_types.view", uuid=structure_type.uuid)
|
||||||
|
|
||||||
|
|
||||||
class UsersType(colander.SchemaType):
|
class UsersType(colander.SchemaType):
|
||||||
|
|
||||||
def __init__(self, request, *args, **kwargs):
|
def __init__(self, request, *args, **kwargs):
|
||||||
|
|
|
||||||
|
|
@ -33,10 +33,80 @@ class WuttaFarmMenuHandler(base.MenuHandler):
|
||||||
|
|
||||||
def make_menus(self, request, **kwargs):
|
def make_menus(self, request, **kwargs):
|
||||||
return [
|
return [
|
||||||
|
self.make_asset_menu(request),
|
||||||
|
self.make_log_menu(request),
|
||||||
self.make_farmos_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_asset_menu(self, request):
|
||||||
|
return {
|
||||||
|
"title": "Assets",
|
||||||
|
"type": "menu",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"title": "Animals",
|
||||||
|
"route": "animals",
|
||||||
|
"perm": "animals.list",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Groups",
|
||||||
|
"route": "groups",
|
||||||
|
"perm": "groups.list",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Structures",
|
||||||
|
"route": "structures",
|
||||||
|
"perm": "structures.list",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Land",
|
||||||
|
"route": "land_assets",
|
||||||
|
"perm": "land_assets.list",
|
||||||
|
},
|
||||||
|
{"type": "sep"},
|
||||||
|
{
|
||||||
|
"title": "Animal Types",
|
||||||
|
"route": "animal_types",
|
||||||
|
"perm": "animal_types.list",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Structure Types",
|
||||||
|
"route": "structure_types",
|
||||||
|
"perm": "structure_types.list",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Land Types",
|
||||||
|
"route": "land_types",
|
||||||
|
"perm": "land_types.list",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Asset Types",
|
||||||
|
"route": "asset_types",
|
||||||
|
"perm": "asset_types.list",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
def make_log_menu(self, request):
|
||||||
|
return {
|
||||||
|
"title": "Logs",
|
||||||
|
"type": "menu",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"title": "Activity Logs",
|
||||||
|
"route": "activity_logs",
|
||||||
|
"perm": "activity_logs.list",
|
||||||
|
},
|
||||||
|
{"type": "sep"},
|
||||||
|
{
|
||||||
|
"title": "Log Types",
|
||||||
|
"route": "log_types",
|
||||||
|
"perm": "log_types.list",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
def make_farmos_menu(self, request):
|
def make_farmos_menu(self, request):
|
||||||
config = request.wutta_config
|
config = request.wutta_config
|
||||||
app = config.get_app()
|
app = config.get_app()
|
||||||
|
|
|
||||||
45
src/wuttafarm/web/templates/farmos/master/view.mako
Normal file
45
src/wuttafarm/web/templates/farmos/master/view.mako
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
## -*- coding: utf-8; -*-
|
||||||
|
<%inherit file="/master/view.mako" />
|
||||||
|
|
||||||
|
<%def name="tool_panels()">
|
||||||
|
${parent.tool_panels()}
|
||||||
|
${self.tool_panel_tools()}
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="tool_panel_tools()">
|
||||||
|
% if raw_json:
|
||||||
|
|
||||||
|
<wutta-tool-panel heading="Tools">
|
||||||
|
<b-button type="is-primary"
|
||||||
|
icon-pack="fas"
|
||||||
|
icon-left="code"
|
||||||
|
@click="viewJsonShowDialog = true">
|
||||||
|
See raw JSON data
|
||||||
|
</b-button>
|
||||||
|
</wutta-tool-panel>
|
||||||
|
|
||||||
|
<${b}-modal :width="1200"
|
||||||
|
% if request.use_oruga:
|
||||||
|
v-model:active="viewJsonShowDialog"
|
||||||
|
% else:
|
||||||
|
:active.sync="viewJsonShowDialog"
|
||||||
|
% endif
|
||||||
|
>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-content">
|
||||||
|
${rendered_json|n}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</${b}-modal>
|
||||||
|
|
||||||
|
% endif
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="modify_vue_vars()">
|
||||||
|
${parent.modify_vue_vars()}
|
||||||
|
% if raw_json:
|
||||||
|
<script>
|
||||||
|
ThisPageData.viewJsonShowDialog = false
|
||||||
|
</script>
|
||||||
|
% endif
|
||||||
|
</%def>
|
||||||
|
|
@ -25,6 +25,8 @@ WuttaFarm Views
|
||||||
|
|
||||||
from wuttaweb.views import essential
|
from wuttaweb.views import essential
|
||||||
|
|
||||||
|
from .master import WuttaFarmMasterView
|
||||||
|
|
||||||
|
|
||||||
def includeme(config):
|
def includeme(config):
|
||||||
|
|
||||||
|
|
@ -34,8 +36,21 @@ def includeme(config):
|
||||||
**{
|
**{
|
||||||
"wuttaweb.views.auth": "wuttafarm.web.views.auth",
|
"wuttaweb.views.auth": "wuttafarm.web.views.auth",
|
||||||
"wuttaweb.views.common": "wuttafarm.web.views.common",
|
"wuttaweb.views.common": "wuttafarm.web.views.common",
|
||||||
|
"wuttaweb.views.users": "wuttafarm.web.views.users",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# native table views
|
||||||
|
config.include("wuttafarm.web.views.asset_types")
|
||||||
|
config.include("wuttafarm.web.views.land_types")
|
||||||
|
config.include("wuttafarm.web.views.structure_types")
|
||||||
|
config.include("wuttafarm.web.views.animal_types")
|
||||||
|
config.include("wuttafarm.web.views.land_assets")
|
||||||
|
config.include("wuttafarm.web.views.structures")
|
||||||
|
config.include("wuttafarm.web.views.animals")
|
||||||
|
config.include("wuttafarm.web.views.groups")
|
||||||
|
config.include("wuttafarm.web.views.log_types")
|
||||||
|
config.include("wuttafarm.web.views.logs_activity")
|
||||||
|
|
||||||
# views for farmOS
|
# views for farmOS
|
||||||
config.include("wuttafarm.web.views.farmos")
|
config.include("wuttafarm.web.views.farmos")
|
||||||
|
|
|
||||||
128
src/wuttafarm/web/views/animal_types.py
Normal file
128
src/wuttafarm/web/views/animal_types.py
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
# -*- 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 Animal Types
|
||||||
|
"""
|
||||||
|
|
||||||
|
from wuttafarm.db.model.animals import AnimalType, Animal
|
||||||
|
from wuttafarm.web.views import WuttaFarmMasterView
|
||||||
|
|
||||||
|
|
||||||
|
class AnimalTypeView(WuttaFarmMasterView):
|
||||||
|
"""
|
||||||
|
Master view for Animal Types
|
||||||
|
"""
|
||||||
|
|
||||||
|
model_class = AnimalType
|
||||||
|
route_prefix = "animal_types"
|
||||||
|
url_prefix = "/animal-types"
|
||||||
|
|
||||||
|
farmos_refurl_path = "/admin/structure/taxonomy/manage/animal_type/overview"
|
||||||
|
|
||||||
|
grid_columns = [
|
||||||
|
"name",
|
||||||
|
"description",
|
||||||
|
"changed",
|
||||||
|
]
|
||||||
|
|
||||||
|
sort_defaults = "name"
|
||||||
|
|
||||||
|
filter_defaults = {
|
||||||
|
"name": {"active": True, "verb": "contains"},
|
||||||
|
}
|
||||||
|
|
||||||
|
form_fields = [
|
||||||
|
"name",
|
||||||
|
"description",
|
||||||
|
"changed",
|
||||||
|
"farmos_uuid",
|
||||||
|
"drupal_id",
|
||||||
|
]
|
||||||
|
|
||||||
|
has_rows = True
|
||||||
|
row_model_class = Animal
|
||||||
|
rows_viewable = True
|
||||||
|
|
||||||
|
row_grid_columns = [
|
||||||
|
"name",
|
||||||
|
"sex",
|
||||||
|
"is_sterile",
|
||||||
|
"birthdate",
|
||||||
|
"active",
|
||||||
|
]
|
||||||
|
|
||||||
|
rows_sort_defaults = "name"
|
||||||
|
|
||||||
|
def configure_grid(self, grid):
|
||||||
|
g = grid
|
||||||
|
super().configure_grid(g)
|
||||||
|
|
||||||
|
# name
|
||||||
|
g.set_link("name")
|
||||||
|
|
||||||
|
def get_farmos_url(self, animal_type):
|
||||||
|
return self.app.get_farmos_url(f"/taxonomy/term/{animal_type.drupal_id}")
|
||||||
|
|
||||||
|
def get_xref_buttons(self, animal_type):
|
||||||
|
buttons = super().get_xref_buttons(animal_type)
|
||||||
|
|
||||||
|
if animal_type.farmos_uuid:
|
||||||
|
buttons.append(
|
||||||
|
self.make_button(
|
||||||
|
"View farmOS record",
|
||||||
|
primary=True,
|
||||||
|
url=self.request.route_url(
|
||||||
|
"farmos_animal_types.view", uuid=animal_type.farmos_uuid
|
||||||
|
),
|
||||||
|
icon_left="eye",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return buttons
|
||||||
|
|
||||||
|
def get_row_grid_data(self, animal_type):
|
||||||
|
model = self.app.model
|
||||||
|
session = self.Session()
|
||||||
|
return session.query(model.Animal).filter(
|
||||||
|
model.Animal.animal_type == animal_type
|
||||||
|
)
|
||||||
|
|
||||||
|
def configure_row_grid(self, grid):
|
||||||
|
g = grid
|
||||||
|
super().configure_row_grid(g)
|
||||||
|
|
||||||
|
# name
|
||||||
|
g.set_link("name")
|
||||||
|
|
||||||
|
def get_row_action_url_view(self, animal, i):
|
||||||
|
return self.request.route_url("animals.view", uuid=animal.uuid)
|
||||||
|
|
||||||
|
|
||||||
|
def defaults(config, **kwargs):
|
||||||
|
base = globals()
|
||||||
|
|
||||||
|
AnimalTypeView = kwargs.get("AnimalTypeView", base["AnimalTypeView"])
|
||||||
|
AnimalTypeView.defaults(config)
|
||||||
|
|
||||||
|
|
||||||
|
def includeme(config):
|
||||||
|
defaults(config)
|
||||||
130
src/wuttafarm/web/views/animals.py
Normal file
130
src/wuttafarm/web/views/animals.py
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
# -*- 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 Animals
|
||||||
|
"""
|
||||||
|
|
||||||
|
from wuttafarm.db.model.animals import Animal
|
||||||
|
from wuttafarm.web.views import WuttaFarmMasterView
|
||||||
|
from wuttafarm.web.forms.schema import AnimalTypeRef
|
||||||
|
from wuttafarm.web.forms.widgets import ImageWidget
|
||||||
|
|
||||||
|
|
||||||
|
class AnimalView(WuttaFarmMasterView):
|
||||||
|
"""
|
||||||
|
Master view for Animals
|
||||||
|
"""
|
||||||
|
|
||||||
|
model_class = Animal
|
||||||
|
route_prefix = "animals"
|
||||||
|
url_prefix = "/animals"
|
||||||
|
|
||||||
|
farmos_refurl_path = "/assets/animal"
|
||||||
|
|
||||||
|
grid_columns = [
|
||||||
|
"name",
|
||||||
|
"animal_type",
|
||||||
|
"sex",
|
||||||
|
"is_sterile",
|
||||||
|
"birthdate",
|
||||||
|
"active",
|
||||||
|
]
|
||||||
|
|
||||||
|
sort_defaults = "name"
|
||||||
|
|
||||||
|
filter_defaults = {
|
||||||
|
"name": {"active": True, "verb": "contains"},
|
||||||
|
}
|
||||||
|
|
||||||
|
form_fields = [
|
||||||
|
"name",
|
||||||
|
"animal_type",
|
||||||
|
"birthdate",
|
||||||
|
"sex",
|
||||||
|
"is_sterile",
|
||||||
|
"active",
|
||||||
|
"notes",
|
||||||
|
"farmos_uuid",
|
||||||
|
"drupal_id",
|
||||||
|
"image_url",
|
||||||
|
"image",
|
||||||
|
]
|
||||||
|
|
||||||
|
def configure_grid(self, grid):
|
||||||
|
g = grid
|
||||||
|
super().configure_grid(g)
|
||||||
|
model = self.app.model
|
||||||
|
|
||||||
|
# name
|
||||||
|
g.set_link("name")
|
||||||
|
|
||||||
|
# animal_type
|
||||||
|
g.set_joiner("animal_type", lambda q: q.join(model.AnimalType))
|
||||||
|
g.set_sorter("animal_type", model.AnimalType.name)
|
||||||
|
g.set_filter("animal_type", model.AnimalType.name, label="Animal Type Name")
|
||||||
|
|
||||||
|
def configure_form(self, form):
|
||||||
|
f = form
|
||||||
|
super().configure_form(f)
|
||||||
|
animal = form.model_instance
|
||||||
|
|
||||||
|
# animal_type
|
||||||
|
f.set_node("animal_type", AnimalTypeRef(self.request))
|
||||||
|
|
||||||
|
# notes
|
||||||
|
f.set_widget("notes", "notes")
|
||||||
|
|
||||||
|
# image
|
||||||
|
if animal.image_url:
|
||||||
|
f.set_widget("image", ImageWidget("animal image"))
|
||||||
|
f.set_default("image", animal.image_url)
|
||||||
|
|
||||||
|
def get_farmos_url(self, animal):
|
||||||
|
return self.app.get_farmos_url(f"/asset/{animal.drupal_id}")
|
||||||
|
|
||||||
|
def get_xref_buttons(self, animal):
|
||||||
|
buttons = super().get_xref_buttons(animal)
|
||||||
|
|
||||||
|
if animal.farmos_uuid:
|
||||||
|
buttons.append(
|
||||||
|
self.make_button(
|
||||||
|
"View farmOS record",
|
||||||
|
primary=True,
|
||||||
|
url=self.request.route_url(
|
||||||
|
"farmos_animals.view", uuid=animal.farmos_uuid
|
||||||
|
),
|
||||||
|
icon_left="eye",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return buttons
|
||||||
|
|
||||||
|
|
||||||
|
def defaults(config, **kwargs):
|
||||||
|
base = globals()
|
||||||
|
|
||||||
|
AnimalView = kwargs.get("AnimalView", base["AnimalView"])
|
||||||
|
AnimalView.defaults(config)
|
||||||
|
|
||||||
|
|
||||||
|
def includeme(config):
|
||||||
|
defaults(config)
|
||||||
90
src/wuttafarm/web/views/asset_types.py
Normal file
90
src/wuttafarm/web/views/asset_types.py
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
# -*- 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 Asset Types
|
||||||
|
"""
|
||||||
|
|
||||||
|
from wuttafarm.db.model.assets import AssetType
|
||||||
|
from wuttafarm.web.views import WuttaFarmMasterView
|
||||||
|
|
||||||
|
|
||||||
|
class AssetTypeView(WuttaFarmMasterView):
|
||||||
|
"""
|
||||||
|
Master view for Asset Types
|
||||||
|
"""
|
||||||
|
|
||||||
|
model_class = AssetType
|
||||||
|
route_prefix = "asset_types"
|
||||||
|
url_prefix = "/asset-types"
|
||||||
|
|
||||||
|
grid_columns = [
|
||||||
|
"name",
|
||||||
|
"description",
|
||||||
|
]
|
||||||
|
|
||||||
|
sort_defaults = "name"
|
||||||
|
|
||||||
|
filter_defaults = {
|
||||||
|
"name": {"active": True, "verb": "contains"},
|
||||||
|
}
|
||||||
|
|
||||||
|
form_fields = [
|
||||||
|
"name",
|
||||||
|
"description",
|
||||||
|
"farmos_uuid",
|
||||||
|
"drupal_id",
|
||||||
|
]
|
||||||
|
|
||||||
|
def configure_grid(self, grid):
|
||||||
|
g = grid
|
||||||
|
super().configure_grid(g)
|
||||||
|
|
||||||
|
# name
|
||||||
|
g.set_link("name")
|
||||||
|
|
||||||
|
def get_xref_buttons(self, asset_type):
|
||||||
|
buttons = super().get_xref_buttons(asset_type)
|
||||||
|
|
||||||
|
if asset_type.farmos_uuid:
|
||||||
|
buttons.append(
|
||||||
|
self.make_button(
|
||||||
|
"View farmOS record",
|
||||||
|
primary=True,
|
||||||
|
url=self.request.route_url(
|
||||||
|
"farmos_asset_types.view", uuid=asset_type.farmos_uuid
|
||||||
|
),
|
||||||
|
icon_left="eye",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return buttons
|
||||||
|
|
||||||
|
|
||||||
|
def defaults(config, **kwargs):
|
||||||
|
base = globals()
|
||||||
|
|
||||||
|
AssetTypeView = kwargs.get("AssetTypeView", base["AssetTypeView"])
|
||||||
|
AssetTypeView.defaults(config)
|
||||||
|
|
||||||
|
|
||||||
|
def includeme(config):
|
||||||
|
defaults(config)
|
||||||
|
|
@ -45,9 +45,24 @@ class CommonView(base.CommonView):
|
||||||
farm_viewer = auth.get_role_farm_viewer(session)
|
farm_viewer = auth.get_role_farm_viewer(session)
|
||||||
farm_viewer.notes = "this is meant to mirror the corresponding role in farmOS"
|
farm_viewer.notes = "this is meant to mirror the corresponding role in farmOS"
|
||||||
|
|
||||||
|
# create system user to represent farmOS
|
||||||
|
auth.make_user(session, username="farmos", prevent_edit=True)
|
||||||
|
|
||||||
site_admin = session.query(model.Role).filter_by(name="Site Admin").first()
|
site_admin = session.query(model.Role).filter_by(name="Site Admin").first()
|
||||||
if site_admin:
|
if site_admin:
|
||||||
site_admin_perms = [
|
site_admin_perms = [
|
||||||
|
"activity_logs.list",
|
||||||
|
"activity_logs.view",
|
||||||
|
"activity_logs.versions",
|
||||||
|
"animal_types.list",
|
||||||
|
"animal_types.view",
|
||||||
|
"animal_types.versions",
|
||||||
|
"animals.list",
|
||||||
|
"animals.view",
|
||||||
|
"animals.versions",
|
||||||
|
"asset_types.list",
|
||||||
|
"asset_types.view",
|
||||||
|
"asset_types.versions",
|
||||||
"farmos_animal_types.list",
|
"farmos_animal_types.list",
|
||||||
"farmos_animal_types.view",
|
"farmos_animal_types.view",
|
||||||
"farmos_animals.list",
|
"farmos_animals.list",
|
||||||
|
|
@ -70,6 +85,24 @@ class CommonView(base.CommonView):
|
||||||
"farmos_structures.view",
|
"farmos_structures.view",
|
||||||
"farmos_users.list",
|
"farmos_users.list",
|
||||||
"farmos_users.view",
|
"farmos_users.view",
|
||||||
|
"groups.list",
|
||||||
|
"groups.view",
|
||||||
|
"groups.versions",
|
||||||
|
"land_assets.list",
|
||||||
|
"land_assets.view",
|
||||||
|
"land_assets.versions",
|
||||||
|
"land_types.list",
|
||||||
|
"land_types.view",
|
||||||
|
"land_types.versions",
|
||||||
|
"log_types.list",
|
||||||
|
"log_types.view",
|
||||||
|
"log_types.versions",
|
||||||
|
"structure_types.list",
|
||||||
|
"structure_types.view",
|
||||||
|
"structure_types.versions",
|
||||||
|
"structures.list",
|
||||||
|
"structures.view",
|
||||||
|
"structures.versions",
|
||||||
]
|
]
|
||||||
for perm in site_admin_perms:
|
for perm in site_admin_perms:
|
||||||
auth.grant_permission(site_admin, perm)
|
auth.grant_permission(site_admin, perm)
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,7 @@ class AnimalTypeView(FarmOSMasterView):
|
||||||
animal_type = self.farmos_client.resource.get_id(
|
animal_type = self.farmos_client.resource.get_id(
|
||||||
"taxonomy_term", "animal_type", self.request.matchdict["uuid"]
|
"taxonomy_term", "animal_type", self.request.matchdict["uuid"]
|
||||||
)
|
)
|
||||||
|
self.raw_json = animal_type
|
||||||
return self.normalize_animal_type(animal_type["data"])
|
return self.normalize_animal_type(animal_type["data"])
|
||||||
|
|
||||||
def get_instance_title(self, animal_type):
|
def get_instance_title(self, animal_type):
|
||||||
|
|
@ -95,7 +96,7 @@ class AnimalTypeView(FarmOSMasterView):
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"uuid": animal_type["id"],
|
"uuid": animal_type["id"],
|
||||||
"drupal_internal_id": animal_type["attributes"]["drupal_internal__tid"],
|
"drupal_id": animal_type["attributes"]["drupal_internal__tid"],
|
||||||
"name": animal_type["attributes"]["name"],
|
"name": animal_type["attributes"]["name"],
|
||||||
"description": description or colander.null,
|
"description": description or colander.null,
|
||||||
"changed": changed,
|
"changed": changed,
|
||||||
|
|
@ -112,18 +113,39 @@ class AnimalTypeView(FarmOSMasterView):
|
||||||
f.set_node("changed", WuttaDateTime())
|
f.set_node("changed", WuttaDateTime())
|
||||||
|
|
||||||
def get_xref_buttons(self, animal_type):
|
def get_xref_buttons(self, animal_type):
|
||||||
return [
|
model = self.app.model
|
||||||
|
session = self.Session()
|
||||||
|
|
||||||
|
buttons = [
|
||||||
self.make_button(
|
self.make_button(
|
||||||
"View in farmOS",
|
"View in farmOS",
|
||||||
primary=True,
|
primary=True,
|
||||||
url=self.app.get_farmos_url(
|
url=self.app.get_farmos_url(
|
||||||
f"/taxonomy/term/{animal_type['drupal_internal_id']}"
|
f"/taxonomy/term/{animal_type['drupal_id']}"
|
||||||
),
|
),
|
||||||
target="_blank",
|
target="_blank",
|
||||||
icon_left="external-link-alt",
|
icon_left="external-link-alt",
|
||||||
),
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if wf_animal_type := (
|
||||||
|
session.query(model.AnimalType)
|
||||||
|
.filter(model.AnimalType.farmos_uuid == animal_type["uuid"])
|
||||||
|
.first()
|
||||||
|
):
|
||||||
|
buttons.append(
|
||||||
|
self.make_button(
|
||||||
|
f"View {self.app.get_title()} record",
|
||||||
|
primary=True,
|
||||||
|
url=self.request.route_url(
|
||||||
|
"animal_types.view", uuid=wf_animal_type.uuid
|
||||||
|
),
|
||||||
|
icon_left="eye",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return buttons
|
||||||
|
|
||||||
|
|
||||||
def defaults(config, **kwargs):
|
def defaults(config, **kwargs):
|
||||||
base = globals()
|
base = globals()
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,10 @@ import datetime
|
||||||
|
|
||||||
import colander
|
import colander
|
||||||
|
|
||||||
from wuttafarm.web.views.farmos import FarmOSMasterView
|
from wuttaweb.forms.schema import WuttaDateTime
|
||||||
|
from wuttaweb.forms.widgets import WuttaDateTimeWidget
|
||||||
|
|
||||||
|
from wuttafarm.web.views.farmos import FarmOSMasterView
|
||||||
from wuttafarm.web.forms.schema import UsersType, AnimalTypeType, StructureType
|
from wuttafarm.web.forms.schema import UsersType, AnimalTypeType, StructureType
|
||||||
from wuttafarm.web.forms.widgets import ImageWidget
|
from wuttafarm.web.forms.widgets import ImageWidget
|
||||||
|
|
||||||
|
|
@ -99,6 +101,7 @@ class AnimalView(FarmOSMasterView):
|
||||||
animal = self.farmos_client.resource.get_id(
|
animal = self.farmos_client.resource.get_id(
|
||||||
"asset", "animal", self.request.matchdict["uuid"]
|
"asset", "animal", self.request.matchdict["uuid"]
|
||||||
)
|
)
|
||||||
|
self.raw_json = animal
|
||||||
|
|
||||||
# instance data
|
# instance data
|
||||||
data = self.normalize_animal(animal["data"])
|
data = self.normalize_animal(animal["data"])
|
||||||
|
|
@ -172,7 +175,7 @@ class AnimalView(FarmOSMasterView):
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"uuid": animal["id"],
|
"uuid": animal["id"],
|
||||||
"drupal_internal_id": animal["attributes"]["drupal_internal__id"],
|
"drupal_id": animal["attributes"]["drupal_internal__id"],
|
||||||
"name": animal["attributes"]["name"],
|
"name": animal["attributes"]["name"],
|
||||||
"birthdate": birthdate,
|
"birthdate": birthdate,
|
||||||
"sex": animal["attributes"]["sex"],
|
"sex": animal["attributes"]["sex"],
|
||||||
|
|
@ -190,6 +193,10 @@ class AnimalView(FarmOSMasterView):
|
||||||
# animal_type
|
# animal_type
|
||||||
f.set_node("animal_type", AnimalTypeType(self.request))
|
f.set_node("animal_type", AnimalTypeType(self.request))
|
||||||
|
|
||||||
|
# birthdate
|
||||||
|
f.set_node("birthdate", WuttaDateTime())
|
||||||
|
f.set_widget("birthdate", WuttaDateTimeWidget(self.request))
|
||||||
|
|
||||||
# is_castrated
|
# is_castrated
|
||||||
f.set_node("is_castrated", colander.Boolean())
|
f.set_node("is_castrated", colander.Boolean())
|
||||||
|
|
||||||
|
|
@ -208,16 +215,35 @@ class AnimalView(FarmOSMasterView):
|
||||||
f.set_default("image", url)
|
f.set_default("image", url)
|
||||||
|
|
||||||
def get_xref_buttons(self, animal):
|
def get_xref_buttons(self, animal):
|
||||||
return [
|
model = self.app.model
|
||||||
|
session = self.Session()
|
||||||
|
|
||||||
|
buttons = [
|
||||||
self.make_button(
|
self.make_button(
|
||||||
"View in farmOS",
|
"View in farmOS",
|
||||||
primary=True,
|
primary=True,
|
||||||
url=self.app.get_farmos_url(f"/asset/{animal['drupal_internal_id']}"),
|
url=self.app.get_farmos_url(f"/asset/{animal['drupal_id']}"),
|
||||||
target="_blank",
|
target="_blank",
|
||||||
icon_left="external-link-alt",
|
icon_left="external-link-alt",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if wf_animal := (
|
||||||
|
session.query(model.Animal)
|
||||||
|
.filter(model.Animal.farmos_uuid == animal["uuid"])
|
||||||
|
.first()
|
||||||
|
):
|
||||||
|
buttons.append(
|
||||||
|
self.make_button(
|
||||||
|
f"View {self.app.get_title()} record",
|
||||||
|
primary=True,
|
||||||
|
url=self.request.route_url("animals.view", uuid=wf_animal.uuid),
|
||||||
|
icon_left="eye",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return buttons
|
||||||
|
|
||||||
|
|
||||||
def defaults(config, **kwargs):
|
def defaults(config, **kwargs):
|
||||||
base = globals()
|
base = globals()
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ class AssetTypeView(FarmOSMasterView):
|
||||||
asset_type = self.farmos_client.resource.get_id(
|
asset_type = self.farmos_client.resource.get_id(
|
||||||
"asset_type", "asset_type", self.request.matchdict["uuid"]
|
"asset_type", "asset_type", self.request.matchdict["uuid"]
|
||||||
)
|
)
|
||||||
|
self.raw_json = asset_type
|
||||||
return self.normalize_asset_type(asset_type["data"])
|
return self.normalize_asset_type(asset_type["data"])
|
||||||
|
|
||||||
def get_instance_title(self, asset_type):
|
def get_instance_title(self, asset_type):
|
||||||
|
|
@ -77,7 +78,7 @@ class AssetTypeView(FarmOSMasterView):
|
||||||
def normalize_asset_type(self, asset_type):
|
def normalize_asset_type(self, asset_type):
|
||||||
return {
|
return {
|
||||||
"uuid": asset_type["id"],
|
"uuid": asset_type["id"],
|
||||||
"drupal_internal_id": asset_type["attributes"]["drupal_internal__id"],
|
"drupal_id": asset_type["attributes"]["drupal_internal__id"],
|
||||||
"label": asset_type["attributes"]["label"],
|
"label": asset_type["attributes"]["label"],
|
||||||
"description": asset_type["attributes"]["description"],
|
"description": asset_type["attributes"]["description"],
|
||||||
}
|
}
|
||||||
|
|
@ -89,6 +90,29 @@ class AssetTypeView(FarmOSMasterView):
|
||||||
# description
|
# description
|
||||||
f.set_widget("description", "notes")
|
f.set_widget("description", "notes")
|
||||||
|
|
||||||
|
def get_xref_buttons(self, asset_type):
|
||||||
|
model = self.app.model
|
||||||
|
session = self.Session()
|
||||||
|
buttons = []
|
||||||
|
|
||||||
|
if wf_asset_type := (
|
||||||
|
session.query(model.AssetType)
|
||||||
|
.filter(model.AssetType.farmos_uuid == asset_type["uuid"])
|
||||||
|
.first()
|
||||||
|
):
|
||||||
|
buttons.append(
|
||||||
|
self.make_button(
|
||||||
|
f"View {self.app.get_title()} record",
|
||||||
|
primary=True,
|
||||||
|
url=self.request.route_url(
|
||||||
|
"asset_types.view", uuid=wf_asset_type.uuid
|
||||||
|
),
|
||||||
|
icon_left="eye",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return buttons
|
||||||
|
|
||||||
|
|
||||||
def defaults(config, **kwargs):
|
def defaults(config, **kwargs):
|
||||||
base = globals()
|
base = globals()
|
||||||
|
|
|
||||||
|
|
@ -88,11 +88,10 @@ class GroupView(FarmOSMasterView):
|
||||||
g.set_renderer("changed", "datetime")
|
g.set_renderer("changed", "datetime")
|
||||||
|
|
||||||
def get_instance(self):
|
def get_instance(self):
|
||||||
|
|
||||||
group = self.farmos_client.resource.get_id(
|
group = self.farmos_client.resource.get_id(
|
||||||
"asset", "group", self.request.matchdict["uuid"]
|
"asset", "group", self.request.matchdict["uuid"]
|
||||||
)
|
)
|
||||||
|
self.raw_json = group
|
||||||
return self.normalize_group(group["data"])
|
return self.normalize_group(group["data"])
|
||||||
|
|
||||||
def get_instance_title(self, group):
|
def get_instance_title(self, group):
|
||||||
|
|
@ -110,7 +109,7 @@ class GroupView(FarmOSMasterView):
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"uuid": group["id"],
|
"uuid": group["id"],
|
||||||
"drupal_internal_id": group["attributes"]["drupal_internal__id"],
|
"drupal_id": group["attributes"]["drupal_internal__id"],
|
||||||
"name": group["attributes"]["name"],
|
"name": group["attributes"]["name"],
|
||||||
"created": created,
|
"created": created,
|
||||||
"changed": changed,
|
"changed": changed,
|
||||||
|
|
@ -142,16 +141,35 @@ class GroupView(FarmOSMasterView):
|
||||||
f.set_widget("changed", WuttaDateTimeWidget(self.request))
|
f.set_widget("changed", WuttaDateTimeWidget(self.request))
|
||||||
|
|
||||||
def get_xref_buttons(self, group):
|
def get_xref_buttons(self, group):
|
||||||
return [
|
model = self.app.model
|
||||||
|
session = self.Session()
|
||||||
|
|
||||||
|
buttons = [
|
||||||
self.make_button(
|
self.make_button(
|
||||||
"View in farmOS",
|
"View in farmOS",
|
||||||
primary=True,
|
primary=True,
|
||||||
url=self.app.get_farmos_url(f"/asset/{group['drupal_internal_id']}"),
|
url=self.app.get_farmos_url(f"/asset/{group['drupal_id']}"),
|
||||||
target="_blank",
|
target="_blank",
|
||||||
icon_left="external-link-alt",
|
icon_left="external-link-alt",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if wf_group := (
|
||||||
|
session.query(model.Group)
|
||||||
|
.filter(model.Group.farmos_uuid == group["uuid"])
|
||||||
|
.first()
|
||||||
|
):
|
||||||
|
buttons.append(
|
||||||
|
self.make_button(
|
||||||
|
f"View {self.app.get_title()} record",
|
||||||
|
primary=True,
|
||||||
|
url=self.request.route_url("groups.view", uuid=wf_group.uuid),
|
||||||
|
icon_left="eye",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return buttons
|
||||||
|
|
||||||
|
|
||||||
def defaults(config, **kwargs):
|
def defaults(config, **kwargs):
|
||||||
base = globals()
|
base = globals()
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ class LandAssetView(FarmOSMasterView):
|
||||||
|
|
||||||
grid_columns = [
|
grid_columns = [
|
||||||
"name",
|
"name",
|
||||||
|
"land_type",
|
||||||
"is_fixed",
|
"is_fixed",
|
||||||
"is_location",
|
"is_location",
|
||||||
"status",
|
"status",
|
||||||
|
|
@ -59,6 +60,7 @@ class LandAssetView(FarmOSMasterView):
|
||||||
|
|
||||||
form_fields = [
|
form_fields = [
|
||||||
"name",
|
"name",
|
||||||
|
"land_type",
|
||||||
"is_fixed",
|
"is_fixed",
|
||||||
"is_location",
|
"is_location",
|
||||||
"status",
|
"status",
|
||||||
|
|
@ -95,6 +97,7 @@ class LandAssetView(FarmOSMasterView):
|
||||||
land_asset = self.farmos_client.resource.get_id(
|
land_asset = self.farmos_client.resource.get_id(
|
||||||
"asset", "land", self.request.matchdict["uuid"]
|
"asset", "land", self.request.matchdict["uuid"]
|
||||||
)
|
)
|
||||||
|
self.raw_json = land_asset
|
||||||
return self.normalize_land_asset(land_asset["data"])
|
return self.normalize_land_asset(land_asset["data"])
|
||||||
|
|
||||||
def get_instance_title(self, land_asset):
|
def get_instance_title(self, land_asset):
|
||||||
|
|
@ -115,8 +118,9 @@ class LandAssetView(FarmOSMasterView):
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"uuid": land["id"],
|
"uuid": land["id"],
|
||||||
"drupal_internal_id": land["attributes"]["drupal_internal__id"],
|
"drupal_id": land["attributes"]["drupal_internal__id"],
|
||||||
"name": land["attributes"]["name"],
|
"name": land["attributes"]["name"],
|
||||||
|
"land_type": land["attributes"]["land_type"],
|
||||||
"created": created,
|
"created": created,
|
||||||
"changed": changed,
|
"changed": changed,
|
||||||
"is_fixed": land["attributes"]["is_fixed"],
|
"is_fixed": land["attributes"]["is_fixed"],
|
||||||
|
|
@ -151,12 +155,42 @@ class LandAssetView(FarmOSMasterView):
|
||||||
self.make_button(
|
self.make_button(
|
||||||
"View in farmOS",
|
"View in farmOS",
|
||||||
primary=True,
|
primary=True,
|
||||||
url=self.app.get_farmos_url(f"/asset/{land['drupal_internal_id']}"),
|
url=self.app.get_farmos_url(f"/asset/{land['drupal_id']}"),
|
||||||
target="_blank",
|
target="_blank",
|
||||||
icon_left="external-link-alt",
|
icon_left="external-link-alt",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def get_xref_buttons(self, land):
|
||||||
|
model = self.app.model
|
||||||
|
session = self.Session()
|
||||||
|
|
||||||
|
buttons = [
|
||||||
|
self.make_button(
|
||||||
|
"View in farmOS",
|
||||||
|
primary=True,
|
||||||
|
url=self.app.get_farmos_url(f"/asset/{land['drupal_id']}"),
|
||||||
|
target="_blank",
|
||||||
|
icon_left="external-link-alt",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
if wf_land := (
|
||||||
|
session.query(model.LandAsset)
|
||||||
|
.filter(model.LandAsset.farmos_uuid == land["uuid"])
|
||||||
|
.first()
|
||||||
|
):
|
||||||
|
buttons.append(
|
||||||
|
self.make_button(
|
||||||
|
f"View {self.app.get_title()} record",
|
||||||
|
primary=True,
|
||||||
|
url=self.request.route_url("land_assets.view", uuid=wf_land.uuid),
|
||||||
|
icon_left="eye",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return buttons
|
||||||
|
|
||||||
|
|
||||||
def defaults(config, **kwargs):
|
def defaults(config, **kwargs):
|
||||||
base = globals()
|
base = globals()
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,7 @@ class LandTypeView(FarmOSMasterView):
|
||||||
land_type = self.farmos_client.resource.get_id(
|
land_type = self.farmos_client.resource.get_id(
|
||||||
"land_type", "land_type", self.request.matchdict["uuid"]
|
"land_type", "land_type", self.request.matchdict["uuid"]
|
||||||
)
|
)
|
||||||
|
self.raw_json = land_type
|
||||||
return self.normalize_land_type(land_type["data"])
|
return self.normalize_land_type(land_type["data"])
|
||||||
|
|
||||||
def get_instance_title(self, land_type):
|
def get_instance_title(self, land_type):
|
||||||
|
|
@ -72,10 +73,33 @@ class LandTypeView(FarmOSMasterView):
|
||||||
def normalize_land_type(self, land_type):
|
def normalize_land_type(self, land_type):
|
||||||
return {
|
return {
|
||||||
"uuid": land_type["id"],
|
"uuid": land_type["id"],
|
||||||
"drupal_internal_id": land_type["attributes"]["drupal_internal__id"],
|
"drupal_id": land_type["attributes"]["drupal_internal__id"],
|
||||||
"label": land_type["attributes"]["label"],
|
"label": land_type["attributes"]["label"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def get_xref_buttons(self, land_type):
|
||||||
|
model = self.app.model
|
||||||
|
session = self.Session()
|
||||||
|
buttons = []
|
||||||
|
|
||||||
|
if wf_land_type := (
|
||||||
|
session.query(model.LandType)
|
||||||
|
.filter(model.LandType.farmos_uuid == land_type["uuid"])
|
||||||
|
.first()
|
||||||
|
):
|
||||||
|
buttons.append(
|
||||||
|
self.make_button(
|
||||||
|
f"View {self.app.get_title()} record",
|
||||||
|
primary=True,
|
||||||
|
url=self.request.route_url(
|
||||||
|
"land_types.view", uuid=wf_land_type.uuid
|
||||||
|
),
|
||||||
|
icon_left="eye",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return buttons
|
||||||
|
|
||||||
|
|
||||||
def defaults(config, **kwargs):
|
def defaults(config, **kwargs):
|
||||||
base = globals()
|
base = globals()
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,7 @@ class LogTypeView(FarmOSMasterView):
|
||||||
log_type = self.farmos_client.resource.get_id(
|
log_type = self.farmos_client.resource.get_id(
|
||||||
"log_type", "log_type", self.request.matchdict["uuid"]
|
"log_type", "log_type", self.request.matchdict["uuid"]
|
||||||
)
|
)
|
||||||
|
self.raw_json = log_type
|
||||||
return self.normalize_log_type(log_type["data"])
|
return self.normalize_log_type(log_type["data"])
|
||||||
|
|
||||||
def get_instance_title(self, log_type):
|
def get_instance_title(self, log_type):
|
||||||
|
|
@ -74,7 +75,7 @@ class LogTypeView(FarmOSMasterView):
|
||||||
def normalize_log_type(self, log_type):
|
def normalize_log_type(self, log_type):
|
||||||
return {
|
return {
|
||||||
"uuid": log_type["id"],
|
"uuid": log_type["id"],
|
||||||
"drupal_internal_id": log_type["attributes"]["drupal_internal__id"],
|
"drupal_id": log_type["attributes"]["drupal_internal__id"],
|
||||||
"label": log_type["attributes"]["label"],
|
"label": log_type["attributes"]["label"],
|
||||||
"description": log_type["attributes"]["description"],
|
"description": log_type["attributes"]["description"],
|
||||||
}
|
}
|
||||||
|
|
@ -86,6 +87,27 @@ class LogTypeView(FarmOSMasterView):
|
||||||
# description
|
# description
|
||||||
f.set_widget("description", "notes")
|
f.set_widget("description", "notes")
|
||||||
|
|
||||||
|
def get_xref_buttons(self, log_type):
|
||||||
|
model = self.app.model
|
||||||
|
session = self.Session()
|
||||||
|
buttons = []
|
||||||
|
|
||||||
|
if wf_log_type := (
|
||||||
|
session.query(model.LogType)
|
||||||
|
.filter(model.LogType.farmos_uuid == log_type["uuid"])
|
||||||
|
.first()
|
||||||
|
):
|
||||||
|
buttons.append(
|
||||||
|
self.make_button(
|
||||||
|
f"View {self.app.get_title()} record",
|
||||||
|
primary=True,
|
||||||
|
url=self.request.route_url("log_types.view", uuid=wf_log_type.uuid),
|
||||||
|
icon_left="eye",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return buttons
|
||||||
|
|
||||||
|
|
||||||
def defaults(config, **kwargs):
|
def defaults(config, **kwargs):
|
||||||
base = globals()
|
base = globals()
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,7 @@ class ActivityLogView(FarmOSMasterView):
|
||||||
|
|
||||||
def get_instance(self):
|
def get_instance(self):
|
||||||
log = self.farmos_client.log.get_id("activity", self.request.matchdict["uuid"])
|
log = self.farmos_client.log.get_id("activity", self.request.matchdict["uuid"])
|
||||||
|
self.raw_json = log
|
||||||
return self.normalize_log(log["data"])
|
return self.normalize_log(log["data"])
|
||||||
|
|
||||||
def get_instance_title(self, log):
|
def get_instance_title(self, log):
|
||||||
|
|
@ -95,7 +96,7 @@ class ActivityLogView(FarmOSMasterView):
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"uuid": log["id"],
|
"uuid": log["id"],
|
||||||
"drupal_internal_id": log["attributes"]["drupal_internal__id"],
|
"drupal_id": log["attributes"]["drupal_internal__id"],
|
||||||
"name": log["attributes"]["name"],
|
"name": log["attributes"]["name"],
|
||||||
"timestamp": timestamp,
|
"timestamp": timestamp,
|
||||||
"status": log["attributes"]["status"],
|
"status": log["attributes"]["status"],
|
||||||
|
|
@ -114,16 +115,35 @@ class ActivityLogView(FarmOSMasterView):
|
||||||
f.set_widget("notes", "notes")
|
f.set_widget("notes", "notes")
|
||||||
|
|
||||||
def get_xref_buttons(self, log):
|
def get_xref_buttons(self, log):
|
||||||
return [
|
model = self.app.model
|
||||||
|
session = self.Session()
|
||||||
|
|
||||||
|
buttons = [
|
||||||
self.make_button(
|
self.make_button(
|
||||||
"View in farmOS",
|
"View in farmOS",
|
||||||
primary=True,
|
primary=True,
|
||||||
url=self.app.get_farmos_url(f"/log/{log['drupal_internal_id']}"),
|
url=self.app.get_farmos_url(f"/log/{log['drupal_id']}"),
|
||||||
target="_blank",
|
target="_blank",
|
||||||
icon_left="external-link-alt",
|
icon_left="external-link-alt",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if wf_log := (
|
||||||
|
session.query(model.ActivityLog)
|
||||||
|
.filter(model.ActivityLog.farmos_uuid == log["uuid"])
|
||||||
|
.first()
|
||||||
|
):
|
||||||
|
buttons.append(
|
||||||
|
self.make_button(
|
||||||
|
f"View {self.app.get_title()} record",
|
||||||
|
primary=True,
|
||||||
|
url=self.request.route_url("activity_logs.view", uuid=wf_log.uuid),
|
||||||
|
icon_left="eye",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return buttons
|
||||||
|
|
||||||
|
|
||||||
def defaults(config, **kwargs):
|
def defaults(config, **kwargs):
|
||||||
base = globals()
|
base = globals()
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,10 @@
|
||||||
Base class for farmOS master views
|
Base class for farmOS master views
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
import markdown
|
||||||
|
|
||||||
from wuttaweb.views import MasterView
|
from wuttaweb.views import MasterView
|
||||||
|
|
||||||
from wuttafarm.web.util import save_farmos_oauth2_token
|
from wuttafarm.web.util import save_farmos_oauth2_token
|
||||||
|
|
@ -54,6 +58,7 @@ class FarmOSMasterView(MasterView):
|
||||||
def __init__(self, request, context=None):
|
def __init__(self, request, context=None):
|
||||||
super().__init__(request, context=context)
|
super().__init__(request, context=context)
|
||||||
self.farmos_client = self.get_farmos_client()
|
self.farmos_client = self.get_farmos_client()
|
||||||
|
self.raw_json = None
|
||||||
|
|
||||||
def get_farmos_client(self):
|
def get_farmos_client(self):
|
||||||
token = self.request.session.get("farmos.oauth2.token")
|
token = self.request.session.get("farmos.oauth2.token")
|
||||||
|
|
@ -71,9 +76,26 @@ class FarmOSMasterView(MasterView):
|
||||||
|
|
||||||
return self.app.get_farmos_client(token=token, token_updater=token_updater)
|
return self.app.get_farmos_client(token=token, token_updater=token_updater)
|
||||||
|
|
||||||
|
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 get_template_context(self, context):
|
def get_template_context(self, context):
|
||||||
|
|
||||||
if self.listing and self.farmos_refurl_path:
|
if self.listing and self.farmos_refurl_path:
|
||||||
context["farmos_refurl"] = self.app.get_farmos_url(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
|
return context
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,7 @@ class StructureTypeView(FarmOSMasterView):
|
||||||
structure_type = self.farmos_client.resource.get_id(
|
structure_type = self.farmos_client.resource.get_id(
|
||||||
"structure_type", "structure_type", self.request.matchdict["uuid"]
|
"structure_type", "structure_type", self.request.matchdict["uuid"]
|
||||||
)
|
)
|
||||||
|
self.raw_json = structure_type
|
||||||
return self.normalize_structure_type(structure_type["data"])
|
return self.normalize_structure_type(structure_type["data"])
|
||||||
|
|
||||||
def get_instance_title(self, structure_type):
|
def get_instance_title(self, structure_type):
|
||||||
|
|
@ -74,10 +75,33 @@ class StructureTypeView(FarmOSMasterView):
|
||||||
def normalize_structure_type(self, structure_type):
|
def normalize_structure_type(self, structure_type):
|
||||||
return {
|
return {
|
||||||
"uuid": structure_type["id"],
|
"uuid": structure_type["id"],
|
||||||
"drupal_internal_id": structure_type["attributes"]["drupal_internal__id"],
|
"drupal_id": structure_type["attributes"]["drupal_internal__id"],
|
||||||
"label": structure_type["attributes"]["label"],
|
"label": structure_type["attributes"]["label"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def get_xref_buttons(self, structure_type):
|
||||||
|
model = self.app.model
|
||||||
|
session = self.Session()
|
||||||
|
buttons = []
|
||||||
|
|
||||||
|
if wf_structure_type := (
|
||||||
|
session.query(model.StructureType)
|
||||||
|
.filter(model.StructureType.farmos_uuid == structure_type["uuid"])
|
||||||
|
.first()
|
||||||
|
):
|
||||||
|
buttons.append(
|
||||||
|
self.make_button(
|
||||||
|
f"View {self.app.get_title()} record",
|
||||||
|
primary=True,
|
||||||
|
url=self.request.route_url(
|
||||||
|
"structure_types.view", uuid=wf_structure_type.uuid
|
||||||
|
),
|
||||||
|
icon_left="eye",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return buttons
|
||||||
|
|
||||||
|
|
||||||
def defaults(config, **kwargs):
|
def defaults(config, **kwargs):
|
||||||
base = globals()
|
base = globals()
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@ class StructureView(FarmOSMasterView):
|
||||||
structure = self.farmos_client.resource.get_id(
|
structure = self.farmos_client.resource.get_id(
|
||||||
"asset", "structure", self.request.matchdict["uuid"]
|
"asset", "structure", self.request.matchdict["uuid"]
|
||||||
)
|
)
|
||||||
|
self.raw_json = structure
|
||||||
data = self.normalize_structure(structure["data"])
|
data = self.normalize_structure(structure["data"])
|
||||||
|
|
||||||
if relationships := structure["data"].get("relationships"):
|
if relationships := structure["data"].get("relationships"):
|
||||||
|
|
@ -147,7 +147,7 @@ class StructureView(FarmOSMasterView):
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"uuid": structure["id"],
|
"uuid": structure["id"],
|
||||||
"drupal_internal_id": structure["attributes"]["drupal_internal__id"],
|
"drupal_id": structure["attributes"]["drupal_internal__id"],
|
||||||
"name": structure["attributes"]["name"],
|
"name": structure["attributes"]["name"],
|
||||||
"structure_type": structure["attributes"]["structure_type"],
|
"structure_type": structure["attributes"]["structure_type"],
|
||||||
"is_fixed": structure["attributes"]["is_fixed"],
|
"is_fixed": structure["attributes"]["is_fixed"],
|
||||||
|
|
@ -186,17 +186,37 @@ class StructureView(FarmOSMasterView):
|
||||||
f.set_default("image", url)
|
f.set_default("image", url)
|
||||||
|
|
||||||
def get_xref_buttons(self, structure):
|
def get_xref_buttons(self, structure):
|
||||||
drupal_id = structure["drupal_internal_id"]
|
model = self.app.model
|
||||||
return [
|
session = self.Session()
|
||||||
|
|
||||||
|
buttons = [
|
||||||
self.make_button(
|
self.make_button(
|
||||||
"View in farmOS",
|
"View in farmOS",
|
||||||
primary=True,
|
primary=True,
|
||||||
url=self.app.get_farmos_url(f"/asset/{drupal_id}"),
|
url=self.app.get_farmos_url(f"/asset/{structure['drupal_id']}"),
|
||||||
target="_blank",
|
target="_blank",
|
||||||
icon_left="external-link-alt",
|
icon_left="external-link-alt",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if wf_structure := (
|
||||||
|
session.query(model.Structure)
|
||||||
|
.filter(model.Structure.farmos_uuid == structure["uuid"])
|
||||||
|
.first()
|
||||||
|
):
|
||||||
|
buttons.append(
|
||||||
|
self.make_button(
|
||||||
|
f"View {self.app.get_title()} record",
|
||||||
|
primary=True,
|
||||||
|
url=self.request.route_url(
|
||||||
|
"structures.view", uuid=wf_structure.uuid
|
||||||
|
),
|
||||||
|
icon_left="eye",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return buttons
|
||||||
|
|
||||||
|
|
||||||
def defaults(config, **kwargs):
|
def defaults(config, **kwargs):
|
||||||
base = globals()
|
base = globals()
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,7 @@ class UserView(FarmOSMasterView):
|
||||||
user = self.farmos_client.resource.get_id(
|
user = self.farmos_client.resource.get_id(
|
||||||
"user", "user", self.request.matchdict["uuid"]
|
"user", "user", self.request.matchdict["uuid"]
|
||||||
)
|
)
|
||||||
|
self.raw_json = user
|
||||||
return self.normalize_user(user["data"])
|
return self.normalize_user(user["data"])
|
||||||
|
|
||||||
def get_instance_title(self, user):
|
def get_instance_title(self, user):
|
||||||
|
|
@ -94,7 +95,7 @@ class UserView(FarmOSMasterView):
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"uuid": user["id"],
|
"uuid": user["id"],
|
||||||
"drupal_internal_id": user["attributes"].get("drupal_internal__uid"),
|
"drupal_id": user["attributes"].get("drupal_internal__uid"),
|
||||||
"display_name": user["attributes"]["display_name"],
|
"display_name": user["attributes"]["display_name"],
|
||||||
"name": user["attributes"].get("name") or colander.null,
|
"name": user["attributes"].get("name") or colander.null,
|
||||||
"mail": user["attributes"].get("mail") or colander.null,
|
"mail": user["attributes"].get("mail") or colander.null,
|
||||||
|
|
@ -115,17 +116,36 @@ class UserView(FarmOSMasterView):
|
||||||
f.set_node("changed", WuttaDateTime())
|
f.set_node("changed", WuttaDateTime())
|
||||||
|
|
||||||
def get_xref_buttons(self, user):
|
def get_xref_buttons(self, user):
|
||||||
if drupal_id := user["drupal_internal_id"]:
|
model = self.app.model
|
||||||
return [
|
session = self.Session()
|
||||||
|
buttons = []
|
||||||
|
|
||||||
|
if drupal_id := user["drupal_id"]:
|
||||||
|
buttons.append(
|
||||||
self.make_button(
|
self.make_button(
|
||||||
"View in farmOS",
|
"View in farmOS",
|
||||||
primary=True,
|
primary=True,
|
||||||
url=self.app.get_farmos_url(f"/user/{drupal_id}"),
|
url=self.app.get_farmos_url(f"/user/{drupal_id}"),
|
||||||
target="_blank",
|
target="_blank",
|
||||||
icon_left="external-link-alt",
|
icon_left="external-link-alt",
|
||||||
),
|
)
|
||||||
]
|
)
|
||||||
return None
|
|
||||||
|
if wf_user := (
|
||||||
|
session.query(model.WuttaFarmUser)
|
||||||
|
.filter(model.WuttaFarmUser.farmos_uuid == user["uuid"])
|
||||||
|
.first()
|
||||||
|
):
|
||||||
|
buttons.append(
|
||||||
|
self.make_button(
|
||||||
|
f"View {self.app.get_title()} record",
|
||||||
|
primary=True,
|
||||||
|
url=self.request.route_url("users.view", uuid=wf_user.uuid),
|
||||||
|
icon_left="eye",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return buttons
|
||||||
|
|
||||||
|
|
||||||
def defaults(config, **kwargs):
|
def defaults(config, **kwargs):
|
||||||
|
|
|
||||||
107
src/wuttafarm/web/views/groups.py
Normal file
107
src/wuttafarm/web/views/groups.py
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
# -*- 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 Groups
|
||||||
|
"""
|
||||||
|
|
||||||
|
from wuttafarm.db.model.groups import Group
|
||||||
|
from wuttafarm.web.views import WuttaFarmMasterView
|
||||||
|
|
||||||
|
|
||||||
|
class GroupView(WuttaFarmMasterView):
|
||||||
|
"""
|
||||||
|
Master view for Groups
|
||||||
|
"""
|
||||||
|
|
||||||
|
model_class = Group
|
||||||
|
route_prefix = "groups"
|
||||||
|
url_prefix = "/groups"
|
||||||
|
|
||||||
|
farmos_refurl_path = "/assets/group"
|
||||||
|
|
||||||
|
grid_columns = [
|
||||||
|
"name",
|
||||||
|
"is_location",
|
||||||
|
"is_fixed",
|
||||||
|
"active",
|
||||||
|
]
|
||||||
|
|
||||||
|
sort_defaults = "name"
|
||||||
|
|
||||||
|
filter_defaults = {
|
||||||
|
"name": {"active": True, "verb": "contains"},
|
||||||
|
}
|
||||||
|
|
||||||
|
form_fields = [
|
||||||
|
"name",
|
||||||
|
"is_location",
|
||||||
|
"is_fixed",
|
||||||
|
"active",
|
||||||
|
"notes",
|
||||||
|
"farmos_uuid",
|
||||||
|
"drupal_id",
|
||||||
|
]
|
||||||
|
|
||||||
|
def configure_grid(self, grid):
|
||||||
|
g = grid
|
||||||
|
super().configure_grid(g)
|
||||||
|
|
||||||
|
# name
|
||||||
|
g.set_link("name")
|
||||||
|
|
||||||
|
def configure_form(self, form):
|
||||||
|
f = form
|
||||||
|
super().configure_form(f)
|
||||||
|
|
||||||
|
# notes
|
||||||
|
f.set_widget("notes", "notes")
|
||||||
|
|
||||||
|
def get_farmos_url(self, group):
|
||||||
|
return self.app.get_farmos_url(f"/asset/{group.drupal_id}")
|
||||||
|
|
||||||
|
def get_xref_buttons(self, group):
|
||||||
|
buttons = super().get_xref_buttons(group)
|
||||||
|
|
||||||
|
if group.farmos_uuid:
|
||||||
|
buttons.append(
|
||||||
|
self.make_button(
|
||||||
|
"View farmOS record",
|
||||||
|
primary=True,
|
||||||
|
url=self.request.route_url(
|
||||||
|
"farmos_groups.view", uuid=group.farmos_uuid
|
||||||
|
),
|
||||||
|
icon_left="eye",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return buttons
|
||||||
|
|
||||||
|
|
||||||
|
def defaults(config, **kwargs):
|
||||||
|
base = globals()
|
||||||
|
|
||||||
|
GroupView = kwargs.get("GroupView", base["GroupView"])
|
||||||
|
GroupView.defaults(config)
|
||||||
|
|
||||||
|
|
||||||
|
def includeme(config):
|
||||||
|
defaults(config)
|
||||||
117
src/wuttafarm/web/views/land_assets.py
Normal file
117
src/wuttafarm/web/views/land_assets.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/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
Master view for Land Assets
|
||||||
|
"""
|
||||||
|
|
||||||
|
from wuttafarm.db.model.land import LandAsset
|
||||||
|
from wuttafarm.web.views import WuttaFarmMasterView
|
||||||
|
from wuttafarm.web.forms.schema import LandTypeRef
|
||||||
|
|
||||||
|
|
||||||
|
class LandAssetView(WuttaFarmMasterView):
|
||||||
|
"""
|
||||||
|
Master view for Land Assets
|
||||||
|
"""
|
||||||
|
|
||||||
|
model_class = LandAsset
|
||||||
|
route_prefix = "land_assets"
|
||||||
|
url_prefix = "/land-assets"
|
||||||
|
|
||||||
|
farmos_refurl_path = "/assets/land"
|
||||||
|
|
||||||
|
grid_columns = [
|
||||||
|
"name",
|
||||||
|
"land_type",
|
||||||
|
"is_location",
|
||||||
|
"is_fixed",
|
||||||
|
"notes",
|
||||||
|
"active",
|
||||||
|
]
|
||||||
|
|
||||||
|
sort_defaults = "name"
|
||||||
|
|
||||||
|
filter_defaults = {
|
||||||
|
"name": {"active": True, "verb": "contains"},
|
||||||
|
}
|
||||||
|
|
||||||
|
form_fields = [
|
||||||
|
"name",
|
||||||
|
"land_type",
|
||||||
|
"is_location",
|
||||||
|
"is_fixed",
|
||||||
|
"notes",
|
||||||
|
"active",
|
||||||
|
"farmos_uuid",
|
||||||
|
"drupal_id",
|
||||||
|
]
|
||||||
|
|
||||||
|
def configure_grid(self, grid):
|
||||||
|
g = grid
|
||||||
|
super().configure_grid(g)
|
||||||
|
model = self.app.model
|
||||||
|
|
||||||
|
# name
|
||||||
|
g.set_link("name")
|
||||||
|
|
||||||
|
# land_type
|
||||||
|
g.set_joiner("land_type", lambda q: q.join(model.LandType))
|
||||||
|
g.set_sorter("land_type", model.LandType.name)
|
||||||
|
g.set_filter("land_type", model.LandType.name, label="Land Type Name")
|
||||||
|
|
||||||
|
def configure_form(self, form):
|
||||||
|
f = form
|
||||||
|
super().configure_form(f)
|
||||||
|
|
||||||
|
# land_type
|
||||||
|
f.set_node("land_type", LandTypeRef(self.request))
|
||||||
|
|
||||||
|
def get_farmos_url(self, land):
|
||||||
|
return self.app.get_farmos_url(f"/asset/{land.drupal_id}")
|
||||||
|
|
||||||
|
def get_xref_buttons(self, land_asset):
|
||||||
|
buttons = super().get_xref_buttons(land_asset)
|
||||||
|
|
||||||
|
if land_asset.farmos_uuid:
|
||||||
|
buttons.append(
|
||||||
|
self.make_button(
|
||||||
|
"View farmOS record",
|
||||||
|
primary=True,
|
||||||
|
url=self.request.route_url(
|
||||||
|
"farmos_land_assets.view", uuid=land_asset.farmos_uuid
|
||||||
|
),
|
||||||
|
icon_left="eye",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return buttons
|
||||||
|
|
||||||
|
|
||||||
|
def defaults(config, **kwargs):
|
||||||
|
base = globals()
|
||||||
|
|
||||||
|
LandAssetView = kwargs.get("LandAssetView", base["LandAssetView"])
|
||||||
|
LandAssetView.defaults(config)
|
||||||
|
|
||||||
|
|
||||||
|
def includeme(config):
|
||||||
|
defaults(config)
|
||||||
118
src/wuttafarm/web/views/land_types.py
Normal file
118
src/wuttafarm/web/views/land_types.py
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
# -*- 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 Land Types
|
||||||
|
"""
|
||||||
|
|
||||||
|
from wuttafarm.db.model.land import LandType, LandAsset
|
||||||
|
from wuttafarm.web.views import WuttaFarmMasterView
|
||||||
|
|
||||||
|
|
||||||
|
class LandTypeView(WuttaFarmMasterView):
|
||||||
|
"""
|
||||||
|
Master view for Land Types
|
||||||
|
"""
|
||||||
|
|
||||||
|
model_class = LandType
|
||||||
|
route_prefix = "land_types"
|
||||||
|
url_prefix = "/land-types"
|
||||||
|
|
||||||
|
grid_columns = [
|
||||||
|
"name",
|
||||||
|
]
|
||||||
|
|
||||||
|
sort_defaults = "name"
|
||||||
|
|
||||||
|
filter_defaults = {
|
||||||
|
"name": {"active": True, "verb": "contains"},
|
||||||
|
}
|
||||||
|
|
||||||
|
form_fields = [
|
||||||
|
"name",
|
||||||
|
"farmos_uuid",
|
||||||
|
"drupal_id",
|
||||||
|
]
|
||||||
|
|
||||||
|
has_rows = True
|
||||||
|
row_model_class = LandAsset
|
||||||
|
rows_viewable = True
|
||||||
|
|
||||||
|
row_grid_columns = [
|
||||||
|
"name",
|
||||||
|
"is_location",
|
||||||
|
"is_fixed",
|
||||||
|
"active",
|
||||||
|
]
|
||||||
|
|
||||||
|
rows_sort_defaults = "name"
|
||||||
|
|
||||||
|
def configure_grid(self, grid):
|
||||||
|
g = grid
|
||||||
|
super().configure_grid(g)
|
||||||
|
|
||||||
|
# name
|
||||||
|
g.set_link("name")
|
||||||
|
|
||||||
|
def get_xref_buttons(self, land_type):
|
||||||
|
buttons = super().get_xref_buttons(land_type)
|
||||||
|
|
||||||
|
if land_type.farmos_uuid:
|
||||||
|
buttons.append(
|
||||||
|
self.make_button(
|
||||||
|
"View farmOS record",
|
||||||
|
primary=True,
|
||||||
|
url=self.request.route_url(
|
||||||
|
"farmos_land_types.view", uuid=land_type.farmos_uuid
|
||||||
|
),
|
||||||
|
icon_left="eye",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return buttons
|
||||||
|
|
||||||
|
def get_row_grid_data(self, land_type):
|
||||||
|
model = self.app.model
|
||||||
|
session = self.Session()
|
||||||
|
return session.query(model.LandAsset).filter(
|
||||||
|
model.LandAsset.land_type == land_type
|
||||||
|
)
|
||||||
|
|
||||||
|
def configure_row_grid(self, grid):
|
||||||
|
g = grid
|
||||||
|
super().configure_row_grid(g)
|
||||||
|
|
||||||
|
# name
|
||||||
|
g.set_link("name")
|
||||||
|
|
||||||
|
def get_row_action_url_view(self, land_asset, i):
|
||||||
|
return self.request.route_url("land_assets.view", uuid=land_asset.uuid)
|
||||||
|
|
||||||
|
|
||||||
|
def defaults(config, **kwargs):
|
||||||
|
base = globals()
|
||||||
|
|
||||||
|
LandTypeView = kwargs.get("LandTypeView", base["LandTypeView"])
|
||||||
|
LandTypeView.defaults(config)
|
||||||
|
|
||||||
|
|
||||||
|
def includeme(config):
|
||||||
|
defaults(config)
|
||||||
90
src/wuttafarm/web/views/log_types.py
Normal file
90
src/wuttafarm/web/views/log_types.py
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
# -*- 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 Log Types
|
||||||
|
"""
|
||||||
|
|
||||||
|
from wuttafarm.db.model.logs import LogType
|
||||||
|
from wuttafarm.web.views import WuttaFarmMasterView
|
||||||
|
|
||||||
|
|
||||||
|
class LogTypeView(WuttaFarmMasterView):
|
||||||
|
"""
|
||||||
|
Master view for Log Types
|
||||||
|
"""
|
||||||
|
|
||||||
|
model_class = LogType
|
||||||
|
route_prefix = "log_types"
|
||||||
|
url_prefix = "/log-types"
|
||||||
|
|
||||||
|
grid_columns = [
|
||||||
|
"name",
|
||||||
|
"description",
|
||||||
|
]
|
||||||
|
|
||||||
|
sort_defaults = "name"
|
||||||
|
|
||||||
|
filter_defaults = {
|
||||||
|
"name": {"active": True, "verb": "contains"},
|
||||||
|
}
|
||||||
|
|
||||||
|
form_fields = [
|
||||||
|
"name",
|
||||||
|
"description",
|
||||||
|
"farmos_uuid",
|
||||||
|
"drupal_id",
|
||||||
|
]
|
||||||
|
|
||||||
|
def configure_grid(self, grid):
|
||||||
|
g = grid
|
||||||
|
super().configure_grid(g)
|
||||||
|
|
||||||
|
# name
|
||||||
|
g.set_link("name")
|
||||||
|
|
||||||
|
def get_xref_buttons(self, log_type):
|
||||||
|
buttons = super().get_xref_buttons(log_type)
|
||||||
|
|
||||||
|
if log_type.farmos_uuid:
|
||||||
|
buttons.append(
|
||||||
|
self.make_button(
|
||||||
|
"View farmOS record",
|
||||||
|
primary=True,
|
||||||
|
url=self.request.route_url(
|
||||||
|
"farmos_log_types.view", uuid=log_type.farmos_uuid
|
||||||
|
),
|
||||||
|
icon_left="eye",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return buttons
|
||||||
|
|
||||||
|
|
||||||
|
def defaults(config, **kwargs):
|
||||||
|
base = globals()
|
||||||
|
|
||||||
|
LogTypeView = kwargs.get("LogTypeView", base["LogTypeView"])
|
||||||
|
LogTypeView.defaults(config)
|
||||||
|
|
||||||
|
|
||||||
|
def includeme(config):
|
||||||
|
defaults(config)
|
||||||
105
src/wuttafarm/web/views/logs_activity.py
Normal file
105
src/wuttafarm/web/views/logs_activity.py
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
# -*- 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 Activity Logs
|
||||||
|
"""
|
||||||
|
|
||||||
|
from wuttafarm.db.model.logs import ActivityLog
|
||||||
|
from wuttafarm.web.views import WuttaFarmMasterView
|
||||||
|
|
||||||
|
|
||||||
|
class ActivityLogView(WuttaFarmMasterView):
|
||||||
|
"""
|
||||||
|
Master view for Activity Logs
|
||||||
|
"""
|
||||||
|
|
||||||
|
model_class = ActivityLog
|
||||||
|
route_prefix = "activity_logs"
|
||||||
|
url_prefix = "/logs/activity"
|
||||||
|
|
||||||
|
farmos_refurl_path = "/logs/activity"
|
||||||
|
|
||||||
|
grid_columns = [
|
||||||
|
"message",
|
||||||
|
"timestamp",
|
||||||
|
"status",
|
||||||
|
]
|
||||||
|
|
||||||
|
sort_defaults = ("timestamp", "desc")
|
||||||
|
|
||||||
|
filter_defaults = {
|
||||||
|
"message": {"active": True, "verb": "contains"},
|
||||||
|
}
|
||||||
|
|
||||||
|
form_fields = [
|
||||||
|
"message",
|
||||||
|
"timestamp",
|
||||||
|
"status",
|
||||||
|
"notes",
|
||||||
|
"farmos_uuid",
|
||||||
|
"drupal_id",
|
||||||
|
]
|
||||||
|
|
||||||
|
def configure_grid(self, grid):
|
||||||
|
g = grid
|
||||||
|
super().configure_grid(g)
|
||||||
|
|
||||||
|
# message
|
||||||
|
g.set_link("message")
|
||||||
|
|
||||||
|
def configure_form(self, form):
|
||||||
|
f = form
|
||||||
|
super().configure_form(f)
|
||||||
|
|
||||||
|
# notes
|
||||||
|
f.set_widget("notes", "notes")
|
||||||
|
|
||||||
|
def get_farmos_url(self, log):
|
||||||
|
return self.app.get_farmos_url(f"/log/{log.drupal_id}")
|
||||||
|
|
||||||
|
def get_xref_buttons(self, log):
|
||||||
|
buttons = super().get_xref_buttons(log)
|
||||||
|
|
||||||
|
if log.farmos_uuid:
|
||||||
|
buttons.append(
|
||||||
|
self.make_button(
|
||||||
|
"View farmOS record",
|
||||||
|
primary=True,
|
||||||
|
url=self.request.route_url(
|
||||||
|
"farmos_logs_activity.view", uuid=log.farmos_uuid
|
||||||
|
),
|
||||||
|
icon_left="eye",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return buttons
|
||||||
|
|
||||||
|
|
||||||
|
def defaults(config, **kwargs):
|
||||||
|
base = globals()
|
||||||
|
|
||||||
|
ActivityLogView = kwargs.get("ActivityLogView", base["ActivityLogView"])
|
||||||
|
ActivityLogView.defaults(config)
|
||||||
|
|
||||||
|
|
||||||
|
def includeme(config):
|
||||||
|
defaults(config)
|
||||||
70
src/wuttafarm/web/views/master.py
Normal file
70
src/wuttafarm/web/views/master.py
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
# -*- 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 WuttaFarm master views
|
||||||
|
"""
|
||||||
|
|
||||||
|
from wuttaweb.views import MasterView
|
||||||
|
|
||||||
|
|
||||||
|
class WuttaFarmMasterView(MasterView):
|
||||||
|
"""
|
||||||
|
Base class for WuttaFarm master views
|
||||||
|
"""
|
||||||
|
|
||||||
|
farmos_refurl_path = None
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
"farmos_uuid": "farmOS UUID",
|
||||||
|
"drupal_id": "Drupal ID",
|
||||||
|
"image_url": "Image URL",
|
||||||
|
}
|
||||||
|
|
||||||
|
row_labels = {
|
||||||
|
"farmos_uuid": "farmOS UUID",
|
||||||
|
"drupal_id": "Drupal ID",
|
||||||
|
"image_url": "Image URL",
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_farmos_url(self, obj):
|
||||||
|
return None
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
def get_xref_buttons(self, obj):
|
||||||
|
url = self.get_farmos_url(obj)
|
||||||
|
if url:
|
||||||
|
return [
|
||||||
|
self.make_button(
|
||||||
|
"View in farmOS",
|
||||||
|
primary=True,
|
||||||
|
url=url,
|
||||||
|
target="_blank",
|
||||||
|
icon_left="external-link-alt",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
return []
|
||||||
118
src/wuttafarm/web/views/structure_types.py
Normal file
118
src/wuttafarm/web/views/structure_types.py
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
# -*- 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 Structure Types
|
||||||
|
"""
|
||||||
|
|
||||||
|
from wuttafarm.db.model.structures import StructureType, Structure
|
||||||
|
from wuttafarm.web.views import WuttaFarmMasterView
|
||||||
|
|
||||||
|
|
||||||
|
class StructureTypeView(WuttaFarmMasterView):
|
||||||
|
"""
|
||||||
|
Master view for Structure Types
|
||||||
|
"""
|
||||||
|
|
||||||
|
model_class = StructureType
|
||||||
|
route_prefix = "structure_types"
|
||||||
|
url_prefix = "/structure-types"
|
||||||
|
|
||||||
|
grid_columns = [
|
||||||
|
"name",
|
||||||
|
]
|
||||||
|
|
||||||
|
sort_defaults = "name"
|
||||||
|
|
||||||
|
filter_defaults = {
|
||||||
|
"name": {"active": True, "verb": "contains"},
|
||||||
|
}
|
||||||
|
|
||||||
|
form_fields = [
|
||||||
|
"name",
|
||||||
|
"farmos_uuid",
|
||||||
|
"drupal_id",
|
||||||
|
]
|
||||||
|
|
||||||
|
has_rows = True
|
||||||
|
row_model_class = Structure
|
||||||
|
rows_viewable = True
|
||||||
|
|
||||||
|
row_grid_columns = [
|
||||||
|
"name",
|
||||||
|
"is_location",
|
||||||
|
"is_fixed",
|
||||||
|
"active",
|
||||||
|
]
|
||||||
|
|
||||||
|
rows_sort_defaults = "name"
|
||||||
|
|
||||||
|
def configure_grid(self, grid):
|
||||||
|
g = grid
|
||||||
|
super().configure_grid(g)
|
||||||
|
|
||||||
|
# name
|
||||||
|
g.set_link("name")
|
||||||
|
|
||||||
|
def get_xref_buttons(self, structure_type):
|
||||||
|
buttons = super().get_xref_buttons(structure_type)
|
||||||
|
|
||||||
|
if structure_type.farmos_uuid:
|
||||||
|
buttons.append(
|
||||||
|
self.make_button(
|
||||||
|
"View farmOS record",
|
||||||
|
primary=True,
|
||||||
|
url=self.request.route_url(
|
||||||
|
"farmos_structure_types.view", uuid=structure_type.farmos_uuid
|
||||||
|
),
|
||||||
|
icon_left="eye",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return buttons
|
||||||
|
|
||||||
|
def get_row_grid_data(self, structure_type):
|
||||||
|
model = self.app.model
|
||||||
|
session = self.Session()
|
||||||
|
return session.query(model.Structure).filter(
|
||||||
|
model.Structure.structure_type == structure_type
|
||||||
|
)
|
||||||
|
|
||||||
|
def configure_row_grid(self, grid):
|
||||||
|
g = grid
|
||||||
|
super().configure_row_grid(g)
|
||||||
|
|
||||||
|
# name
|
||||||
|
g.set_link("name")
|
||||||
|
|
||||||
|
def get_row_action_url_view(self, structure, i):
|
||||||
|
return self.request.route_url("structures.view", uuid=structure.uuid)
|
||||||
|
|
||||||
|
|
||||||
|
def defaults(config, **kwargs):
|
||||||
|
base = globals()
|
||||||
|
|
||||||
|
StructureTypeView = kwargs.get("StructureTypeView", base["StructureTypeView"])
|
||||||
|
StructureTypeView.defaults(config)
|
||||||
|
|
||||||
|
|
||||||
|
def includeme(config):
|
||||||
|
defaults(config)
|
||||||
127
src/wuttafarm/web/views/structures.py
Normal file
127
src/wuttafarm/web/views/structures.py
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
# -*- 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 Structures
|
||||||
|
"""
|
||||||
|
|
||||||
|
from wuttafarm.db.model.structures import Structure
|
||||||
|
from wuttafarm.web.views import WuttaFarmMasterView
|
||||||
|
from wuttafarm.web.forms.schema import StructureTypeRef
|
||||||
|
from wuttafarm.web.forms.widgets import ImageWidget
|
||||||
|
|
||||||
|
|
||||||
|
class StructureView(WuttaFarmMasterView):
|
||||||
|
"""
|
||||||
|
Master view for Structures
|
||||||
|
"""
|
||||||
|
|
||||||
|
model_class = Structure
|
||||||
|
route_prefix = "structures"
|
||||||
|
url_prefix = "/structures"
|
||||||
|
|
||||||
|
farmos_refurl_path = "/assets/structure"
|
||||||
|
|
||||||
|
grid_columns = [
|
||||||
|
"name",
|
||||||
|
"structure_type",
|
||||||
|
"is_location",
|
||||||
|
"is_fixed",
|
||||||
|
"active",
|
||||||
|
]
|
||||||
|
|
||||||
|
sort_defaults = "name"
|
||||||
|
|
||||||
|
filter_defaults = {
|
||||||
|
"name": {"active": True, "verb": "contains"},
|
||||||
|
}
|
||||||
|
|
||||||
|
form_fields = [
|
||||||
|
"name",
|
||||||
|
"structure_type",
|
||||||
|
"is_location",
|
||||||
|
"is_fixed",
|
||||||
|
"notes",
|
||||||
|
"active",
|
||||||
|
"farmos_uuid",
|
||||||
|
"drupal_id",
|
||||||
|
"image_url",
|
||||||
|
"image",
|
||||||
|
]
|
||||||
|
|
||||||
|
def configure_grid(self, grid):
|
||||||
|
g = grid
|
||||||
|
super().configure_grid(g)
|
||||||
|
model = self.app.model
|
||||||
|
|
||||||
|
# name
|
||||||
|
g.set_link("name")
|
||||||
|
|
||||||
|
# structure_type
|
||||||
|
g.set_joiner("structure_type", lambda q: q.join(model.StructureType))
|
||||||
|
g.set_sorter("structure_type", model.StructureType.name)
|
||||||
|
g.set_filter(
|
||||||
|
"structure_type", model.StructureType.name, label="Structure Type Name"
|
||||||
|
)
|
||||||
|
|
||||||
|
def configure_form(self, form):
|
||||||
|
f = form
|
||||||
|
super().configure_form(f)
|
||||||
|
structure = form.model_instance
|
||||||
|
|
||||||
|
# structure_type
|
||||||
|
f.set_node("structure_type", StructureTypeRef(self.request))
|
||||||
|
|
||||||
|
# image
|
||||||
|
if structure.image_url:
|
||||||
|
f.set_widget("image", ImageWidget("structure image"))
|
||||||
|
f.set_default("image", structure.image_url)
|
||||||
|
|
||||||
|
def get_farmos_url(self, structure):
|
||||||
|
return self.app.get_farmos_url(f"/asset/{structure.drupal_id}")
|
||||||
|
|
||||||
|
def get_xref_buttons(self, structure):
|
||||||
|
buttons = super().get_xref_buttons(structure)
|
||||||
|
|
||||||
|
if structure.farmos_uuid:
|
||||||
|
buttons.append(
|
||||||
|
self.make_button(
|
||||||
|
"View farmOS record",
|
||||||
|
primary=True,
|
||||||
|
url=self.request.route_url(
|
||||||
|
"farmos_structures.view", uuid=structure.farmos_uuid
|
||||||
|
),
|
||||||
|
icon_left="eye",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return buttons
|
||||||
|
|
||||||
|
|
||||||
|
def defaults(config, **kwargs):
|
||||||
|
base = globals()
|
||||||
|
|
||||||
|
StructureView = kwargs.get("StructureView", base["StructureView"])
|
||||||
|
StructureView.defaults(config)
|
||||||
|
|
||||||
|
|
||||||
|
def includeme(config):
|
||||||
|
defaults(config)
|
||||||
99
src/wuttafarm/web/views/users.py
Normal file
99
src/wuttafarm/web/views/users.py
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
# -*- 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 for Users
|
||||||
|
"""
|
||||||
|
|
||||||
|
from wuttaweb.views import users as base
|
||||||
|
|
||||||
|
|
||||||
|
class UserView(base.UserView):
|
||||||
|
"""
|
||||||
|
Custom master view for Users.
|
||||||
|
"""
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
"farmos_uuid": "farmOS UUID",
|
||||||
|
"drupal_id": "Drupal ID",
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_template_context(self, context):
|
||||||
|
context = super().get_template_context(context)
|
||||||
|
|
||||||
|
if self.listing:
|
||||||
|
context["farmos_refurl"] = self.app.get_farmos_url("/admin/people")
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
def configure_form(self, form):
|
||||||
|
""" """
|
||||||
|
f = form
|
||||||
|
super().configure_form(f)
|
||||||
|
user = f.model_instance
|
||||||
|
|
||||||
|
# farmos_uuid
|
||||||
|
if not self.creating:
|
||||||
|
f.fields.append("farmos_uuid")
|
||||||
|
f.set_default("farmos_uuid", user.farmos_uuid)
|
||||||
|
|
||||||
|
# drupal_id
|
||||||
|
if not self.creating:
|
||||||
|
f.fields.append("drupal_id")
|
||||||
|
f.set_default("drupal_id", user.drupal_id)
|
||||||
|
|
||||||
|
def get_xref_buttons(self, user):
|
||||||
|
buttons = []
|
||||||
|
|
||||||
|
if user.drupal_id:
|
||||||
|
buttons.append(
|
||||||
|
self.make_button(
|
||||||
|
"View in farmOS",
|
||||||
|
primary=True,
|
||||||
|
url=self.app.get_farmos_url(f"/user/{user.drupal_id}"),
|
||||||
|
target="_blank",
|
||||||
|
icon_left="external-link-alt",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if user.farmos_uuid:
|
||||||
|
buttons.append(
|
||||||
|
self.make_button(
|
||||||
|
"View farmOS record",
|
||||||
|
primary=True,
|
||||||
|
url=self.request.route_url(
|
||||||
|
"farmos_users.view", uuid=user.farmos_uuid
|
||||||
|
),
|
||||||
|
icon_left="eye",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return buttons
|
||||||
|
|
||||||
|
|
||||||
|
def defaults(config, **kwargs):
|
||||||
|
local = globals()
|
||||||
|
UserView = kwargs.get("UserView", local["UserView"])
|
||||||
|
base.defaults(config, **{"UserView": UserView})
|
||||||
|
|
||||||
|
|
||||||
|
def includeme(config):
|
||||||
|
defaults(config)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue