Compare commits

...

161 commits

Author SHA1 Message Date
b2b49d93ae docs: fix doc warning 2026-03-04 14:20:09 -06:00
7bffa6cba6 fix: bump version requirement for wuttaweb 2026-03-04 14:15:23 -06:00
0a1aee591a bump: version 0.6.0 → 0.7.0 2026-03-04 14:14:52 -06:00
a0f73e6a32 fix: show drupal ID column for asset types 2026-03-04 12:59:55 -06:00
e8a8ce2528 feat: expose "group membership" for assets 2026-03-04 12:59:55 -06:00
b2c3d3a301 fix: remove unique constraint for LandAsset.land_type_uuid
not sure why that was in there..assuming a mistake
2026-03-04 12:59:55 -06:00
759eb906b9 feat: expose "current location" for assets
based on most recent movement log, as in farmOS
2026-03-04 12:59:55 -06:00
41870ee2e2 fix: move farmOS UUID field below the Drupal ID 2026-03-04 12:59:55 -06:00
0ac2485bff feat: add schema, sync support for Log.is_movement 2026-03-04 12:59:55 -06:00
eb16990b0b feat: add schema, import support for Asset.owners 2026-03-04 12:59:55 -06:00
ce103137a5 fix: add links for Parents column in All Assets grid 2026-03-04 12:59:55 -06:00
547cc6e4ae feat: add schema, import support for Log.quick 2026-03-04 12:59:55 -06:00
32d23a7073 feat: show quantities when viewing log 2026-03-04 12:59:55 -06:00
7890b18568 fix: set timestamp for new log in quick eggs form 2026-03-04 12:59:55 -06:00
90ff7eb793 fix: set default grid pagesize to 50
to better match farmOS
2026-03-04 12:59:55 -06:00
d07f3ed716 feat: add sync support for MedicalLog.vet 2026-03-04 12:59:54 -06:00
7d2ae48067 feat: add schema, import support for Log.quantities 2026-03-04 12:59:54 -06:00
1d877545ae feat: add schema, import support for Log.groups 2026-03-04 12:59:54 -06:00
87f3764ebf feat: add schema, import support for Log.locations
still need to add support for edit, export
2026-03-04 12:59:54 -06:00
3ae4d639ec feat: add sync support for Log.is_group_assignment 2026-03-04 12:59:54 -06:00
a5550091d3 feat: add support for exporting log status, timestamp to farmOS 2026-03-04 12:59:54 -06:00
61402c183e fix: add placeholder for log 'quick' field 2026-03-04 12:59:54 -06:00
64e4392a92 feat: add support for log 'owners' 2026-03-04 12:59:54 -06:00
ae73d2f87f fix: define log grid columns to match farmOS
some of these still do not have values yet..
2026-03-04 12:59:54 -06:00
86e36bc64a fix: make AllLogView inherit from LogMasterView
and improve asset rendering for those grids
2026-03-04 12:59:54 -06:00
d1817a3611 fix: rename views for "all records" (all assets, all logs etc.)
just for clarity's sake, i think it's better
2026-03-04 12:59:54 -06:00
d465934818 fix: ensure token refresh works regardless where API client is used 2026-03-04 12:59:54 -06:00
c353d5bcef feat: add support for edit, import/export of plant type data
esp. plant types for a plant asset
2026-03-04 12:59:54 -06:00
bdda586ccd fix: render links for Plant Type column in Plant Assets grid 2026-03-04 12:59:54 -06:00
0d989dcb2c fix: fix land asset type 2026-03-04 12:59:54 -06:00
2f84f76d89 fix: prevent edit for asset types, land types when app is mirror 2026-03-04 12:59:51 -06:00
3343524325 fix: add farmOS-style links for Parents column in Land Assets grid 2026-02-28 22:08:57 -06:00
28ecb4d786 fix: remove unique constraint for AnimalType.name
since it is not guaranteed unique in farmOS; can't do it here either
or else import may fail
2026-02-28 22:08:57 -06:00
338da0208c fix: prevent delete if animal type is still being referenced 2026-02-28 22:08:57 -06:00
ec67340e66 feat: add way to create animal type when editing animal 2026-02-28 22:08:55 -06:00
1c0286eda0 fix: add reminder to restart if changing integration mode 2026-02-28 22:08:55 -06:00
7d5ff47e8e feat: add related version tables for asset/log revision history 2026-02-28 22:08:53 -06:00
5046171b76 fix: prevent edit for user farmos_uuid, drupal_id 2026-02-28 22:08:53 -06:00
f374ae426c fix: remove 'contains' verb for sex filter 2026-02-28 22:08:53 -06:00
2a375b0a6f fix: add enum, row hilite for log status 2026-02-28 22:08:53 -06:00
a5d7f89fcb feat: improve mirror/deletion for assets, logs, animal types 2026-02-28 22:08:51 -06:00
96ccf30e46 feat: auto-delete asset from farmOS if deleting via mirror app 2026-02-28 22:08:48 -06:00
38dad49bbd fix: fix Sex field when empty and deleting an animal 2026-02-26 17:35:05 -06:00
f2be7d0a53 fix: add get_farmos_client_for_user() convenience function 2026-02-26 17:25:49 -06:00
9b4afb845b fix: use current user token for auto-sync within web app
to ensure data writes to farmOS have correct authorship
2026-02-26 17:04:55 -06:00
f4b5f3960c fix: set log type, status enums for log grids 2026-02-25 15:22:25 -06:00
127ea49d74 fix: add more default perms for first site admin user 2026-02-25 14:59:54 -06:00
30e1fd23d6 fix: only show quick form menu if perms allow 2026-02-25 14:59:54 -06:00
df517cfbfa fix: expose config for farmOS OAuth2 client_id and scope
refs: #3
2026-02-25 14:59:46 -06:00
ec6ac443fb fix: add separate permission for each quick form view 2026-02-25 11:22:49 -06:00
11781dd70b bump: version 0.5.0 → 0.6.0 2026-02-25 09:02:12 -06:00
b9ab27523f fix: add Notes schema type
this is because the dict we get from (normalizing the) farmOS API
record will have e.g. `notes=None` but that winds up rendering as
"None" instead of empty string - so we use colander.null value in such
cases so empty string is rendered
2026-02-24 20:03:59 -06:00
331543d74b docs: fix sphinx warnings 2026-02-24 19:57:25 -06:00
e7ef5c3d32 feat: add common normalizer to simplify code in view, importer etc.
only the "log" normalizer exists so far, but will add more..
2026-02-24 16:19:26 -06:00
1a6870b8fe feat: overhaul farmOS log views; add Eggs quick form
probably a few other changes...i'm tired and need a savepoint
2026-02-24 16:19:24 -06:00
ad6ac13d50 feat: add basic CRUD for direct API views: animal types, animal assets 2026-02-21 18:38:08 -06:00
c976d94bdd fix: add grid filter for animal birthdate 2026-02-20 21:37:57 -06:00
5d7dea5a84 fix: add thumbnail to farmOS asset base view 2026-02-20 20:52:08 -06:00
e5e3d38365 fix: add setting to toggle "farmOS-style grid links"
not sure yet if users prefer farmOS style, but will assume so by
default just to be safe.  but i want the "traditional" behavior
myself, so setting is needed either way
2026-02-20 20:38:31 -06:00
1af2b695dc feat: use 'include' API param for better Animal Assets grid data
this commit also renames all farmOS asset routes, for some reason.  at
least now they are consistent
2026-02-20 19:21:49 -06:00
bbb1207b27 feat: add backend filters, sorting for farmOS animal types, assets
could not add pagination due to quirks with how Drupal JSONAPI works
for that.  but so far it looks like we can add filter/sort to all of
the farmOS grids..now just need to do it
2026-02-20 16:10:44 -06:00
9cfa91e091 fix: standardize a bit more for the farmOS Animal Assets view 2026-02-20 14:53:14 -06:00
87101d6b04 feat: include/exclude certain views, menus based on integration mode
refs: #3
2026-02-20 14:53:14 -06:00
1f254ca775 fix: set *default* instead of configured menu handler 2026-02-20 14:53:14 -06:00
d884a761ad fix: expose farmOS integration mode, URL in app settings
although as of now changing the integration mode setting will not
actually change any behavior.. but it will

refs: #3
2026-02-20 14:53:14 -06:00
cfe2e4b7b4 feat: add Standard Quantities table, views, import 2026-02-20 14:53:14 -06:00
c93660ec4a feat: add Quantity Types table, views, import 2026-02-20 14:53:14 -06:00
0a0d43aa9f feat: add Units table, views, import/export 2026-02-20 14:53:13 -06:00
bc0836fc3c fix: reword some menu entries 2026-02-18 19:31:58 -06:00
e7b493d7c9 fix: add WuttaFarm -> farmOS export for Plant Assets 2026-02-18 19:31:41 -06:00
185cd86efb fix: fix default admin user perms, per new log schema 2026-02-18 19:09:39 -06:00
5ee2db267a bump: version 0.4.1 → 0.5.0 2026-02-18 19:03:45 -06:00
26a4746898 feat: add produces_eggs flag for animal, group assets
even if the farmOS instance does not have `farm_eggs` module
installed, we should support the schema
2026-02-18 18:56:40 -06:00
2e0ec73317 feat: add more assets (plant) and logs (harvest, medical, observation) 2026-02-18 18:40:37 -06:00
b061959b18 feat: refactor log models, views to use generic/common base 2026-02-18 13:21:38 -06:00
982da89861 fix: rename db model modules, for better convention 2026-02-18 11:45:07 -06:00
4ec7923164 fix: add override for requests cert validation
for use in local dev with self-signed certs
2026-02-18 11:29:38 -06:00
4bc556aec5 bump: version 0.4.0 → 0.4.1 2026-02-17 20:12:38 -06:00
e520a34fa5 fix: remove AnimalType.changed column
that was just confusing things.  WuttaFarm model should have its own
notion of when things changed, and farmOS can have its own.
2026-02-17 18:13:16 -06:00
d741a88299 docs: update feature list, roadmap, screenshots 2026-02-17 16:54:43 -06:00
36eca08895 bump: version 0.3.1 → 0.4.0 2026-02-17 15:40:03 -06:00
da9b559752 feat: add basic support for WuttaFarm → farmOS export
typical CLI export tool, but also the export happens automatically
when create or edit of record happens in wuttafarm

supported models:

- AnimalType
- AnimalAsset
- GroupAsset
- LandAsset
- StructureAsset
2026-02-17 13:41:50 -06:00
6677fe1e23 fix: misc. field tweaks for asset forms 2026-02-16 15:09:50 -06:00
b85259c013 fix: show warning when viewing an archived asset 2026-02-16 15:09:50 -06:00
bb21d6a364 fix: fix some perms for all assets view 2026-02-15 14:11:14 -06:00
ec89230893 fix: fix initial admin perms per route renaming 2026-02-15 14:08:01 -06:00
2fc9c88cd5 feat: convert group assets to use common base/mixin 2026-02-15 14:07:03 -06:00
3435b4714e feat: convert structure assets to use common base/mixin 2026-02-15 13:38:38 -06:00
7b6280b6dc feat: convert land assets to use common base/mixin 2026-02-15 13:38:36 -06:00
140f3cbdba feat: add "generic" assets, new animal assets based on that 2026-02-15 11:11:13 -06:00
ac084c4e79 fix: add parent relationships support for land assets
this may not be complete yet, we'll see.  works for the simple case afaik
2026-02-14 22:50:34 -06:00
71592e883a fix: cleanup Land views to better match farmOS 2026-02-14 20:33:54 -06:00
e60b91fd45 fix: cleanup Structure views to better match farmOS 2026-02-14 20:18:35 -06:00
aae01c010b fix: cleanup Group views to better match farmOS 2026-02-14 20:06:09 -06:00
5e4cd8978d fix: add / display thumbnail image for animals 2026-02-14 20:00:39 -06:00
e120812eae fix: improve handling of 'archived' records for grid/form views 2026-02-14 19:35:42 -06:00
25b2dc6cec fix: use Male/Female dict enum for animal sex field
and some related changes to make Animal views more like farmOS
2026-02-14 19:35:40 -06:00
df4536741d fix: prevent direct edit of farmos_uuid and drupal_id fields 2026-02-14 19:25:34 -06:00
e9161e8c93 fix: use same datetime display format as farmOS
at least i think this is right..anyway this is how you change it
2026-02-14 19:24:16 -06:00
4ed61380de fix: convert active flag to archived
to better mirror farmOS
2026-02-14 18:52:49 -06:00
98be276bd1 fix: suppress output when user farmos/drupal keys are empty 2026-02-14 15:43:47 -06:00
96d575feb7 fix: customize page footer to mention farmOS 2026-02-14 15:07:10 -06:00
02d022295c bump: version 0.3.0 → 0.3.1 2026-02-14 15:04:44 -06:00
985d224cb8 fix: update sterile, archived flags per farmOS 4.x
3.x should still work okay too though
2026-02-14 14:54:12 -06:00
35068c0cb1 docs: add some notes on email setup 2026-02-14 08:44:24 -06:00
34cb6b210d bump: version 0.2.3 → 0.3.0 2026-02-13 15:51:52 -06:00
061dac39f9 docs: add basic docs for oauth2 setup, import data from farmOS 2026-02-13 15:50:32 -06:00
be64b4959a docs: add basic docs for CLI commands 2026-02-13 15:18:53 -06:00
311a2c328b fix: always make 'farmos' system user in app setup
mainly for sake of attributing data changes coming from farmOS
2026-02-13 15:11:10 -06:00
935c64464a fix: avoid error for Create User form 2026-02-13 15:07:48 -06:00
1dbf14f3bb fix: add more perms to Site Admin role in app setup 2026-02-13 15:07:48 -06:00
ed768a83d0 feat: add native table for Activity Logs; import from farmOS API 2026-02-13 14:53:02 -06:00
f4e4c3efb3 fix: rename drupal_internal_id => drupal_id 2026-02-13 14:53:02 -06:00
81daa5d913 feat: add native table for Groups; import from farmOS API 2026-02-13 14:53:02 -06:00
3e5ca3483e feat: add native table for Animals; import from farmOS API 2026-02-13 14:52:58 -06:00
c38d00a7cc feat: add native table for Structures; import from farmOS API 2026-02-13 12:28:54 -06:00
1d898cb580 feat: add native table for Land Assets; import from farmOS API 2026-02-13 10:43:34 -06:00
6204db8ae3 feat: add native table for Log Types; import from farmOS API 2026-02-10 19:51:08 -06:00
5189c12f43 feat: add native table for Structure Types; import from farmOS API 2026-02-10 19:43:20 -06:00
b573ae459e feat: add native table for Land Types; import from farmOS API 2026-02-10 19:43:18 -06:00
10666de488 feat: add native table for Asset Types; import from farmOS API 2026-02-10 19:21:01 -06:00
fd2f09fcf3 feat: add extension table for Users; import from farmOS API 2026-02-10 19:21:01 -06:00
4a517bf7bf feat: add native table for Animal Types; import from farmOS API 2026-02-10 19:20:59 -06:00
09042747a0 feat: add "See raw JSON data" button for farmOS API views 2026-02-10 18:30:35 -06:00
9cc7237bfb bump: version 0.2.2 → 0.2.3 2026-02-08 14:35:42 -06:00
a17d269adb docs: add docs link to readme 2026-02-08 14:10:26 -06:00
00fd484669 fix: add custom (built) buefy css to repo
this makes things simpler, and no real reason not to include it
2026-02-08 14:07:23 -06:00
4c0754ee01 docs: initial/basic project docs 2026-02-08 12:54:11 -06:00
3290a9936e bump: version 0.2.1 → 0.2.2 2026-02-08 11:54:18 -06:00
fa5469958e fix: update project links for PyPI 2026-02-08 11:53:46 -06:00
1327d1f7b2 bump: version 0.2.0 → 0.2.1 2026-02-08 11:36:38 -06:00
8cc4af950e fix: run web app via uvicorn/ASGI by default
just seems faster
2026-02-08 11:35:39 -06:00
ccb64c5c4d bump: version 0.1.5 → 0.2.0 2026-02-08 09:24:28 -06:00
920811136e fix: add pyramid_exclog dependency
mostly for convenience, since IMHO it is good to use in production
2026-02-08 09:23:46 -06:00
c778997239 feat: add view for farmOS activity logs 2026-02-08 08:55:19 -06:00
f7d5d0ab1c feat: add view for farmOS log types 2026-02-07 19:25:13 -06:00
33717bb055 fix: add menu option, "Go to farmOS" 2026-02-07 18:48:56 -06:00
7d65d3c5a2 feat: add view for farmOS structure types 2026-02-07 18:48:56 -06:00
acba07aa0e feat: add view for farmOS land types 2026-02-07 18:48:56 -06:00
233b2a2dab feat: add view for farmOS land assets 2026-02-07 18:48:56 -06:00
5005c3c978 feat: add view for farmOS groups 2026-02-07 18:48:56 -06:00
ba926ec2de fix: ensure Buefy version matches what we use for custom css
this is an alright solution for now, but may need to improve in the
future once we look at Vue 3 etc.

basically the only reason this solution isn't terrible, is because
buefy 0.9.x (for Vue 2) is "stable"
2026-02-07 18:48:56 -06:00
19b6738e5d feat: add view for farmOS asset types
of limited value perhaps, but what the heck
2026-02-07 18:48:56 -06:00
d9ef550100 feat: add view for farmOS structures 2026-02-07 18:48:54 -06:00
baacd1c15c feat: add view for farmOS animal types 2026-02-07 14:58:47 -06:00
7415504926 feat: add view for farmOS users 2026-02-07 14:26:49 -06:00
f0a2308bd9 bump: version 0.1.4 → 0.1.5 2026-02-07 12:31:00 -06:00
d070caae05 fix: fix built wheel to include custom buefy css 2026-02-07 12:30:22 -06:00
f42761f359 bump: version 0.1.3 → 0.1.4 2026-02-07 09:48:52 -06:00
768859b6b9 fix: add custom style to better match farmOS color scheme 2026-02-07 09:48:26 -06:00
87b97f53b8 build: explicitly ignore emacs backup files, for sake of sdist 2026-02-06 19:19:02 -06:00
9bd1c07193 bump: version 0.1.2 → 0.1.3 2026-02-06 17:43:06 -06:00
b161109d65 fix: fix a couple more edge cases around oauth2 token refresh 2026-02-06 17:42:47 -06:00
b546c9e97d bump: version 0.1.1 → 0.1.2 2026-02-06 14:36:03 -06:00
5b96fcfc2a fix: add support for farmOS/OAuth2 Authorization Code grant/workflow 2026-02-06 14:24:41 -06:00
039aa60038 bump: version 0.1.0 → 0.1.1 2026-02-05 21:17:11 -06:00
19dcc2d24b fix: preserve oauth2 token so auto-refresh works correctly
also add `get_farmos_client()` handler methods
2026-02-05 20:06:55 -06:00
45bb985465 fix: customize app installer to configure farmos_url 2026-02-04 09:18:44 -06:00
bd972e30db fix: add some more info when viewing animal 2026-02-04 08:53:22 -06:00
07b6fa7c22 fix: require minimum version for wuttaweb 2026-02-04 08:52:10 -06:00
8ef59bc53f build: no tests to run yet 2026-02-03 19:37:55 -06:00
197 changed files with 23606 additions and 137 deletions

3
.gitignore vendored
View file

@ -1,3 +1,6 @@
*~
*.pyc
dist/
docs/_build/
style/dist/
style/node_modules/

View file

@ -5,6 +5,238 @@ 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/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## v0.7.0 (2026-03-04)
### Feat
- expose "group membership" for assets
- expose "current location" for assets
- add schema, sync support for `Log.is_movement`
- add schema, import support for `Asset.owners`
- add schema, import support for `Log.quick`
- show quantities when viewing log
- add sync support for `MedicalLog.vet`
- add schema, import support for `Log.quantities`
- add schema, import support for `Log.groups`
- add schema, import support for `Log.locations`
- add sync support for `Log.is_group_assignment`
- add support for exporting log status, timestamp to farmOS
- add support for log 'owners'
- add support for edit, import/export of plant type data
- add way to create animal type when editing animal
- add related version tables for asset/log revision history
- improve mirror/deletion for assets, logs, animal types
- auto-delete asset from farmOS if deleting via mirror app
### Fix
- show drupal ID column for asset types
- remove unique constraint for `LandAsset.land_type_uuid`
- move farmOS UUID field below the Drupal ID
- add links for Parents column in All Assets grid
- set timestamp for new log in quick eggs form
- set default grid pagesize to 50
- add placeholder for log 'quick' field
- define log grid columns to match farmOS
- make AllLogView inherit from LogMasterView
- rename views for "all records" (all assets, all logs etc.)
- ensure token refresh works regardless where API client is used
- render links for Plant Type column in Plant Assets grid
- fix land asset type
- prevent edit for asset types, land types when app is mirror
- add farmOS-style links for Parents column in Land Assets grid
- remove unique constraint for `AnimalType.name`
- prevent delete if animal type is still being referenced
- add reminder to restart if changing integration mode
- prevent edit for user farmos_uuid, drupal_id
- remove 'contains' verb for sex filter
- add enum, row hilite for log status
- fix Sex field when empty and deleting an animal
- add `get_farmos_client_for_user()` convenience function
- use current user token for auto-sync within web app
- set log type, status enums for log grids
- add more default perms for first site admin user
- only show quick form menu if perms allow
- expose config for farmOS OAuth2 client_id and scope
- add separate permission for each quick form view
## v0.6.0 (2026-02-25)
### Feat
- add common normalizer to simplify code in view, importer etc.
- overhaul farmOS log views; add Eggs quick form
- add basic CRUD for direct API views: animal types, animal assets
- use 'include' API param for better Animal Assets grid data
- add backend filters, sorting for farmOS animal types, assets
- include/exclude certain views, menus based on integration mode
- add Standard Quantities table, views, import
- add Quantity Types table, views, import
- add Units table, views, import/export
### Fix
- add `Notes` schema type
- add grid filter for animal birthdate
- add thumbnail to farmOS asset base view
- add setting to toggle "farmOS-style grid links"
- standardize a bit more for the farmOS Animal Assets view
- set *default* instead of configured menu handler
- expose farmOS integration mode, URL in app settings
- reword some menu entries
- add WuttaFarm -> farmOS export for Plant Assets
- fix default admin user perms, per new log schema
## v0.5.0 (2026-02-18)
### Feat
- add `produces_eggs` flag for animal, group assets
- add more assets (plant) and logs (harvest, medical, observation)
- refactor log models, views to use generic/common base
### Fix
- rename db model modules, for better convention
- add override for requests cert validation
## v0.4.1 (2026-02-17)
### Fix
- remove `AnimalType.changed` column
## v0.4.0 (2026-02-17)
### Feat
- add basic support for WuttaFarm → farmOS export
- convert group assets to use common base/mixin
- convert structure assets to use common base/mixin
- convert land assets to use common base/mixin
- add "generic" assets, new animal assets based on that
### Fix
- misc. field tweaks for asset forms
- show warning when viewing an archived asset
- fix some perms for all assets view
- fix initial admin perms per route renaming
- add parent relationships support for land assets
- cleanup Land views to better match farmOS
- cleanup Structure views to better match farmOS
- cleanup Group views to better match farmOS
- add / display thumbnail image for animals
- improve handling of 'archived' records for grid/form views
- use Male/Female dict enum for animal sex field
- prevent direct edit of `farmos_uuid` and `drupal_id` fields
- use same datetime display format as farmOS
- convert `active` flag to `archived`
- suppress output when user farmos/drupal keys are empty
- customize page footer to mention farmOS
## v0.3.1 (2026-02-14)
### Fix
- update sterile, archived flags per farmOS 4.x
## 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)
### Fix
- add custom (built) buefy css to repo
## v0.2.2 (2026-02-08)
### Fix
- update project links for PyPI
## v0.2.1 (2026-02-08)
### Fix
- run web app via uvicorn/ASGI by default
## v0.2.0 (2026-02-08)
### Feat
- add view for farmOS activity logs
- add view for farmOS log types
- add view for farmOS structure types
- add view for farmOS land types
- add view for farmOS land assets
- add view for farmOS groups
- add view for farmOS asset types
- add view for farmOS structures
- add view for farmOS animal types
- add view for farmOS users
### Fix
- add pyramid_exclog dependency
- add menu option, "Go to farmOS"
- ensure Buefy version matches what we use for custom css
## v0.1.5 (2026-02-07)
### Fix
- fix built wheel to include custom buefy css
## v0.1.4 (2026-02-07)
### Fix
- add custom style to better match farmOS color scheme
## v0.1.3 (2026-02-06)
### Fix
- fix a couple more edge cases around oauth2 token refresh
## v0.1.2 (2026-02-06)
### Fix
- add support for farmOS/OAuth2 Authorization Code grant/workflow
## v0.1.1 (2026-02-05)
### Fix
- preserve oauth2 token so auto-refresh works correctly
- customize app installer to configure farmos_url
- add some more info when viewing animal
- require minimum version for wuttaweb
## v0.1.0 (2026-02-03)
### Feat

View file

@ -13,14 +13,4 @@ include:
- possibly add more schema / extra features
- possibly sync data back to farmOS
## Quick Start
Make a virtual environment and install the app:
python3 -m venv .venv
.venv/bin/pip install -e .
.venv/bin/wuttafarm install
For more info see
https://docs.wuttaproject.org/wuttjamaican/narr/install/index.html
See full docs at https://docs.wuttaproject.org/wuttafarm/

20
docs/Makefile Normal file
View file

@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = .
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

0
docs/_static/.keepme vendored Normal file
View file

View file

@ -0,0 +1,6 @@
``wuttafarm.app``
=================
.. automodule:: wuttafarm.app
:members:

View file

@ -0,0 +1,6 @@
``wuttafarm.auth``
==================
.. automodule:: wuttafarm.auth
:members:

View file

@ -0,0 +1,6 @@
``wuttafarm.cli.base``
======================
.. automodule:: wuttafarm.cli.base
:members:

View file

@ -0,0 +1,6 @@
``wuttafarm.cli.import_farmos``
===============================
.. automodule:: wuttafarm.cli.import_farmos
:members:

View file

@ -0,0 +1,6 @@
``wuttafarm.cli.install``
=========================
.. automodule:: wuttafarm.cli.install
:members:

View file

@ -0,0 +1,6 @@
``wuttafarm.cli``
=================
.. automodule:: wuttafarm.cli
:members:

View file

@ -0,0 +1,6 @@
``wuttafarm.config``
====================
.. automodule:: wuttafarm.config
:members:

View file

@ -0,0 +1,6 @@
``wuttafarm.db.model``
======================
.. automodule:: wuttafarm.db.model
:members:

View file

@ -0,0 +1,6 @@
``wuttafarm.db``
================
.. automodule:: wuttafarm.db
:members:

View file

@ -0,0 +1,6 @@
``wuttafarm.farmos.handler``
============================
.. automodule:: wuttafarm.farmos.handler
:members:

View file

@ -0,0 +1,6 @@
``wuttafarm.farmos``
====================
.. automodule:: wuttafarm.farmos
:members:

View file

@ -0,0 +1,6 @@
``wuttafarm.importing.farmos``
==============================
.. automodule:: wuttafarm.importing.farmos
:members:

View file

@ -0,0 +1,6 @@
``wuttafarm.importing``
=======================
.. automodule:: wuttafarm.importing
:members:

View file

@ -0,0 +1,6 @@
``wuttafarm.install``
=====================
.. automodule:: wuttafarm.install
:members:

6
docs/api/wuttafarm.rst Normal file
View file

@ -0,0 +1,6 @@
``wuttafarm``
=============
.. automodule:: wuttafarm
:members:

View file

@ -0,0 +1,6 @@
``wuttafarm.web.app``
=====================
.. automodule:: wuttafarm.web.app
:members:

View file

@ -0,0 +1,6 @@
``wuttafarm.web.forms``
=======================
.. automodule:: wuttafarm.web.forms
:members:

View file

@ -0,0 +1,6 @@
``wuttafarm.web.forms.schema``
==============================
.. automodule:: wuttafarm.web.forms.schema
:members:

View file

@ -0,0 +1,6 @@
``wuttafarm.web.forms.widgets``
===============================
.. automodule:: wuttafarm.web.forms.widgets
:members:

View file

@ -0,0 +1,6 @@
``wuttafarm.web.menus``
=======================
.. automodule:: wuttafarm.web.menus
:members:

View file

@ -0,0 +1,6 @@
``wuttafarm.web``
=================
.. automodule:: wuttafarm.web
:members:

View file

@ -0,0 +1,6 @@
``wuttafarm.web.static``
========================
.. automodule:: wuttafarm.web.static
:members:

View file

@ -0,0 +1,6 @@
``wuttafarm.web.subscribers``
=============================
.. automodule:: wuttafarm.web.subscribers
:members:

View file

@ -0,0 +1,6 @@
``wuttafarm.web.util``
======================
.. automodule:: wuttafarm.web.util
:members:

View file

@ -0,0 +1,6 @@
``wuttafarm.web.views.auth``
============================
.. automodule:: wuttafarm.web.views.auth
:members:

View file

@ -0,0 +1,6 @@
``wuttafarm.web.views.common``
==============================
.. automodule:: wuttafarm.web.views.common
:members:

View file

@ -0,0 +1,6 @@
``wuttafarm.web.views.farmos.animal_types``
===========================================
.. automodule:: wuttafarm.web.views.farmos.animal_types
:members:

View file

@ -0,0 +1,6 @@
``wuttafarm.web.views.farmos.animals``
======================================
.. automodule:: wuttafarm.web.views.farmos.animals
:members:

View file

@ -0,0 +1,6 @@
``wuttafarm.web.views.farmos.asset_types``
==========================================
.. automodule:: wuttafarm.web.views.farmos.asset_types
:members:

View file

@ -0,0 +1,6 @@
``wuttafarm.web.views.farmos.groups``
=====================================
.. automodule:: wuttafarm.web.views.farmos.groups
:members:

View file

@ -0,0 +1,6 @@
``wuttafarm.web.views.farmos.land_assets``
==========================================
.. automodule:: wuttafarm.web.views.farmos.land_assets
:members:

View file

@ -0,0 +1,6 @@
``wuttafarm.web.views.farmos.land_types``
=========================================
.. automodule:: wuttafarm.web.views.farmos.land_types
:members:

View file

@ -0,0 +1,6 @@
``wuttafarm.web.views.farmos.log_types``
========================================
.. automodule:: wuttafarm.web.views.farmos.log_types
:members:

View file

@ -0,0 +1,6 @@
``wuttafarm.web.views.farmos.logs_activity``
============================================
.. automodule:: wuttafarm.web.views.farmos.logs_activity
:members:

View file

@ -0,0 +1,6 @@
``wuttafarm.web.views.farmos.master``
=====================================
.. automodule:: wuttafarm.web.views.farmos.master
:members:

View file

@ -0,0 +1,6 @@
``wuttafarm.web.views.farmos``
==============================
.. automodule:: wuttafarm.web.views.farmos
:members:

View file

@ -0,0 +1,6 @@
``wuttafarm.web.views.farmos.structure_types``
==============================================
.. automodule:: wuttafarm.web.views.farmos.structure_types
:members:

View file

@ -0,0 +1,6 @@
``wuttafarm.web.views.farmos.structures``
=========================================
.. automodule:: wuttafarm.web.views.farmos.structures
:members:

View file

@ -0,0 +1,6 @@
``wuttafarm.web.views.farmos.users``
====================================
.. automodule:: wuttafarm.web.views.farmos.users
:members:

View file

@ -0,0 +1,6 @@
``wuttafarm.web.views``
=======================
.. automodule:: wuttafarm.web.views
:members:

40
docs/conf.py Normal file
View file

@ -0,0 +1,40 @@
# Configuration file for the Sphinx documentation builder.
#
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
from importlib.metadata import version as get_version
project = "WuttaFarm"
copyright = "2026, Lance Edgar"
author = "Lance Edgar"
release = get_version("WuttaFarm")
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.intersphinx",
"sphinx.ext.viewcode",
"sphinx.ext.todo",
"sphinxcontrib.programoutput",
]
templates_path = ["_templates"]
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
intersphinx_mapping = {
"wuttjamaican": ("https://docs.wuttaproject.org/wuttjamaican/", None),
"wutta-continuum": ("https://docs.wuttaproject.org/wutta-continuum/", None),
}
# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
html_theme = "furo"
html_static_path = ["_static"]

84
docs/index.rst Normal file
View file

@ -0,0 +1,84 @@
WuttaFarm
=========
This is a Python web app (built with `WuttaWeb`_), to integrate with
and extend `farmOS`_.
.. _WuttaWeb: https://wuttaproject.org
.. _farmOS: https://farmos.org
It is just an experiment so far; the ideas I hope to play with
include:
- display farmOS data directly, via real-time API fetch
- add "mirror" schema and sync data from farmOS to app DB (and display it)
- possibly add more schema / extra features
- possibly sync data back to farmOS
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
:target: https://github.com/psf/black
.. toctree::
:maxdepth: 2
:caption: Documentation:
narr/install
narr/auth
narr/features
narr/cli
.. toctree::
:maxdepth: 1
:caption: Package API:
api/wuttafarm
api/wuttafarm.app
api/wuttafarm.auth
api/wuttafarm.cli
api/wuttafarm.cli.base
api/wuttafarm.cli.import_farmos
api/wuttafarm.cli.install
api/wuttafarm.config
api/wuttafarm.db
api/wuttafarm.db.model
api/wuttafarm.farmos
api/wuttafarm.farmos.handler
api/wuttafarm.importing
api/wuttafarm.importing.farmos
api/wuttafarm.install
api/wuttafarm.web
api/wuttafarm.web.app
api/wuttafarm.web.forms
api/wuttafarm.web.forms.schema
api/wuttafarm.web.forms.widgets
api/wuttafarm.web.menus
api/wuttafarm.web.static
api/wuttafarm.web.subscribers
api/wuttafarm.web.util
api/wuttafarm.web.views
api/wuttafarm.web.views.auth
api/wuttafarm.web.views.common
api/wuttafarm.web.views.farmos
api/wuttafarm.web.views.farmos.animals
api/wuttafarm.web.views.farmos.animal_types
api/wuttafarm.web.views.farmos.asset_types
api/wuttafarm.web.views.farmos.groups
api/wuttafarm.web.views.farmos.land_assets
api/wuttafarm.web.views.farmos.land_types
api/wuttafarm.web.views.farmos.logs_activity
api/wuttafarm.web.views.farmos.log_types
api/wuttafarm.web.views.farmos.master
api/wuttafarm.web.views.farmos.structures
api/wuttafarm.web.views.farmos.structure_types
api/wuttafarm.web.views.farmos.users
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

35
docs/make.bat Normal file
View file

@ -0,0 +1,35 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=.
set BUILDDIR=_build
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.https://www.sphinx-doc.org/
exit /b 1
)
if "%1" == "" goto help
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
:end
popd

58
docs/narr/auth.rst Normal file
View file

@ -0,0 +1,58 @@
==============
Authentication
==============
At the moment, the expected user login process is as follows:
First Launch
------------
When you first visit the app, it will not have any user accounts so
you will be shown a form to create one.
The username should ideally match your main (daily driver) username
within farmOS. The password you give can be anything though, does not
need to (and perhaps should not) match farmOS.
This account will belong to the Administrator role within WuttaFarm,
which means it can "become root" (same concept as ``sudo`` basically).
Once the account is created you will be shown the normal login page.
Go ahead and login with this account using the username and password
you gave it. But then you should logout again, for the next step.
OAuth2
------
The assumption (for now) is that users will login via farmOS / OAuth2
for normal operations. Doing so will embed the access token within
the WuttaFarm app user session, which means the user can actually
browse farmOS data within the WuttaFarm views.
.. note::
If you login to WuttaFarm directly with username/password, then
your user session will not have a farmOS access token and so the
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
will initiate the OAuth2 workflow, at which point you may be asked to
login to farmOS (if you're not already) and if you wish to grant
access to the 3rd party app (WuttaFarm; if you didn't already).
If all goes well you should be back in WuttaFarm, logged in as the
same username you have in farmOS.
Note that your first admin user in WuttaFarm ideally *should* have the
same username as farmOS, but regardless when you login via OAuth2, a
user account will be automatically created if necessary in WuttaFarm,
with that same username.

39
docs/narr/cli.rst Normal file
View 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

122
docs/narr/features.rst Normal file
View file

@ -0,0 +1,122 @@
========
Features
========
Here is the list of features currently supported:
* login via farmOS / OAuth2 workflows
* Authorization Code workflow is supported
* (technically, Password Grant workflow is also supported, for now)
* view some farmOS data directly
* limited data is fetched via farmOS API for several views
* performance isn't bad, but data is not very "complete"
* 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)
* export some data back to farmOS
* limited data is exported back via farmOS API, from native tables
* supported tables are auto-synced when a record is created/updated
* AnimalType
* AnimalAsset
* GroupAsset
* LandAsset
* StructureAsset
How I Use This App
------------------
My production farmOS instance is deployed via Podman container, which
I prefer over Docker. (Not that I know much about any of that
really.) It has a PostgreSQL database which runs in a separate
container.
My production WuttaFarm instance is installed directly on the same
host machine, in a Python virtual environment. PostgreSQL is also
installed on the host machine; the app uses that for DB.
I ran the initial "special" import to establish the user accounts;
then I ran the "full" import (farmOS → WuttaFarm). See also
:doc:`/narr/install`.
I configured a cron job to run the full import every night, but in
dry-run mode with warnings. This means I will get an email if
WuttaFarm is ever out of sync with farmOS.
With all that in place, I can use WuttaFarm as my "daily driver" to
add/edit assets (and soon, logs). Changes I make are immediately
synced to farmOS, so as long as the overnight check does not send me
an email, I know everything is good.
Roadmap
-------
Here are some things I still have planned so far:
* finish support for auto-sync, in current asset models
* must make "asset parents" editable
* add more asset models?
* i may only add those i need for now, but others can add more
* flesh out the log model support
* add more tables, fields to schema
* add/improve import and export
* basically this should be as good as the asset model support
* although again i may only add those i need for now
* add custom "quick forms" for assets and logs
* again i probably will just add a few (e.g. egg collection)
* but this could be an interesting path to go down, we'll see
* add custom "CSV/file importers"
* the framework has some pretty neat tools around this, so..
* ..even if i don't need CSV import i'd like to show what's possible
Notably **off the table** for now are:
* anything involving maps
* file/image attachments
I will just import "thumbnail" and "large" image URLs from farmOS for
each asset for now. Will have to think more on the image/attachment
stuff before I'll know if/how to add support in WuttaFarm.
Maps will wait mostly because I have never done anything involving
those (or GIS etc. - if that's even the right term). And anyway the
main "use" for this app is probably around data entry, so it may never
"need" maps support.
Screenshots
-----------
Login Screen
~~~~~~~~~~~~
.. image:: https://wuttaproject.org/images/wuttafarm/screenshot001.png
List All Assets
~~~~~~~~~~~~~~~
.. image:: https://wuttaproject.org/images/wuttafarm/screenshot002.png
View Animal Asset
~~~~~~~~~~~~~~~~~
.. image:: https://wuttaproject.org/images/wuttafarm/screenshot003.png
Edit Animal Asset
~~~~~~~~~~~~~~~~~
.. image:: https://wuttaproject.org/images/wuttafarm/screenshot004.png

185
docs/narr/install.rst Normal file
View file

@ -0,0 +1,185 @@
==============
Installation
==============
For now, these instructions mostly reflect my own dev workflow. It
uses a Python virtual environment but no (Docker) containers.
Eventually it may make sense to add production deployment steps using
Docker etc. - but that will wait for now.
Requirements
------------
WuttaFarm is designed to run on a (Debian-based) Linux machine; YMMV
with others.
farmOS
~~~~~~
First you must have a *production* `farmOS`_ instance running
somewhere. For more on that see `Hosting farmOS`_.
.. _farmOS: https://farmos.org
.. _Hosting farmOS: https://farmos.org/hosting/
This must use HTTPS for the OAuth2 workflows to work correctly. (Not
sure but it may also need to be at the root of the domain, i.e. no
subpath.)
Database
~~~~~~~~
You also must create a PostgreSQL (or MySQL) database for the
WuttaFarm app to use. See also :ref:`wuttjamaican:create-appdb`.
App Setup
---------
The short version:
.. code-block:: sh
python3 -m venv ./venv
./venv/bin/pip install WuttaFarm
./venv/bin/wuttafarm install
The app installer (last command above) will prompt you for DB
credentials, and the farmOS URL.
One of the questions is about data versioning with
:doc:`wutta-continuum:index`. You should probaby enable that even
though as of writing the default is disabled. It adds "revision
history" for most types of records in the WuttaFarm app DB.
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
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
Email Setup
-----------
WuttaFarm can send emails of various kinds; of note are:
* when user submits Feedback via button in top right of screen
* importer diff warning for farmOS → WuttaFarm
That last one is optional, triggered via the ``-W`` flag in the
importer command line.
Anyway the app basically assumes there is a Postfix or similar mail
server running on "localhost" which it can use as the SMTP server, and
which is in turn responsible for "really" sending the email out via
some configured relay. This has always worked very well for me since
I tend to want to have email working for other reasons on each Linux
server I maintain. (And since I have not traditionally used Docker
and/or containers.)
So if you need something else, touch base and we'll figure something
out. But assuming localhost is okay to use:
In the web app menu, see Admin -> App Info and then click Configure.
Check the box to enable email and plug in the default sender and
recipient (which should be the admin responsible for the app). I
often create an alias so I can use e.g. wuttafarm@edbob.org as
sender - aliased back to myself in case it generates bounces so I can
see them.
From there you can also see Admin -> Email Settings in the menu; this
lets you control and preview each type of email separately.
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

View file

@ -5,7 +5,7 @@ build-backend = "hatchling.build"
[project]
name = "WuttaFarm"
version = "0.1.0"
version = "0.7.0"
description = "Web app to integrate with and extend farmOS"
readme = "README.md"
authors = [
@ -31,28 +31,42 @@ license = {text = "GNU General Public License v3"}
dependencies = [
"farmOS",
"psycopg2",
"WuttaWeb[continuum]",
"pyramid_exclog",
"uvicorn[standard]",
"WuttaSync",
"WuttaWeb[continuum]>=0.29.0",
]
[project.optional-dependencies]
docs = ["Sphinx", "furo", "sphinxcontrib-programoutput"]
[project.scripts]
"wuttafarm" = "wuttafarm.cli:wuttafarm_typer"
[project.entry-points."paste.app_factory"]
"main" = "wuttafarm.web.app:main"
[project.entry-points."wutta.app.providers"]
wuttafarm = "wuttafarm.app:WuttaFarmAppProvider"
[project.entry-points."wutta.config.extensions"]
"wuttafarm" = "wuttafarm.config:WuttaFarmConfig"
[project.entry-points."wutta.web.menus"]
"wuttafarm" = "wuttafarm.web.menus:WuttaFarmMenuHandler"
[project.entry-points."wuttasync.importing"]
"export.to_farmos.from_wuttafarm" = "wuttafarm.farmos.importing.wuttafarm:FromWuttaFarmToFarmOS"
"import.to_wuttafarm.from_farmos" = "wuttafarm.importing.farmos:FromFarmOSToWuttaFarm"
[project.urls]
Homepage = "https://forgejo.wuttaproject.org/lance/wuttafarm"
Repository = "https://forgejo.wuttaproject.org/lance/wuttafarm"
Issues = "https://forgejo.wuttaproject.org/lance/wuttafarm/issues"
Changelog = "https://forgejo.wuttaproject.org/lance/wuttafarm/src/branch/master/CHANGELOG.md"
Homepage = "https://forgejo.wuttaproject.org/wutta/wuttafarm"
Repository = "https://forgejo.wuttaproject.org/wutta/wuttafarm"
Issues = "https://forgejo.wuttaproject.org/wutta/wuttafarm/issues"
Changelog = "https://forgejo.wuttaproject.org/wutta/wuttafarm/src/branch/master/CHANGELOG.md"
[tool.commitizen]
@ -62,3 +76,8 @@ update_changelog_on_bump = true
[tool.hatch.build.targets.wheel]
packages = ["src/wuttafarm"]
[tool.hatch.build.targets.sdist]
exclude = [
"style/node_modules/",
]

View file

@ -31,7 +31,25 @@ class WuttaFarmAppHandler(base.AppHandler):
Custom :term:`app handler` for WuttaFarm.
"""
display_format_datetime = "%a, %m/%d/%Y - %H:%M"
default_auth_handler_spec = "wuttafarm.auth:WuttaFarmAuthHandler"
default_install_handler_spec = "wuttafarm.install:WuttaFarmInstallHandler"
def get_asset_handler(self):
"""
Get the configured asset handler.
:rtype: :class:`~wuttafarm.assets.AssetHandler`
"""
if "asset" not in self.handlers:
spec = self.config.get(
f"{self.appname}.asset_handler",
default="wuttafarm.assets:AssetHandler",
)
factory = self.load_object(spec)
self.handlers["asset"] = factory(self.config)
return self.handlers["asset"]
def get_farmos_handler(self):
"""
@ -42,16 +60,167 @@ class WuttaFarmAppHandler(base.AppHandler):
if "farmos" not in self.handlers:
spec = self.config.get(
f"{self.appname}.farmos_handler",
default="wuttafarm.farmos:FarmOSHandler",
default="wuttafarm.farmos.handler:FarmOSHandler",
)
factory = self.load_object(spec)
self.handlers["farmos"] = factory(self.config)
return self.handlers["farmos"]
def get_farmos_integration_mode(self):
"""
Returns the integration mode for farmOS, i.e. to control the
app's behavior regarding that.
"""
enum = self.enum
return self.config.get(
f"{self.appname}.farmos_integration_mode",
default=enum.FARMOS_INTEGRATION_MODE_WRAPPER,
)
def is_farmos_mirror(self):
"""
Returns ``True`` if the app is configured in "mirror"
integration mode with regard to farmOS.
"""
enum = self.enum
mode = self.get_farmos_integration_mode()
return mode == enum.FARMOS_INTEGRATION_MODE_MIRROR
def is_farmos_wrapper(self):
"""
Returns ``True`` if the app is configured in "wrapper"
integration mode with regard to farmOS.
"""
enum = self.enum
mode = self.get_farmos_integration_mode()
return mode == enum.FARMOS_INTEGRATION_MODE_WRAPPER
def is_standalone(self):
"""
Returns ``True`` if the app is configured in "standalone" mode
with regard to farmOS.
"""
enum = self.enum
mode = self.get_farmos_integration_mode()
return mode == enum.FARMOS_INTEGRATION_MODE_NONE
def get_farmos_url(self, *args, **kwargs):
"""
Get a farmOS URL. This is a convenience wrapper around
:meth:`~wuttafarm.farmos.FarmOSHandler.get_farmos_url()`.
:meth:`~wuttafarm.farmos.handler.FarmOSHandler.get_farmos_url()`.
"""
handler = self.get_farmos_handler()
return handler.get_farmos_url(*args, **kwargs)
def get_farmos_client(self, *args, **kwargs):
"""
Get a farmOS client. This is a convenience wrapper around
:meth:`~wuttafarm.farmos.handler.FarmOSHandler.get_farmos_client()`.
"""
handler = self.get_farmos_handler()
return handler.get_farmos_client(*args, **kwargs)
def is_farmos_3x(self, *args, **kwargs):
"""
Check if the farmOS version is 3.x. This is a convenience
wrapper around
:meth:`~wuttafarm.farmos.handler.FarmOSHandler.is_farmos_3x()`.
"""
handler = self.get_farmos_handler()
return handler.is_farmos_3x(*args, **kwargs)
def is_farmos_4x(self, *args, **kwargs):
"""
Check if the farmOS version is 4.x. This is a convenience
wrapper around
:meth:`~wuttafarm.farmos.handler.FarmOSHandler.is_farmos_4x()`.
"""
handler = self.get_farmos_handler()
return handler.is_farmos_4x(*args, **kwargs)
def get_normalizer(self, farmos_client=None):
"""
Get the configured farmOS integration handler.
:rtype: :class:`~wuttafarm.farmos.FarmOSHandler`
"""
spec = self.config.get(
f"{self.appname}.normalizer_spec",
default="wuttafarm.normal:Normalizer",
)
factory = self.load_object(spec)
return factory(self.config, farmos_client)
def auto_sync_to_farmos(self, obj, model_name=None, client=None, require=True):
"""
Export the given object to farmOS, using configured handler.
This should ensure the given object is also *updated* with the
farmOS UUID and Drupal ID, when new record is created in
farmOS.
:param obj: Any data object in WuttaFarm, e.g. AnimalAsset
instance.
:param client: Existing farmOS API client to use. If not
specified, a new one will be instantiated.
:param require: If true, this will *require* the export
handler to support objects of the given type. If false,
then nothing will happen / export is silently skipped when
there is no such exporter.
"""
handler = self.app.get_import_handler("export.to_farmos.from_wuttafarm")
if not model_name:
model_name = type(obj).__name__
if model_name not in handler.importers:
if require:
raise ValueError(f"no exporter found for {model_name}")
return
# nb. begin txn to establish the API client
handler.begin_target_transaction(client)
importer = handler.get_importer(model_name, caches_target=False)
normal = importer.normalize_source_object(obj)
importer.process_data(source_data=[normal])
def auto_sync_from_farmos(self, obj, model_name, client=None, require=True):
"""
Import the given object from farmOS, using configured handler.
:param obj: Any data record from farmOS.
:param model_name': Model name for the importer to use,
e.g. ``"AnimalAsset"``.
:param client: Existing farmOS API client to use. If not
specified, a new one will be instantiated.
:param require: If true, this will *require* the import
handler to support objects of the given type. If false,
then nothing will happen / import is silently skipped when
there is no such importer.
"""
handler = self.app.get_import_handler("import.to_wuttafarm.from_farmos")
if model_name not in handler.importers:
if require:
raise ValueError(f"no importer found for {model_name}")
return
# nb. begin txn to establish the API client
handler.begin_source_transaction(client)
with self.short_session(commit=True) as session:
handler.target_session = session
importer = handler.get_importer(model_name, caches_target=False)
normal = importer.normalize_source_object(obj)
importer.process_data(source_data=[normal])
class WuttaFarmAppProvider(base.AppProvider):
"""
The :term:`app provider` for WuttaFarm.
"""
email_modules = ["wuttafarm.emails"]

65
src/wuttafarm/assets.py Normal file
View file

@ -0,0 +1,65 @@
# -*- coding: utf-8; -*-
################################################################################
#
# WuttaFarm --Web app to integrate with and extend farmOS
# Copyright © 2026 Lance Edgar
#
# This file is part of WuttaFarm.
#
# WuttaFarm is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# WuttaFarm is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# WuttaFarm. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Asset handler
"""
from wuttjamaican.app import GenericHandler
class AssetHandler(GenericHandler):
"""
Base class and default implementation for the asset
:term:`handler`.
"""
def get_groups(self, asset):
model = self.app.model
session = self.app.get_session(asset)
grplog = (
session.query(model.Log)
.join(model.LogAsset)
.filter(model.LogAsset.asset == asset)
.filter(model.Log.is_group_assignment == True)
.order_by(model.Log.timestamp.desc())
.first()
)
if grplog:
return grplog.groups
return []
def get_locations(self, asset):
model = self.app.model
session = self.app.get_session(asset)
loclog = (
session.query(model.Log)
.join(model.LogAsset)
.filter(model.LogAsset.asset == asset)
.filter(model.Log.is_movement == True)
.order_by(model.Log.timestamp.desc())
.first()
)
if loclog:
return loclog.locations
return []

View file

@ -25,7 +25,6 @@ Auth handler for use with farmOS
from uuid import UUID
from farmOS import farmOS
from oauthlib.oauth2.rfc6749.errors import InvalidGrantError
from sqlalchemy import orm
@ -89,8 +88,7 @@ class WuttaFarmAuthHandler(AuthHandler):
return None
def get_farmos_oauth2_token(self, username, password):
url = self.app.get_farmos_url()
client = farmOS(url)
client = self.app.get_farmos_client()
try:
return client.authorize(username=username, password=password)
except InvalidGrantError:

View 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
"""
from .base import wuttafarm_typer
# nb. must bring in all modules for discovery to work
from . import export_farmos
from . import import_farmos
from . import install

31
src/wuttafarm/cli/base.py Normal file
View 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"
)

View 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-export-farmos`
"""
import typer
from wuttasync.cli import import_command, ImportCommandHandler
from wuttafarm.cli import wuttafarm_typer
@wuttafarm_typer.command()
@import_command
def export_farmos(ctx: typer.Context, **kwargs):
"""
Export data from WuttaFarm to farmOS API
"""
config = ctx.parent.wutta_config
handler = ImportCommandHandler(config, key="export.to_farmos.from_wuttafarm")
handler.run(ctx)

View 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)

View file

@ -25,12 +25,7 @@ WuttaFarm CLI
import typer
from wuttjamaican.cli import make_typer
wuttafarm_typer = make_typer(
name="wuttafarm", help="WuttaFarm -- Web app to integrate with and extend farmOS"
)
from wuttafarm.cli import wuttafarm_typer
@wuttafarm_typer.command()

View file

@ -23,6 +23,8 @@
WuttaFarm config extensions
"""
import os
from wuttjamaican.conf import WuttaConfigExtension
@ -39,19 +41,26 @@ class WuttaFarmConfig(WuttaConfigExtension):
config.setdefault(f"{config.appname}.app_title", "WuttaFarm")
config.setdefault(f"{config.appname}.app_dist", "WuttaFarm")
# app model
# app model/enum
config.setdefault(f"{config.appname}.model_spec", "wuttafarm.db.model")
config.setdefault(f"{config.appname}.enum_spec", "wuttafarm.enum")
# app handler
config.setdefault(
f"{config.appname}.app.handler", "wuttafarm.app:WuttaFarmAppHandler"
)
# web app menu
# web app stuff
config.setdefault(
f"{config.appname}.web.menus.handler.spec",
f"{config.appname}.web.menus.handler.default_spec",
"wuttafarm.web.menus:WuttaFarmMenuHandler",
)
config.setdefault("wuttaweb.grids.default_pagesize", "50")
# web app libcache
# config.setdefault('wuttaweb.static_libcache.module', 'wuttafarm.web.static')
# maybe override cert validation for requests lib.
# nb. this is "global" and not "specific" to the farmos API requests!
if bundle := config.get(f"{config.appname}.requests_ca_bundle"):
os.environ.setdefault("REQUESTS_CA_BUNDLE", bundle)

View file

@ -0,0 +1,37 @@
"""add Log.is_movement
Revision ID: 0771322957bd
Revises: 12de43facb95
Create Date: 2026-03-02 20:21:03.889847
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import wuttjamaican.db.util
# revision identifiers, used by Alembic.
revision: str = "0771322957bd"
down_revision: Union[str, None] = "12de43facb95"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# log
op.add_column("log", sa.Column("is_movement", sa.Boolean(), nullable=True))
op.add_column(
"log_version",
sa.Column("is_movement", sa.Boolean(), autoincrement=False, nullable=True),
)
def downgrade() -> None:
# log
op.drop_column("log_version", "is_movement")
op.drop_column("log", "is_movement")

View file

@ -0,0 +1,596 @@
"""add Plant Assets and more Logs
Revision ID: 11e0e46f48a6
Revises: dd6351e69233
Create Date: 2026-02-18 18:11:46.536930
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import wuttjamaican.db.util
# revision identifiers, used by Alembic.
revision: str = "11e0e46f48a6"
down_revision: Union[str, None] = "dd6351e69233"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# plant_type
op.create_table(
"plant_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.Integer(), nullable=True),
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_plant_type")),
sa.UniqueConstraint("drupal_id", name=op.f("uq_plant_type_drupal_id")),
sa.UniqueConstraint("farmos_uuid", name=op.f("uq_plant_type_farmos_uuid")),
sa.UniqueConstraint("name", name=op.f("uq_plant_type_name")),
)
op.create_table(
"plant_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_plant_type_version")
),
)
op.create_index(
op.f("ix_plant_type_version_end_transaction_id"),
"plant_type_version",
["end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_plant_type_version_operation_type"),
"plant_type_version",
["operation_type"],
unique=False,
)
op.create_index(
"ix_plant_type_version_pk_transaction_id",
"plant_type_version",
["uuid", sa.literal_column("transaction_id DESC")],
unique=False,
)
op.create_index(
"ix_plant_type_version_pk_validity",
"plant_type_version",
["uuid", "transaction_id", "end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_plant_type_version_transaction_id"),
"plant_type_version",
["transaction_id"],
unique=False,
)
# asset_plant
op.create_table(
"asset_plant",
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.ForeignKeyConstraint(
["uuid"], ["asset.uuid"], name=op.f("fk_asset_plant_uuid_asset")
),
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_asset_plant")),
)
op.create_table(
"asset_plant_version",
sa.Column(
"uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
),
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_plant_version")
),
)
op.create_index(
op.f("ix_asset_plant_version_end_transaction_id"),
"asset_plant_version",
["end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_asset_plant_version_operation_type"),
"asset_plant_version",
["operation_type"],
unique=False,
)
op.create_index(
"ix_asset_plant_version_pk_transaction_id",
"asset_plant_version",
["uuid", sa.literal_column("transaction_id DESC")],
unique=False,
)
op.create_index(
"ix_asset_plant_version_pk_validity",
"asset_plant_version",
["uuid", "transaction_id", "end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_asset_plant_version_transaction_id"),
"asset_plant_version",
["transaction_id"],
unique=False,
)
# asset_plant_plant_type
op.create_table(
"asset_plant_plant_type",
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.Column("plant_asset_uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.Column("plant_type_uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.ForeignKeyConstraint(
["plant_asset_uuid"],
["asset_plant.uuid"],
name=op.f("fk_asset_plant_plant_type_plant_asset_uuid_asset_plant"),
),
sa.ForeignKeyConstraint(
["plant_type_uuid"],
["plant_type.uuid"],
name=op.f("fk_asset_plant_plant_type_plant_type_uuid_plant_type"),
),
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_asset_plant_plant_type")),
)
op.create_table(
"asset_plant_plant_type_version",
sa.Column(
"uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
),
sa.Column(
"plant_asset_uuid",
wuttjamaican.db.util.UUID(),
autoincrement=False,
nullable=True,
),
sa.Column(
"plant_type_uuid",
wuttjamaican.db.util.UUID(),
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_plant_plant_type_version")
),
)
op.create_index(
op.f("ix_asset_plant_plant_type_version_end_transaction_id"),
"asset_plant_plant_type_version",
["end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_asset_plant_plant_type_version_operation_type"),
"asset_plant_plant_type_version",
["operation_type"],
unique=False,
)
op.create_index(
"ix_asset_plant_plant_type_version_pk_transaction_id",
"asset_plant_plant_type_version",
["uuid", sa.literal_column("transaction_id DESC")],
unique=False,
)
op.create_index(
"ix_asset_plant_plant_type_version_pk_validity",
"asset_plant_plant_type_version",
["uuid", "transaction_id", "end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_asset_plant_plant_type_version_transaction_id"),
"asset_plant_plant_type_version",
["transaction_id"],
unique=False,
)
# log_asset
op.create_table(
"log_asset",
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.Column("log_uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.Column("asset_uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.ForeignKeyConstraint(
["asset_uuid"], ["asset.uuid"], name=op.f("fk_log_asset_asset_uuid_asset")
),
sa.ForeignKeyConstraint(
["log_uuid"], ["log.uuid"], name=op.f("fk_log_asset_log_uuid_log")
),
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_log_asset")),
)
op.create_table(
"log_asset_version",
sa.Column(
"uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
),
sa.Column(
"log_uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=True
),
sa.Column(
"asset_uuid",
wuttjamaican.db.util.UUID(),
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_asset_version")
),
)
op.create_index(
op.f("ix_log_asset_version_end_transaction_id"),
"log_asset_version",
["end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_log_asset_version_operation_type"),
"log_asset_version",
["operation_type"],
unique=False,
)
op.create_index(
"ix_log_asset_version_pk_transaction_id",
"log_asset_version",
["uuid", sa.literal_column("transaction_id DESC")],
unique=False,
)
op.create_index(
"ix_log_asset_version_pk_validity",
"log_asset_version",
["uuid", "transaction_id", "end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_log_asset_version_transaction_id"),
"log_asset_version",
["transaction_id"],
unique=False,
)
# log_harvest
op.create_table(
"log_harvest",
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.ForeignKeyConstraint(
["uuid"], ["log.uuid"], name=op.f("fk_log_harvest_uuid_log")
),
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_log_harvest")),
)
op.create_table(
"log_harvest_version",
sa.Column(
"uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
),
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_harvest_version")
),
)
op.create_index(
op.f("ix_log_harvest_version_end_transaction_id"),
"log_harvest_version",
["end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_log_harvest_version_operation_type"),
"log_harvest_version",
["operation_type"],
unique=False,
)
op.create_index(
"ix_log_harvest_version_pk_transaction_id",
"log_harvest_version",
["uuid", sa.literal_column("transaction_id DESC")],
unique=False,
)
op.create_index(
"ix_log_harvest_version_pk_validity",
"log_harvest_version",
["uuid", "transaction_id", "end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_log_harvest_version_transaction_id"),
"log_harvest_version",
["transaction_id"],
unique=False,
)
# log_medical
op.create_table(
"log_medical",
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.ForeignKeyConstraint(
["uuid"], ["log.uuid"], name=op.f("fk_log_medical_uuid_log")
),
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_log_medical")),
)
op.create_table(
"log_medical_version",
sa.Column(
"uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
),
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_medical_version")
),
)
op.create_index(
op.f("ix_log_medical_version_end_transaction_id"),
"log_medical_version",
["end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_log_medical_version_operation_type"),
"log_medical_version",
["operation_type"],
unique=False,
)
op.create_index(
"ix_log_medical_version_pk_transaction_id",
"log_medical_version",
["uuid", sa.literal_column("transaction_id DESC")],
unique=False,
)
op.create_index(
"ix_log_medical_version_pk_validity",
"log_medical_version",
["uuid", "transaction_id", "end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_log_medical_version_transaction_id"),
"log_medical_version",
["transaction_id"],
unique=False,
)
# log_observation
op.create_table(
"log_observation",
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.ForeignKeyConstraint(
["uuid"], ["log.uuid"], name=op.f("fk_log_observation_uuid_log")
),
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_log_observation")),
)
op.create_table(
"log_observation_version",
sa.Column(
"uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
),
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_observation_version")
),
)
op.create_index(
op.f("ix_log_observation_version_end_transaction_id"),
"log_observation_version",
["end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_log_observation_version_operation_type"),
"log_observation_version",
["operation_type"],
unique=False,
)
op.create_index(
"ix_log_observation_version_pk_transaction_id",
"log_observation_version",
["uuid", sa.literal_column("transaction_id DESC")],
unique=False,
)
op.create_index(
"ix_log_observation_version_pk_validity",
"log_observation_version",
["uuid", "transaction_id", "end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_log_observation_version_transaction_id"),
"log_observation_version",
["transaction_id"],
unique=False,
)
def downgrade() -> None:
# log_observation
op.drop_index(
op.f("ix_log_observation_version_transaction_id"),
table_name="log_observation_version",
)
op.drop_index(
"ix_log_observation_version_pk_validity", table_name="log_observation_version"
)
op.drop_index(
"ix_log_observation_version_pk_transaction_id",
table_name="log_observation_version",
)
op.drop_index(
op.f("ix_log_observation_version_operation_type"),
table_name="log_observation_version",
)
op.drop_index(
op.f("ix_log_observation_version_end_transaction_id"),
table_name="log_observation_version",
)
op.drop_table("log_observation_version")
op.drop_table("log_observation")
# log_medical
op.drop_index(
op.f("ix_log_medical_version_transaction_id"), table_name="log_medical_version"
)
op.drop_index(
"ix_log_medical_version_pk_validity", table_name="log_medical_version"
)
op.drop_index(
"ix_log_medical_version_pk_transaction_id", table_name="log_medical_version"
)
op.drop_index(
op.f("ix_log_medical_version_operation_type"), table_name="log_medical_version"
)
op.drop_index(
op.f("ix_log_medical_version_end_transaction_id"),
table_name="log_medical_version",
)
op.drop_table("log_medical_version")
op.drop_table("log_medical")
# log_harvest
op.drop_index(
op.f("ix_log_harvest_version_transaction_id"), table_name="log_harvest_version"
)
op.drop_index(
"ix_log_harvest_version_pk_validity", table_name="log_harvest_version"
)
op.drop_index(
"ix_log_harvest_version_pk_transaction_id", table_name="log_harvest_version"
)
op.drop_index(
op.f("ix_log_harvest_version_operation_type"), table_name="log_harvest_version"
)
op.drop_index(
op.f("ix_log_harvest_version_end_transaction_id"),
table_name="log_harvest_version",
)
op.drop_table("log_harvest_version")
op.drop_table("log_harvest")
# log_asset
op.drop_index(
op.f("ix_log_asset_version_transaction_id"), table_name="log_asset_version"
)
op.drop_index("ix_log_asset_version_pk_validity", table_name="log_asset_version")
op.drop_index(
"ix_log_asset_version_pk_transaction_id", table_name="log_asset_version"
)
op.drop_index(
op.f("ix_log_asset_version_operation_type"), table_name="log_asset_version"
)
op.drop_index(
op.f("ix_log_asset_version_end_transaction_id"), table_name="log_asset_version"
)
op.drop_table("log_asset_version")
op.drop_table("log_asset")
# asset_plant_plant_type
op.drop_index(
op.f("ix_asset_plant_plant_type_version_transaction_id"),
table_name="asset_plant_plant_type_version",
)
op.drop_index(
"ix_asset_plant_plant_type_version_pk_validity",
table_name="asset_plant_plant_type_version",
)
op.drop_index(
"ix_asset_plant_plant_type_version_pk_transaction_id",
table_name="asset_plant_plant_type_version",
)
op.drop_index(
op.f("ix_asset_plant_plant_type_version_operation_type"),
table_name="asset_plant_plant_type_version",
)
op.drop_index(
op.f("ix_asset_plant_plant_type_version_end_transaction_id"),
table_name="asset_plant_plant_type_version",
)
op.drop_table("asset_plant_plant_type_version")
op.drop_table("asset_plant_plant_type")
# asset_plant
op.drop_index(
op.f("ix_asset_plant_version_transaction_id"), table_name="asset_plant_version"
)
op.drop_index(
"ix_asset_plant_version_pk_validity", table_name="asset_plant_version"
)
op.drop_index(
"ix_asset_plant_version_pk_transaction_id", table_name="asset_plant_version"
)
op.drop_index(
op.f("ix_asset_plant_version_operation_type"), table_name="asset_plant_version"
)
op.drop_index(
op.f("ix_asset_plant_version_end_transaction_id"),
table_name="asset_plant_version",
)
op.drop_table("asset_plant_version")
op.drop_table("asset_plant")
# plant_type
op.drop_index(
op.f("ix_plant_type_version_transaction_id"), table_name="plant_type_version"
)
op.drop_index("ix_plant_type_version_pk_validity", table_name="plant_type_version")
op.drop_index(
"ix_plant_type_version_pk_transaction_id", table_name="plant_type_version"
)
op.drop_index(
op.f("ix_plant_type_version_operation_type"), table_name="plant_type_version"
)
op.drop_index(
op.f("ix_plant_type_version_end_transaction_id"),
table_name="plant_type_version",
)
op.drop_table("plant_type_version")
op.drop_table("plant_type")

View file

@ -0,0 +1,114 @@
"""add Asset.owners
Revision ID: 12de43facb95
Revises: 85d4851e8292
Create Date: 2026-03-02 19:03:35.511398
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import wuttjamaican.db.util
# revision identifiers, used by Alembic.
revision: str = "12de43facb95"
down_revision: Union[str, None] = "85d4851e8292"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# asset_owner
op.create_table(
"asset_owner",
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.Column("asset_uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.Column("user_uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.ForeignKeyConstraint(
["asset_uuid"], ["asset.uuid"], name=op.f("fk_asset_owner_asset_uuid_asset")
),
sa.ForeignKeyConstraint(
["user_uuid"], ["user.uuid"], name=op.f("fk_asset_owner_user_uuid_user")
),
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_asset_owner")),
)
op.create_table(
"asset_owner_version",
sa.Column(
"uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
),
sa.Column(
"asset_uuid",
wuttjamaican.db.util.UUID(),
autoincrement=False,
nullable=True,
),
sa.Column(
"user_uuid", wuttjamaican.db.util.UUID(), 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_owner_version")
),
)
op.create_index(
op.f("ix_asset_owner_version_end_transaction_id"),
"asset_owner_version",
["end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_asset_owner_version_operation_type"),
"asset_owner_version",
["operation_type"],
unique=False,
)
op.create_index(
"ix_asset_owner_version_pk_transaction_id",
"asset_owner_version",
["uuid", sa.literal_column("transaction_id DESC")],
unique=False,
)
op.create_index(
"ix_asset_owner_version_pk_validity",
"asset_owner_version",
["uuid", "transaction_id", "end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_asset_owner_version_transaction_id"),
"asset_owner_version",
["transaction_id"],
unique=False,
)
def downgrade() -> None:
# asset_owner
op.drop_index(
op.f("ix_asset_owner_version_transaction_id"), table_name="asset_owner_version"
)
op.drop_index(
"ix_asset_owner_version_pk_validity", table_name="asset_owner_version"
)
op.drop_index(
"ix_asset_owner_version_pk_transaction_id", table_name="asset_owner_version"
)
op.drop_index(
op.f("ix_asset_owner_version_operation_type"), table_name="asset_owner_version"
)
op.drop_index(
op.f("ix_asset_owner_version_end_transaction_id"),
table_name="asset_owner_version",
)
op.drop_table("asset_owner_version")
op.drop_table("asset_owner")

View 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")

View file

@ -0,0 +1,119 @@
"""add Quantity Types
Revision ID: 1f98d27cabeb
Revises: ea88e72a5fa5
Create Date: 2026-02-18 21:03:52.245619
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import wuttjamaican.db.util
# revision identifiers, used by Alembic.
revision: str = "1f98d27cabeb"
down_revision: Union[str, None] = "ea88e72a5fa5"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# quantity_type
op.create_table(
"quantity_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_quantity_type")),
sa.UniqueConstraint("drupal_id", name=op.f("uq_quantity_type_drupal_id")),
sa.UniqueConstraint("farmos_uuid", name=op.f("uq_quantity_type_farmos_uuid")),
sa.UniqueConstraint("name", name=op.f("uq_quantity_type_name")),
)
op.create_table(
"quantity_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_quantity_type_version")
),
)
op.create_index(
op.f("ix_quantity_type_version_end_transaction_id"),
"quantity_type_version",
["end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_quantity_type_version_operation_type"),
"quantity_type_version",
["operation_type"],
unique=False,
)
op.create_index(
"ix_quantity_type_version_pk_transaction_id",
"quantity_type_version",
["uuid", sa.literal_column("transaction_id DESC")],
unique=False,
)
op.create_index(
"ix_quantity_type_version_pk_validity",
"quantity_type_version",
["uuid", "transaction_id", "end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_quantity_type_version_transaction_id"),
"quantity_type_version",
["transaction_id"],
unique=False,
)
def downgrade() -> None:
# quantity_type
op.drop_index(
op.f("ix_quantity_type_version_transaction_id"),
table_name="quantity_type_version",
)
op.drop_index(
"ix_quantity_type_version_pk_validity", table_name="quantity_type_version"
)
op.drop_index(
"ix_quantity_type_version_pk_transaction_id", table_name="quantity_type_version"
)
op.drop_index(
op.f("ix_quantity_type_version_operation_type"),
table_name="quantity_type_version",
)
op.drop_index(
op.f("ix_quantity_type_version_end_transaction_id"),
table_name="quantity_type_version",
)
op.drop_table("quantity_type_version")
op.drop_table("quantity_type")

View file

@ -0,0 +1,41 @@
"""add animal thumbnail url
Revision ID: 2a49127e974b
Revises: 8898184c5c75
Create Date: 2026-02-14 19:41:22.039343
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import wuttjamaican.db.util
# revision identifiers, used by Alembic.
revision: str = "2a49127e974b"
down_revision: Union[str, None] = "8898184c5c75"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# animal
op.add_column(
"animal", sa.Column("thumbnail_url", sa.String(length=255), nullable=True)
)
op.add_column(
"animal_version",
sa.Column(
"thumbnail_url", sa.String(length=255), autoincrement=False, nullable=True
),
)
def downgrade() -> None:
# animal
op.drop_column("animal_version", "thumbnail_url")
op.drop_column("animal", "thumbnail_url")

View file

@ -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")

View file

@ -0,0 +1,236 @@
"""use shared base for Structure Assets
Revision ID: 34ec51d80f52
Revises: d882682c82f9
Create Date: 2026-02-15 13:19:18.814523
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import wuttjamaican.db.util
# revision identifiers, used by Alembic.
revision: str = "34ec51d80f52"
down_revision: Union[str, None] = "d882682c82f9"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# asset_structure
op.create_table(
"asset_structure",
sa.Column("structure_type_uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.ForeignKeyConstraint(
["structure_type_uuid"],
["structure_type.uuid"],
name=op.f("fk_asset_structure_structure_type_uuid_structure_type"),
),
sa.ForeignKeyConstraint(
["uuid"], ["asset.uuid"], name=op.f("fk_asset_structure_uuid_asset")
),
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_asset_structure")),
)
op.create_table(
"asset_structure_version",
sa.Column(
"structure_type_uuid",
wuttjamaican.db.util.UUID(),
autoincrement=False,
nullable=True,
),
sa.Column(
"uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
),
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_structure_version")
),
)
op.create_index(
op.f("ix_asset_structure_version_end_transaction_id"),
"asset_structure_version",
["end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_asset_structure_version_operation_type"),
"asset_structure_version",
["operation_type"],
unique=False,
)
op.create_index(
"ix_asset_structure_version_pk_transaction_id",
"asset_structure_version",
["uuid", sa.literal_column("transaction_id DESC")],
unique=False,
)
op.create_index(
"ix_asset_structure_version_pk_validity",
"asset_structure_version",
["uuid", "transaction_id", "end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_asset_structure_version_transaction_id"),
"asset_structure_version",
["transaction_id"],
unique=False,
)
# structure
op.drop_index(
op.f("ix_structure_version_end_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_pk_transaction_id"), table_name="structure_version"
)
op.drop_index(
op.f("ix_structure_version_pk_validity"), table_name="structure_version"
)
op.drop_index(
op.f("ix_structure_version_transaction_id"), table_name="structure_version"
)
op.drop_table("structure_version")
op.drop_table("structure")
def downgrade() -> None:
# structure
op.create_table(
"structure",
sa.Column("uuid", sa.UUID(), autoincrement=False, nullable=False),
sa.Column("name", sa.VARCHAR(length=100), autoincrement=False, nullable=False),
sa.Column("archived", sa.BOOLEAN(), autoincrement=False, nullable=False),
sa.Column(
"structure_type_uuid", sa.UUID(), autoincrement=False, nullable=False
),
sa.Column("is_location", sa.BOOLEAN(), autoincrement=False, nullable=False),
sa.Column("is_fixed", sa.BOOLEAN(), autoincrement=False, nullable=False),
sa.Column("notes", sa.TEXT(), autoincrement=False, nullable=True),
sa.Column(
"image_url", sa.VARCHAR(length=255), autoincrement=False, nullable=True
),
sa.Column("farmos_uuid", sa.UUID(), autoincrement=False, nullable=True),
sa.Column("drupal_id", sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column(
"thumbnail_url", sa.VARCHAR(length=255), autoincrement=False, 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"),
postgresql_include=[],
postgresql_nulls_not_distinct=False,
),
sa.UniqueConstraint(
"farmos_uuid",
name=op.f("uq_structure_farmos_uuid"),
postgresql_include=[],
postgresql_nulls_not_distinct=False,
),
sa.UniqueConstraint(
"name",
name=op.f("uq_structure_name"),
postgresql_include=[],
postgresql_nulls_not_distinct=False,
),
)
op.create_table(
"structure_version",
sa.Column("uuid", sa.UUID(), autoincrement=False, nullable=False),
sa.Column("name", sa.VARCHAR(length=100), autoincrement=False, nullable=True),
sa.Column("archived", sa.BOOLEAN(), autoincrement=False, nullable=True),
sa.Column("structure_type_uuid", sa.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.VARCHAR(length=255), autoincrement=False, nullable=True
),
sa.Column("farmos_uuid", sa.UUID(), autoincrement=False, nullable=True),
sa.Column("drupal_id", sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column("transaction_id", sa.BIGINT(), autoincrement=False, nullable=False),
sa.Column(
"end_transaction_id", sa.BIGINT(), autoincrement=False, nullable=True
),
sa.Column("operation_type", sa.SMALLINT(), autoincrement=False, nullable=False),
sa.Column(
"thumbnail_url", sa.VARCHAR(length=255), autoincrement=False, nullable=True
),
sa.PrimaryKeyConstraint(
"uuid", "transaction_id", name=op.f("pk_structure_version")
),
)
op.create_index(
op.f("ix_structure_version_transaction_id"),
"structure_version",
["transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_structure_version_pk_validity"),
"structure_version",
["uuid", "transaction_id", "end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_structure_version_pk_transaction_id"),
"structure_version",
["uuid", sa.literal_column("transaction_id DESC")],
unique=False,
)
op.create_index(
op.f("ix_structure_version_operation_type"),
"structure_version",
["operation_type"],
unique=False,
)
op.create_index(
op.f("ix_structure_version_end_transaction_id"),
"structure_version",
["end_transaction_id"],
unique=False,
)
# asset_structure
op.drop_index(
op.f("ix_asset_structure_version_transaction_id"),
table_name="asset_structure_version",
)
op.drop_index(
"ix_asset_structure_version_pk_validity", table_name="asset_structure_version"
)
op.drop_index(
"ix_asset_structure_version_pk_transaction_id",
table_name="asset_structure_version",
)
op.drop_index(
op.f("ix_asset_structure_version_operation_type"),
table_name="asset_structure_version",
)
op.drop_index(
op.f("ix_asset_structure_version_end_transaction_id"),
table_name="asset_structure_version",
)
op.drop_table("asset_structure_version")
op.drop_table("asset_structure")

View file

@ -0,0 +1,118 @@
"""add LogLocation
Revision ID: 3bef7d380a38
Revises: f3c7e273bfa3
Create Date: 2026-02-28 20:41:56.051847
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import wuttjamaican.db.util
# revision identifiers, used by Alembic.
revision: str = "3bef7d380a38"
down_revision: Union[str, None] = "f3c7e273bfa3"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# log_location
op.create_table(
"log_location",
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.Column("log_uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.Column("asset_uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.ForeignKeyConstraint(
["asset_uuid"],
["asset.uuid"],
name=op.f("fk_log_location_asset_uuid_asset"),
),
sa.ForeignKeyConstraint(
["log_uuid"], ["log.uuid"], name=op.f("fk_log_location_log_uuid_log")
),
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_log_location")),
)
op.create_table(
"log_location_version",
sa.Column(
"uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
),
sa.Column(
"log_uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=True
),
sa.Column(
"asset_uuid",
wuttjamaican.db.util.UUID(),
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_location_version")
),
)
op.create_index(
op.f("ix_log_location_version_end_transaction_id"),
"log_location_version",
["end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_log_location_version_operation_type"),
"log_location_version",
["operation_type"],
unique=False,
)
op.create_index(
"ix_log_location_version_pk_transaction_id",
"log_location_version",
["uuid", sa.literal_column("transaction_id DESC")],
unique=False,
)
op.create_index(
"ix_log_location_version_pk_validity",
"log_location_version",
["uuid", "transaction_id", "end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_log_location_version_transaction_id"),
"log_location_version",
["transaction_id"],
unique=False,
)
def downgrade() -> None:
# log_location
op.drop_index(
op.f("ix_log_location_version_transaction_id"),
table_name="log_location_version",
)
op.drop_index(
"ix_log_location_version_pk_validity", table_name="log_location_version"
)
op.drop_index(
"ix_log_location_version_pk_transaction_id", table_name="log_location_version"
)
op.drop_index(
op.f("ix_log_location_version_operation_type"),
table_name="log_location_version",
)
op.drop_index(
op.f("ix_log_location_version_end_transaction_id"),
table_name="log_location_version",
)
op.drop_table("log_location_version")
op.drop_table("log_location")

View file

@ -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")

View file

@ -0,0 +1,37 @@
"""remove unique for animal_type.name
Revision ID: 45c7718d2ed2
Revises: 5b6c87d8cddf
Create Date: 2026-02-27 16:53:59.310342
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import wuttjamaican.db.util
# revision identifiers, used by Alembic.
revision: str = "45c7718d2ed2"
down_revision: Union[str, None] = "5b6c87d8cddf"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# animal_type
op.drop_constraint(op.f("uq_animal_type_name"), "animal_type", type_="unique")
def downgrade() -> None:
# animal_type
op.create_unique_constraint(
op.f("uq_animal_type_name"),
"animal_type",
["name"],
postgresql_nulls_not_distinct=False,
)

View file

@ -0,0 +1,108 @@
"""add LogOwner
Revision ID: 47d0ebd84554
Revises: 45c7718d2ed2
Create Date: 2026-02-28 19:18:49.122090
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import wuttjamaican.db.util
# revision identifiers, used by Alembic.
revision: str = "47d0ebd84554"
down_revision: Union[str, None] = "45c7718d2ed2"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# log_owner
op.create_table(
"log_owner",
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.Column("log_uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.Column("user_uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.ForeignKeyConstraint(
["log_uuid"], ["log.uuid"], name=op.f("fk_log_owner_log_uuid_log")
),
sa.ForeignKeyConstraint(
["user_uuid"], ["user.uuid"], name=op.f("fk_log_owner_user_uuid_user")
),
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_log_owner")),
)
op.create_table(
"log_owner_version",
sa.Column(
"uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
),
sa.Column(
"log_uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=True
),
sa.Column(
"user_uuid", wuttjamaican.db.util.UUID(), 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_owner_version")
),
)
op.create_index(
op.f("ix_log_owner_version_end_transaction_id"),
"log_owner_version",
["end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_log_owner_version_operation_type"),
"log_owner_version",
["operation_type"],
unique=False,
)
op.create_index(
"ix_log_owner_version_pk_transaction_id",
"log_owner_version",
["uuid", sa.literal_column("transaction_id DESC")],
unique=False,
)
op.create_index(
"ix_log_owner_version_pk_validity",
"log_owner_version",
["uuid", "transaction_id", "end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_log_owner_version_transaction_id"),
"log_owner_version",
["transaction_id"],
unique=False,
)
def downgrade() -> None:
# log_owner
op.drop_index(
op.f("ix_log_owner_version_transaction_id"), table_name="log_owner_version"
)
op.drop_index("ix_log_owner_version_pk_validity", table_name="log_owner_version")
op.drop_index(
"ix_log_owner_version_pk_transaction_id", table_name="log_owner_version"
)
op.drop_index(
op.f("ix_log_owner_version_operation_type"), table_name="log_owner_version"
)
op.drop_index(
op.f("ix_log_owner_version_end_transaction_id"), table_name="log_owner_version"
)
op.drop_table("log_owner_version")
op.drop_table("log_owner")

View 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")

View file

@ -0,0 +1,125 @@
"""add LandAssetParent model
Revision ID: 554e6168c339
Revises: 8cc1565d38e7
Create Date: 2026-02-14 20:41:24.859064
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import wuttjamaican.db.util
# revision identifiers, used by Alembic.
revision: str = "554e6168c339"
down_revision: Union[str, None] = "8cc1565d38e7"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# land_asset_parent
op.create_table(
"land_asset_parent",
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.Column("land_asset_uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.Column("parent_asset_uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.ForeignKeyConstraint(
["land_asset_uuid"],
["land_asset.uuid"],
name=op.f("fk_land_asset_parent_land_asset_uuid_land_asset"),
),
sa.ForeignKeyConstraint(
["parent_asset_uuid"],
["land_asset.uuid"],
name=op.f("fk_land_asset_parent_parent_asset_uuid_land_asset"),
),
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_land_asset_parent")),
)
op.create_table(
"land_asset_parent_version",
sa.Column(
"uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
),
sa.Column(
"land_asset_uuid",
wuttjamaican.db.util.UUID(),
autoincrement=False,
nullable=True,
),
sa.Column(
"parent_asset_uuid",
wuttjamaican.db.util.UUID(),
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_parent_version")
),
)
op.create_index(
op.f("ix_land_asset_parent_version_end_transaction_id"),
"land_asset_parent_version",
["end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_land_asset_parent_version_operation_type"),
"land_asset_parent_version",
["operation_type"],
unique=False,
)
op.create_index(
"ix_land_asset_parent_version_pk_transaction_id",
"land_asset_parent_version",
["uuid", sa.literal_column("transaction_id DESC")],
unique=False,
)
op.create_index(
"ix_land_asset_parent_version_pk_validity",
"land_asset_parent_version",
["uuid", "transaction_id", "end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_land_asset_parent_version_transaction_id"),
"land_asset_parent_version",
["transaction_id"],
unique=False,
)
def downgrade() -> None:
# land_asset_parent
op.drop_index(
op.f("ix_land_asset_parent_version_transaction_id"),
table_name="land_asset_parent_version",
)
op.drop_index(
"ix_land_asset_parent_version_pk_validity",
table_name="land_asset_parent_version",
)
op.drop_index(
"ix_land_asset_parent_version_pk_transaction_id",
table_name="land_asset_parent_version",
)
op.drop_index(
op.f("ix_land_asset_parent_version_operation_type"),
table_name="land_asset_parent_version",
)
op.drop_index(
op.f("ix_land_asset_parent_version_end_transaction_id"),
table_name="land_asset_parent_version",
)
op.drop_table("land_asset_parent_version")
op.drop_table("land_asset_parent")

View file

@ -0,0 +1,293 @@
"""add Standard Quantities
Revision ID: 5b6c87d8cddf
Revises: 1f98d27cabeb
Create Date: 2026-02-19 15:42:19.691148
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import wuttjamaican.db.util
# revision identifiers, used by Alembic.
revision: str = "5b6c87d8cddf"
down_revision: Union[str, None] = "1f98d27cabeb"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# measure
op.create_table(
"measure",
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.Column("name", sa.String(length=100), nullable=False),
sa.Column("drupal_id", sa.String(length=20), nullable=True),
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_measure")),
sa.UniqueConstraint("drupal_id", name=op.f("uq_measure_drupal_id")),
sa.UniqueConstraint("name", name=op.f("uq_measure_name")),
)
op.create_table(
"measure_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(
"drupal_id", sa.String(length=20), 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_measure_version")
),
)
op.create_index(
op.f("ix_measure_version_end_transaction_id"),
"measure_version",
["end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_measure_version_operation_type"),
"measure_version",
["operation_type"],
unique=False,
)
op.create_index(
"ix_measure_version_pk_transaction_id",
"measure_version",
["uuid", sa.literal_column("transaction_id DESC")],
unique=False,
)
op.create_index(
"ix_measure_version_pk_validity",
"measure_version",
["uuid", "transaction_id", "end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_measure_version_transaction_id"),
"measure_version",
["transaction_id"],
unique=False,
)
# quantity
op.create_table(
"quantity",
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.Column("quantity_type_id", sa.String(length=50), nullable=False),
sa.Column("measure_id", sa.String(length=20), nullable=False),
sa.Column("value_numerator", sa.Integer(), nullable=False),
sa.Column("value_denominator", sa.Integer(), nullable=False),
sa.Column("units_uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.Column("label", 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(
["measure_id"],
["measure.drupal_id"],
name=op.f("fk_quantity_measure_id_measure"),
),
sa.ForeignKeyConstraint(
["quantity_type_id"],
["quantity_type.drupal_id"],
name=op.f("fk_quantity_quantity_type_id_quantity_type"),
),
sa.ForeignKeyConstraint(
["units_uuid"], ["unit.uuid"], name=op.f("fk_quantity_units_uuid_unit")
),
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_quantity")),
sa.UniqueConstraint("drupal_id", name=op.f("uq_quantity_drupal_id")),
sa.UniqueConstraint("farmos_uuid", name=op.f("uq_quantity_farmos_uuid")),
)
op.create_table(
"quantity_version",
sa.Column(
"uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
),
sa.Column(
"quantity_type_id", sa.String(length=50), autoincrement=False, nullable=True
),
sa.Column(
"measure_id", sa.String(length=20), autoincrement=False, nullable=True
),
sa.Column("value_numerator", sa.Integer(), autoincrement=False, nullable=True),
sa.Column(
"value_denominator", sa.Integer(), autoincrement=False, nullable=True
),
sa.Column(
"units_uuid",
wuttjamaican.db.util.UUID(),
autoincrement=False,
nullable=True,
),
sa.Column("label", 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_quantity_version")
),
)
op.create_index(
op.f("ix_quantity_version_end_transaction_id"),
"quantity_version",
["end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_quantity_version_operation_type"),
"quantity_version",
["operation_type"],
unique=False,
)
op.create_index(
"ix_quantity_version_pk_transaction_id",
"quantity_version",
["uuid", sa.literal_column("transaction_id DESC")],
unique=False,
)
op.create_index(
"ix_quantity_version_pk_validity",
"quantity_version",
["uuid", "transaction_id", "end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_quantity_version_transaction_id"),
"quantity_version",
["transaction_id"],
unique=False,
)
# quantity_standard
op.create_table(
"quantity_standard",
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.ForeignKeyConstraint(
["uuid"], ["quantity.uuid"], name=op.f("fk_quantity_standard_uuid_quantity")
),
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_quantity_standard")),
)
op.create_table(
"quantity_standard_version",
sa.Column(
"uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
),
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_quantity_standard_version")
),
)
op.create_index(
op.f("ix_quantity_standard_version_end_transaction_id"),
"quantity_standard_version",
["end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_quantity_standard_version_operation_type"),
"quantity_standard_version",
["operation_type"],
unique=False,
)
op.create_index(
"ix_quantity_standard_version_pk_transaction_id",
"quantity_standard_version",
["uuid", sa.literal_column("transaction_id DESC")],
unique=False,
)
op.create_index(
"ix_quantity_standard_version_pk_validity",
"quantity_standard_version",
["uuid", "transaction_id", "end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_quantity_standard_version_transaction_id"),
"quantity_standard_version",
["transaction_id"],
unique=False,
)
def downgrade() -> None:
# quantity_standard
op.drop_index(
op.f("ix_quantity_standard_version_transaction_id"),
table_name="quantity_standard_version",
)
op.drop_index(
"ix_quantity_standard_version_pk_validity",
table_name="quantity_standard_version",
)
op.drop_index(
"ix_quantity_standard_version_pk_transaction_id",
table_name="quantity_standard_version",
)
op.drop_index(
op.f("ix_quantity_standard_version_operation_type"),
table_name="quantity_standard_version",
)
op.drop_index(
op.f("ix_quantity_standard_version_end_transaction_id"),
table_name="quantity_standard_version",
)
op.drop_table("quantity_standard_version")
op.drop_table("quantity_standard")
# quantity
op.drop_index(
op.f("ix_quantity_version_transaction_id"), table_name="quantity_version"
)
op.drop_index("ix_quantity_version_pk_validity", table_name="quantity_version")
op.drop_index(
"ix_quantity_version_pk_transaction_id", table_name="quantity_version"
)
op.drop_index(
op.f("ix_quantity_version_operation_type"), table_name="quantity_version"
)
op.drop_index(
op.f("ix_quantity_version_end_transaction_id"), table_name="quantity_version"
)
op.drop_table("quantity_version")
op.drop_table("quantity")
# measure
op.drop_index(
op.f("ix_measure_version_transaction_id"), table_name="measure_version"
)
op.drop_index("ix_measure_version_pk_validity", table_name="measure_version")
op.drop_index("ix_measure_version_pk_transaction_id", table_name="measure_version")
op.drop_index(
op.f("ix_measure_version_operation_type"), table_name="measure_version"
)
op.drop_index(
op.f("ix_measure_version_end_transaction_id"), table_name="measure_version"
)
op.drop_table("measure_version")
op.drop_table("measure")

View file

@ -0,0 +1,39 @@
"""remove unwanted unique constraint
Revision ID: 5f474125a80e
Revises: 0771322957bd
Create Date: 2026-03-04 12:03:16.034291
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import wuttjamaican.db.util
# revision identifiers, used by Alembic.
revision: str = "5f474125a80e"
down_revision: Union[str, None] = "0771322957bd"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# asset_land
op.drop_constraint(
op.f("uq_asset_land_land_type_uuid"), "asset_land", type_="unique"
)
def downgrade() -> None:
# asset_land
op.create_unique_constraint(
op.f("uq_asset_land_land_type_uuid"),
"asset_land",
["land_type_uuid"],
postgresql_nulls_not_distinct=False,
)

View file

@ -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")

View file

@ -0,0 +1,111 @@
"""add LogGroup
Revision ID: 74d32b4ec210
Revises: 3bef7d380a38
Create Date: 2026-02-28 21:35:24.125784
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import wuttjamaican.db.util
# revision identifiers, used by Alembic.
revision: str = "74d32b4ec210"
down_revision: Union[str, None] = "3bef7d380a38"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# log_group
op.create_table(
"log_group",
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.Column("log_uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.Column("asset_uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.ForeignKeyConstraint(
["asset_uuid"], ["asset.uuid"], name=op.f("fk_log_group_asset_uuid_asset")
),
sa.ForeignKeyConstraint(
["log_uuid"], ["log.uuid"], name=op.f("fk_log_group_log_uuid_log")
),
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_log_group")),
)
op.create_table(
"log_group_version",
sa.Column(
"uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
),
sa.Column(
"log_uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=True
),
sa.Column(
"asset_uuid",
wuttjamaican.db.util.UUID(),
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_group_version")
),
)
op.create_index(
op.f("ix_log_group_version_end_transaction_id"),
"log_group_version",
["end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_log_group_version_operation_type"),
"log_group_version",
["operation_type"],
unique=False,
)
op.create_index(
"ix_log_group_version_pk_transaction_id",
"log_group_version",
["uuid", sa.literal_column("transaction_id DESC")],
unique=False,
)
op.create_index(
"ix_log_group_version_pk_validity",
"log_group_version",
["uuid", "transaction_id", "end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_log_group_version_transaction_id"),
"log_group_version",
["transaction_id"],
unique=False,
)
def downgrade() -> None:
# log_group
op.drop_index(
op.f("ix_log_group_version_transaction_id"), table_name="log_group_version"
)
op.drop_index("ix_log_group_version_pk_validity", table_name="log_group_version")
op.drop_index(
"ix_log_group_version_pk_transaction_id", table_name="log_group_version"
)
op.drop_index(
op.f("ix_log_group_version_operation_type"), table_name="log_group_version"
)
op.drop_index(
op.f("ix_log_group_version_end_transaction_id"), table_name="log_group_version"
)
op.drop_table("log_group_version")
op.drop_table("log_group")

View file

@ -0,0 +1,52 @@
"""add produces_eggs via EggMixin
Revision ID: 82a03f4ef1a4
Revises: 11e0e46f48a6
Create Date: 2026-02-18 18:45:36.015144
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import wuttjamaican.db.util
# revision identifiers, used by Alembic.
revision: str = "82a03f4ef1a4"
down_revision: Union[str, None] = "11e0e46f48a6"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# asset_animal
op.add_column(
"asset_animal", sa.Column("produces_eggs", sa.Boolean(), nullable=True)
)
op.add_column(
"asset_animal_version",
sa.Column("produces_eggs", sa.Boolean(), autoincrement=False, nullable=True),
)
# asset_group
op.add_column(
"asset_group", sa.Column("produces_eggs", sa.Boolean(), nullable=True)
)
op.add_column(
"asset_group_version",
sa.Column("produces_eggs", sa.Boolean(), autoincrement=False, nullable=True),
)
def downgrade() -> None:
# asset_group
op.drop_column("asset_group_version", "produces_eggs")
op.drop_column("asset_group", "produces_eggs")
# asset_animal
op.drop_column("asset_animal_version", "produces_eggs")
op.drop_column("asset_animal", "produces_eggs")

View file

@ -0,0 +1,37 @@
"""add Log.quick
Revision ID: 85d4851e8292
Revises: d459db991404
Create Date: 2026-03-02 18:42:56.070281
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import wuttjamaican.db.util
# revision identifiers, used by Alembic.
revision: str = "85d4851e8292"
down_revision: Union[str, None] = "d459db991404"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# log
op.add_column("log", sa.Column("quick", sa.String(length=20), nullable=True))
op.add_column(
"log_version",
sa.Column("quick", sa.String(length=20), autoincrement=False, nullable=True),
)
def downgrade() -> None:
# log
op.drop_column("log_version", "quick")
op.drop_column("log", "quick")

View file

@ -0,0 +1,250 @@
"""convert active to archived
Revision ID: 8898184c5c75
Revises: 3e2ef02bf264
Create Date: 2026-02-14 18:41:23.042951
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import wuttjamaican.db.util
# revision identifiers, used by Alembic.
revision: str = "8898184c5c75"
down_revision: Union[str, None] = "3e2ef02bf264"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# animal
op.alter_column("animal", "active", new_column_name="archived")
animal = sa.sql.table(
"animal",
sa.sql.column("uuid"),
sa.sql.column("archived"),
)
cursor = op.get_bind().execute(animal.select())
for row in cursor.fetchall():
op.get_bind().execute(
animal.update()
.where(animal.c.uuid == row.uuid)
.values({"archived": not row.archived})
)
op.alter_column("animal_version", "active", new_column_name="archived")
animal_version = sa.sql.table(
"animal_version",
sa.sql.column("uuid"),
sa.sql.column("archived"),
)
cursor = op.get_bind().execute(animal_version.select())
for row in cursor.fetchall():
op.get_bind().execute(
animal_version.update()
.where(animal_version.c.uuid == row.uuid)
.values({"archived": not row.archived})
)
# group
op.alter_column("group", "active", new_column_name="archived")
group = sa.sql.table(
"group",
sa.sql.column("uuid"),
sa.sql.column("archived"),
)
cursor = op.get_bind().execute(group.select())
for row in cursor.fetchall():
op.get_bind().execute(
group.update()
.where(group.c.uuid == row.uuid)
.values({"archived": not row.archived})
)
op.alter_column("group_version", "active", new_column_name="archived")
group_version = sa.sql.table(
"group_version",
sa.sql.column("uuid"),
sa.sql.column("archived"),
)
cursor = op.get_bind().execute(group_version.select())
for row in cursor.fetchall():
op.get_bind().execute(
group_version.update()
.where(group_version.c.uuid == row.uuid)
.values({"archived": not row.archived})
)
# land_asset
op.alter_column("land_asset", "active", new_column_name="archived")
land_asset = sa.sql.table(
"land_asset",
sa.sql.column("uuid"),
sa.sql.column("archived"),
)
cursor = op.get_bind().execute(land_asset.select())
for row in cursor.fetchall():
op.get_bind().execute(
land_asset.update()
.where(land_asset.c.uuid == row.uuid)
.values({"archived": not row.archived})
)
op.alter_column("land_asset_version", "active", new_column_name="archived")
land_asset_version = sa.sql.table(
"land_asset_version",
sa.sql.column("uuid"),
sa.sql.column("archived"),
)
cursor = op.get_bind().execute(land_asset_version.select())
for row in cursor.fetchall():
op.get_bind().execute(
land_asset_version.update()
.where(land_asset_version.c.uuid == row.uuid)
.values({"archived": not row.archived})
)
# structure
op.alter_column("structure", "active", new_column_name="archived")
structure = sa.sql.table(
"structure",
sa.sql.column("uuid"),
sa.sql.column("archived"),
)
cursor = op.get_bind().execute(structure.select())
for row in cursor.fetchall():
op.get_bind().execute(
structure.update()
.where(structure.c.uuid == row.uuid)
.values({"archived": not row.archived})
)
op.alter_column("structure_version", "active", new_column_name="archived")
structure_version = sa.sql.table(
"structure_version",
sa.sql.column("uuid"),
sa.sql.column("archived"),
)
cursor = op.get_bind().execute(structure_version.select())
for row in cursor.fetchall():
op.get_bind().execute(
structure_version.update()
.where(structure_version.c.uuid == row.uuid)
.values({"archived": not row.archived})
)
def downgrade() -> None:
# structure
op.alter_column("structure", "archived", new_column_name="active")
structure = sa.sql.table(
"structure",
sa.sql.column("uuid"),
sa.sql.column("active"),
)
cursor = op.get_bind().execute(structure.select())
for row in cursor.fetchall():
op.get_bind().execute(
structure.update()
.where(structure.c.uuid == row.uuid)
.values({"active": not row.active})
)
op.alter_column("structure_version", "archived", new_column_name="active")
structure_version = sa.sql.table(
"structure_version",
sa.sql.column("uuid"),
sa.sql.column("active"),
)
cursor = op.get_bind().execute(structure_version.select())
for row in cursor.fetchall():
op.get_bind().execute(
structure_version.update()
.where(structure_version.c.uuid == row.uuid)
.values({"active": not row.active})
)
# land_asset
op.alter_column("land_asset", "archived", new_column_name="active")
land_asset = sa.sql.table(
"land_asset",
sa.sql.column("uuid"),
sa.sql.column("active"),
)
cursor = op.get_bind().execute(land_asset.select())
for row in cursor.fetchall():
op.get_bind().execute(
land_asset.update()
.where(land_asset.c.uuid == row.uuid)
.values({"active": not row.active})
)
op.alter_column("land_asset_version", "archived", new_column_name="active")
land_asset_version = sa.sql.table(
"land_asset_version",
sa.sql.column("uuid"),
sa.sql.column("active"),
)
cursor = op.get_bind().execute(land_asset_version.select())
for row in cursor.fetchall():
op.get_bind().execute(
land_asset_version.update()
.where(land_asset_version.c.uuid == row.uuid)
.values({"active": not row.active})
)
# group
op.alter_column("group", "archived", new_column_name="active")
group = sa.sql.table(
"group",
sa.sql.column("uuid"),
sa.sql.column("active"),
)
cursor = op.get_bind().execute(group.select())
for row in cursor.fetchall():
op.get_bind().execute(
group.update()
.where(group.c.uuid == row.uuid)
.values({"active": not row.active})
)
op.alter_column("group_version", "archived", new_column_name="active")
group_version = sa.sql.table(
"group_version",
sa.sql.column("uuid"),
sa.sql.column("active"),
)
cursor = op.get_bind().execute(group_version.select())
for row in cursor.fetchall():
op.get_bind().execute(
group_version.update()
.where(group_version.c.uuid == row.uuid)
.values({"active": not row.active})
)
# animal
op.alter_column("animal", "archived", new_column_name="active")
animal = sa.sql.table(
"animal",
sa.sql.column("uuid"),
sa.sql.column("active"),
)
cursor = op.get_bind().execute(animal.select())
for row in cursor.fetchall():
op.get_bind().execute(
animal.update()
.where(animal.c.uuid == row.uuid)
.values({"active": not row.active})
)
op.alter_column("animal_version", "archived", new_column_name="active")
animal_version = sa.sql.table(
"animal_version",
sa.sql.column("uuid"),
sa.sql.column("active"),
)
cursor = op.get_bind().execute(animal_version.select())
for row in cursor.fetchall():
op.get_bind().execute(
animal_version.update()
.where(animal_version.c.uuid == row.uuid)
.values({"active": not row.active})
)

View file

@ -0,0 +1,41 @@
"""add structure thumbnail url
Revision ID: 8cc1565d38e7
Revises: 2a49127e974b
Create Date: 2026-02-14 20:07:33.913573
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import wuttjamaican.db.util
# revision identifiers, used by Alembic.
revision: str = "8cc1565d38e7"
down_revision: Union[str, None] = "2a49127e974b"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# structure
op.add_column(
"structure", sa.Column("thumbnail_url", sa.String(length=255), nullable=True)
)
op.add_column(
"structure_version",
sa.Column(
"thumbnail_url", sa.String(length=255), autoincrement=False, nullable=True
),
)
def downgrade() -> None:
# structure
op.drop_column("structure_version", "thumbnail_url")
op.drop_column("structure", "thumbnail_url")

View 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")

View file

@ -0,0 +1,118 @@
"""add LogQuantity
Revision ID: 9e875e5cbdc1
Revises: 74d32b4ec210
Create Date: 2026-02-28 21:55:31.876087
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import wuttjamaican.db.util
# revision identifiers, used by Alembic.
revision: str = "9e875e5cbdc1"
down_revision: Union[str, None] = "74d32b4ec210"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# log_quantity
op.create_table(
"log_quantity",
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.Column("log_uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.Column("quantity_uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.ForeignKeyConstraint(
["log_uuid"], ["log.uuid"], name=op.f("fk_log_quantity_log_uuid_log")
),
sa.ForeignKeyConstraint(
["quantity_uuid"],
["quantity.uuid"],
name=op.f("fk_log_quantity_quantity_uuid_quantity"),
),
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_log_quantity")),
)
op.create_table(
"log_quantity_version",
sa.Column(
"uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
),
sa.Column(
"log_uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=True
),
sa.Column(
"quantity_uuid",
wuttjamaican.db.util.UUID(),
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_quantity_version")
),
)
op.create_index(
op.f("ix_log_quantity_version_end_transaction_id"),
"log_quantity_version",
["end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_log_quantity_version_operation_type"),
"log_quantity_version",
["operation_type"],
unique=False,
)
op.create_index(
"ix_log_quantity_version_pk_transaction_id",
"log_quantity_version",
["uuid", sa.literal_column("transaction_id DESC")],
unique=False,
)
op.create_index(
"ix_log_quantity_version_pk_validity",
"log_quantity_version",
["uuid", "transaction_id", "end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_log_quantity_version_transaction_id"),
"log_quantity_version",
["transaction_id"],
unique=False,
)
def downgrade() -> None:
# log_quantity
op.drop_index(
op.f("ix_log_quantity_version_transaction_id"),
table_name="log_quantity_version",
)
op.drop_index(
"ix_log_quantity_version_pk_validity", table_name="log_quantity_version"
)
op.drop_index(
"ix_log_quantity_version_pk_transaction_id", table_name="log_quantity_version"
)
op.drop_index(
op.f("ix_log_quantity_version_operation_type"),
table_name="log_quantity_version",
)
op.drop_index(
op.f("ix_log_quantity_version_end_transaction_id"),
table_name="log_quantity_version",
)
op.drop_table("log_quantity_version")
op.drop_table("log_quantity")

View 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")

View file

@ -0,0 +1,194 @@
"""use shared base for Group Assets
Revision ID: aecfd9175624
Revises: 34ec51d80f52
Create Date: 2026-02-15 13:57:01.055304
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import wuttjamaican.db.util
# revision identifiers, used by Alembic.
revision: str = "aecfd9175624"
down_revision: Union[str, None] = "34ec51d80f52"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# asset_group
op.create_table(
"asset_group",
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.ForeignKeyConstraint(
["uuid"], ["asset.uuid"], name=op.f("fk_asset_group_uuid_asset")
),
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_asset_group")),
)
op.create_table(
"asset_group_version",
sa.Column(
"uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
),
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_group_version")
),
)
op.create_index(
op.f("ix_asset_group_version_end_transaction_id"),
"asset_group_version",
["end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_asset_group_version_operation_type"),
"asset_group_version",
["operation_type"],
unique=False,
)
op.create_index(
"ix_asset_group_version_pk_transaction_id",
"asset_group_version",
["uuid", sa.literal_column("transaction_id DESC")],
unique=False,
)
op.create_index(
"ix_asset_group_version_pk_validity",
"asset_group_version",
["uuid", "transaction_id", "end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_asset_group_version_transaction_id"),
"asset_group_version",
["transaction_id"],
unique=False,
)
# group
op.drop_index(
op.f("ix_group_version_end_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_pk_transaction_id"), table_name="group_version"
)
op.drop_index(op.f("ix_group_version_pk_validity"), table_name="group_version")
op.drop_index(op.f("ix_group_version_transaction_id"), table_name="group_version")
op.drop_table("group_version")
op.drop_table("group")
def downgrade() -> None:
# group
op.create_table(
"group",
sa.Column("uuid", sa.UUID(), autoincrement=False, nullable=False),
sa.Column("name", sa.VARCHAR(length=100), autoincrement=False, nullable=False),
sa.Column("is_location", sa.BOOLEAN(), autoincrement=False, nullable=False),
sa.Column("is_fixed", sa.BOOLEAN(), autoincrement=False, nullable=False),
sa.Column("archived", sa.BOOLEAN(), autoincrement=False, nullable=False),
sa.Column("notes", sa.TEXT(), autoincrement=False, nullable=True),
sa.Column("farmos_uuid", sa.UUID(), autoincrement=False, nullable=True),
sa.Column("drupal_id", sa.INTEGER(), autoincrement=False, nullable=True),
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_group")),
sa.UniqueConstraint(
"drupal_id",
name=op.f("uq_group_drupal_id"),
postgresql_include=[],
postgresql_nulls_not_distinct=False,
),
sa.UniqueConstraint(
"farmos_uuid",
name=op.f("uq_group_farmos_uuid"),
postgresql_include=[],
postgresql_nulls_not_distinct=False,
),
sa.UniqueConstraint(
"name",
name=op.f("uq_group_name"),
postgresql_include=[],
postgresql_nulls_not_distinct=False,
),
)
op.create_table(
"group_version",
sa.Column("uuid", sa.UUID(), autoincrement=False, nullable=False),
sa.Column("name", sa.VARCHAR(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("archived", sa.BOOLEAN(), autoincrement=False, nullable=True),
sa.Column("notes", sa.TEXT(), autoincrement=False, nullable=True),
sa.Column("farmos_uuid", sa.UUID(), autoincrement=False, nullable=True),
sa.Column("drupal_id", sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column("transaction_id", sa.BIGINT(), autoincrement=False, nullable=False),
sa.Column(
"end_transaction_id", sa.BIGINT(), autoincrement=False, nullable=True
),
sa.Column("operation_type", sa.SMALLINT(), autoincrement=False, nullable=False),
sa.PrimaryKeyConstraint(
"uuid", "transaction_id", name=op.f("pk_group_version")
),
)
op.create_index(
op.f("ix_group_version_transaction_id"),
"group_version",
["transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_group_version_pk_validity"),
"group_version",
["uuid", "transaction_id", "end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_group_version_pk_transaction_id"),
"group_version",
["uuid", sa.literal_column("transaction_id DESC")],
unique=False,
)
op.create_index(
op.f("ix_group_version_operation_type"),
"group_version",
["operation_type"],
unique=False,
)
op.create_index(
op.f("ix_group_version_end_transaction_id"),
"group_version",
["end_transaction_id"],
unique=False,
)
# asset_group
op.drop_index(
op.f("ix_asset_group_version_transaction_id"), table_name="asset_group_version"
)
op.drop_index(
"ix_asset_group_version_pk_validity", table_name="asset_group_version"
)
op.drop_index(
"ix_asset_group_version_pk_transaction_id", table_name="asset_group_version"
)
op.drop_index(
op.f("ix_asset_group_version_operation_type"), table_name="asset_group_version"
)
op.drop_index(
op.f("ix_asset_group_version_end_transaction_id"),
table_name="asset_group_version",
)
op.drop_table("asset_group_version")
op.drop_table("asset_group")

View file

@ -0,0 +1,37 @@
"""remove AnimalType.changed
Revision ID: b8cd4a8f981f
Revises: aecfd9175624
Create Date: 2026-02-17 18:11:06.110003
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import wuttjamaican.db.util
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision: str = "b8cd4a8f981f"
down_revision: Union[str, None] = "aecfd9175624"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# animal_type
op.drop_column("animal_type", "changed")
def downgrade() -> None:
# animal_type
op.add_column(
"animal_type",
sa.Column(
"changed", postgresql.TIMESTAMP(), autoincrement=False, nullable=True
),
)

View file

@ -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")

View file

@ -0,0 +1,37 @@
"""add MedicalLog.vet
Revision ID: d459db991404
Revises: 9e875e5cbdc1
Create Date: 2026-02-28 22:17:57.001134
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import wuttjamaican.db.util
# revision identifiers, used by Alembic.
revision: str = "d459db991404"
down_revision: Union[str, None] = "9e875e5cbdc1"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# log_medical
op.add_column("log_medical", sa.Column("vet", sa.String(length=100), nullable=True))
op.add_column(
"log_medical_version",
sa.Column("vet", sa.String(length=100), autoincrement=False, nullable=True),
)
def downgrade() -> None:
# log_medical
op.drop_column("log_medical_version", "vet")
op.drop_column("log_medical", "vet")

View file

@ -0,0 +1,333 @@
"""add generic, animal assets
Revision ID: d6e8d16d6854
Revises: 554e6168c339
Create Date: 2026-02-15 09:11:04.886362
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import wuttjamaican.db.util
# revision identifiers, used by Alembic.
revision: str = "d6e8d16d6854"
down_revision: Union[str, None] = "554e6168c339"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# animal
op.drop_table("animal")
op.drop_index(
op.f("ix_animal_version_end_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_pk_transaction_id"), table_name="animal_version"
)
op.drop_index(op.f("ix_animal_version_pk_validity"), table_name="animal_version")
op.drop_index(op.f("ix_animal_version_transaction_id"), table_name="animal_version")
op.drop_table("animal_version")
# asset
op.create_table(
"asset",
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.Column("asset_type", sa.String(length=100), nullable=False),
sa.Column("asset_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("notes", sa.Text(), nullable=True),
sa.Column("thumbnail_url", sa.String(length=255), nullable=True),
sa.Column("image_url", sa.String(length=255), nullable=True),
sa.Column("archived", sa.Boolean(), nullable=False),
sa.ForeignKeyConstraint(
["asset_type"],
["asset_type.drupal_id"],
name=op.f("fk_asset_asset_type_asset_type"),
),
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_asset")),
sa.UniqueConstraint("drupal_id", name=op.f("uq_asset_drupal_id")),
sa.UniqueConstraint("farmos_uuid", name=op.f("uq_asset_farmos_uuid")),
)
op.create_table(
"asset_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(
"asset_type", sa.String(length=100), autoincrement=False, nullable=True
),
sa.Column(
"asset_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("notes", sa.Text(), autoincrement=False, nullable=True),
sa.Column(
"thumbnail_url", sa.String(length=255), autoincrement=False, nullable=True
),
sa.Column(
"image_url", sa.String(length=255), autoincrement=False, nullable=True
),
sa.Column("archived", sa.Boolean(), 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_version")
),
)
op.create_index(
op.f("ix_asset_version_end_transaction_id"),
"asset_version",
["end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_asset_version_operation_type"),
"asset_version",
["operation_type"],
unique=False,
)
op.create_index(
"ix_asset_version_pk_transaction_id",
"asset_version",
["uuid", sa.literal_column("transaction_id DESC")],
unique=False,
)
op.create_index(
"ix_asset_version_pk_validity",
"asset_version",
["uuid", "transaction_id", "end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_asset_version_transaction_id"),
"asset_version",
["transaction_id"],
unique=False,
)
# asset_animal
op.create_table(
"asset_animal",
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("uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.ForeignKeyConstraint(
["animal_type_uuid"],
["animal_type.uuid"],
name=op.f("fk_asset_animal_animal_type_uuid_animal_type"),
),
sa.ForeignKeyConstraint(
["uuid"], ["asset.uuid"], name=op.f("fk_asset_animal_uuid_asset")
),
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_asset_animal")),
)
op.create_table(
"asset_animal_version",
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(
"uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
),
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_animal_version")
),
)
op.create_index(
op.f("ix_asset_animal_version_end_transaction_id"),
"asset_animal_version",
["end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_asset_animal_version_operation_type"),
"asset_animal_version",
["operation_type"],
unique=False,
)
op.create_index(
"ix_asset_animal_version_pk_transaction_id",
"asset_animal_version",
["uuid", sa.literal_column("transaction_id DESC")],
unique=False,
)
op.create_index(
"ix_asset_animal_version_pk_validity",
"asset_animal_version",
["uuid", "transaction_id", "end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_asset_animal_version_transaction_id"),
"asset_animal_version",
["transaction_id"],
unique=False,
)
def downgrade() -> None:
# asset_animal
op.drop_index(
op.f("ix_asset_animal_version_transaction_id"),
table_name="asset_animal_version",
)
op.drop_index(
"ix_asset_animal_version_pk_validity", table_name="asset_animal_version"
)
op.drop_index(
"ix_asset_animal_version_pk_transaction_id", table_name="asset_animal_version"
)
op.drop_index(
op.f("ix_asset_animal_version_operation_type"),
table_name="asset_animal_version",
)
op.drop_index(
op.f("ix_asset_animal_version_end_transaction_id"),
table_name="asset_animal_version",
)
op.drop_table("asset_animal_version")
op.drop_table("asset_animal")
# asset
op.drop_index(op.f("ix_asset_version_transaction_id"), table_name="asset_version")
op.drop_index("ix_asset_version_pk_validity", table_name="asset_version")
op.drop_index("ix_asset_version_pk_transaction_id", table_name="asset_version")
op.drop_index(op.f("ix_asset_version_operation_type"), table_name="asset_version")
op.drop_index(
op.f("ix_asset_version_end_transaction_id"), table_name="asset_version"
)
op.drop_table("asset_version")
op.drop_table("asset")
# animal
op.create_table(
"animal",
sa.Column("uuid", sa.UUID(), autoincrement=False, nullable=False),
sa.Column("name", sa.VARCHAR(length=100), autoincrement=False, nullable=False),
sa.Column("animal_type_uuid", sa.UUID(), autoincrement=False, nullable=False),
sa.Column("birthdate", sa.DateTime(), autoincrement=False, nullable=True),
sa.Column("sex", sa.VARCHAR(length=1), autoincrement=False, nullable=True),
sa.Column("is_sterile", sa.BOOLEAN(), autoincrement=False, nullable=True),
sa.Column("archived", sa.BOOLEAN(), autoincrement=False, nullable=False),
sa.Column("notes", sa.TEXT(), autoincrement=False, nullable=True),
sa.Column(
"image_url", sa.VARCHAR(length=255), autoincrement=False, nullable=True
),
sa.Column("farmos_uuid", sa.UUID(), autoincrement=False, nullable=True),
sa.Column("drupal_id", sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column(
"thumbnail_url", sa.VARCHAR(length=255), autoincrement=False, 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"),
postgresql_include=[],
postgresql_nulls_not_distinct=False,
),
sa.UniqueConstraint(
"farmos_uuid",
name=op.f("uq_animal_farmos_uuid"),
postgresql_include=[],
postgresql_nulls_not_distinct=False,
),
)
op.create_table(
"animal_version",
sa.Column("uuid", sa.UUID(), autoincrement=False, nullable=False),
sa.Column("name", sa.VARCHAR(length=100), autoincrement=False, nullable=True),
sa.Column("animal_type_uuid", sa.UUID(), autoincrement=False, nullable=True),
sa.Column(
"birthdate", postgresql.TIMESTAMP(), autoincrement=False, nullable=True
),
sa.Column("sex", sa.VARCHAR(length=1), autoincrement=False, nullable=True),
sa.Column("is_sterile", sa.BOOLEAN(), autoincrement=False, nullable=True),
sa.Column("archived", sa.BOOLEAN(), autoincrement=False, nullable=True),
sa.Column("notes", sa.TEXT(), autoincrement=False, nullable=True),
sa.Column(
"image_url", sa.VARCHAR(length=255), autoincrement=False, nullable=True
),
sa.Column("farmos_uuid", sa.UUID(), autoincrement=False, nullable=True),
sa.Column("drupal_id", sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column("transaction_id", sa.BIGINT(), autoincrement=False, nullable=False),
sa.Column(
"end_transaction_id", sa.BIGINT(), autoincrement=False, nullable=True
),
sa.Column("operation_type", sa.SMALLINT(), autoincrement=False, nullable=False),
sa.Column(
"thumbnail_url", sa.VARCHAR(length=255), autoincrement=False, nullable=True
),
sa.PrimaryKeyConstraint(
"uuid", "transaction_id", name=op.f("pk_animal_version")
),
)
op.create_index(
op.f("ix_animal_version_transaction_id"),
"animal_version",
["transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_animal_version_pk_validity"),
"animal_version",
["uuid", "transaction_id", "end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_animal_version_pk_transaction_id"),
"animal_version",
["uuid", sa.literal_column("transaction_id DESC")],
unique=False,
)
op.create_index(
op.f("ix_animal_version_operation_type"),
"animal_version",
["operation_type"],
unique=False,
)
op.create_index(
op.f("ix_animal_version_end_transaction_id"),
"animal_version",
["end_transaction_id"],
unique=False,
)

View file

@ -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")

View file

@ -0,0 +1,411 @@
"""use shared base for Land Assets
Revision ID: d882682c82f9
Revises: d6e8d16d6854
Create Date: 2026-02-15 12:00:27.036011
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import wuttjamaican.db.util
# revision identifiers, used by Alembic.
revision: str = "d882682c82f9"
down_revision: Union[str, None] = "d6e8d16d6854"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# asset_parent
op.create_table(
"asset_parent",
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.Column("asset_uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.Column("parent_uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.ForeignKeyConstraint(
["asset_uuid"],
["asset.uuid"],
name=op.f("fk_asset_parent_asset_uuid_asset"),
),
sa.ForeignKeyConstraint(
["parent_uuid"],
["asset.uuid"],
name=op.f("fk_asset_parent_parent_uuid_asset"),
),
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_asset_parent")),
)
op.create_table(
"asset_parent_version",
sa.Column(
"uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
),
sa.Column(
"asset_uuid",
wuttjamaican.db.util.UUID(),
autoincrement=False,
nullable=True,
),
sa.Column(
"parent_uuid",
wuttjamaican.db.util.UUID(),
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_parent_version")
),
)
op.create_index(
op.f("ix_asset_parent_version_end_transaction_id"),
"asset_parent_version",
["end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_asset_parent_version_operation_type"),
"asset_parent_version",
["operation_type"],
unique=False,
)
op.create_index(
"ix_asset_parent_version_pk_transaction_id",
"asset_parent_version",
["uuid", sa.literal_column("transaction_id DESC")],
unique=False,
)
op.create_index(
"ix_asset_parent_version_pk_validity",
"asset_parent_version",
["uuid", "transaction_id", "end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_asset_parent_version_transaction_id"),
"asset_parent_version",
["transaction_id"],
unique=False,
)
# asset_land
op.create_table(
"asset_land",
sa.Column("land_type_uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.ForeignKeyConstraint(
["land_type_uuid"],
["land_type.uuid"],
name=op.f("fk_asset_land_land_type_uuid_land_type"),
),
sa.ForeignKeyConstraint(
["uuid"], ["asset.uuid"], name=op.f("fk_asset_land_uuid_asset")
),
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_asset_land")),
sa.UniqueConstraint(
"land_type_uuid", name=op.f("uq_asset_land_land_type_uuid")
),
)
op.create_table(
"asset_land_version",
sa.Column(
"land_type_uuid",
wuttjamaican.db.util.UUID(),
autoincrement=False,
nullable=True,
),
sa.Column(
"uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
),
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_land_version")
),
)
op.create_index(
op.f("ix_asset_land_version_end_transaction_id"),
"asset_land_version",
["end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_asset_land_version_operation_type"),
"asset_land_version",
["operation_type"],
unique=False,
)
op.create_index(
"ix_asset_land_version_pk_transaction_id",
"asset_land_version",
["uuid", sa.literal_column("transaction_id DESC")],
unique=False,
)
op.create_index(
"ix_asset_land_version_pk_validity",
"asset_land_version",
["uuid", "transaction_id", "end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_asset_land_version_transaction_id"),
"asset_land_version",
["transaction_id"],
unique=False,
)
# land_asset_parent
op.drop_index(
op.f("ix_land_asset_parent_version_end_transaction_id"),
table_name="land_asset_parent_version",
)
op.drop_index(
op.f("ix_land_asset_parent_version_operation_type"),
table_name="land_asset_parent_version",
)
op.drop_index(
op.f("ix_land_asset_parent_version_pk_transaction_id"),
table_name="land_asset_parent_version",
)
op.drop_index(
op.f("ix_land_asset_parent_version_pk_validity"),
table_name="land_asset_parent_version",
)
op.drop_index(
op.f("ix_land_asset_parent_version_transaction_id"),
table_name="land_asset_parent_version",
)
op.drop_table("land_asset_parent_version")
op.drop_table("land_asset_parent")
# land_asset
op.drop_index(
op.f("ix_land_asset_version_end_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_pk_transaction_id"), table_name="land_asset_version"
)
op.drop_index(
op.f("ix_land_asset_version_pk_validity"), table_name="land_asset_version"
)
op.drop_index(
op.f("ix_land_asset_version_transaction_id"), table_name="land_asset_version"
)
op.drop_table("land_asset_version")
op.drop_table("land_asset")
def downgrade() -> None:
# land_asset
op.create_table(
"land_asset",
sa.Column("uuid", sa.UUID(), autoincrement=False, nullable=False),
sa.Column("name", sa.VARCHAR(length=100), autoincrement=False, nullable=False),
sa.Column("land_type_uuid", sa.UUID(), autoincrement=False, nullable=False),
sa.Column("is_location", sa.BOOLEAN(), autoincrement=False, nullable=False),
sa.Column("is_fixed", sa.BOOLEAN(), autoincrement=False, nullable=False),
sa.Column("notes", sa.TEXT(), autoincrement=False, nullable=True),
sa.Column("archived", sa.BOOLEAN(), autoincrement=False, nullable=False),
sa.Column("farmos_uuid", sa.UUID(), autoincrement=False, nullable=True),
sa.Column("drupal_id", sa.INTEGER(), autoincrement=False, 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"),
postgresql_include=[],
postgresql_nulls_not_distinct=False,
),
sa.UniqueConstraint(
"farmos_uuid",
name=op.f("uq_land_asset_farmos_uuid"),
postgresql_include=[],
postgresql_nulls_not_distinct=False,
),
sa.UniqueConstraint(
"land_type_uuid",
name=op.f("uq_land_asset_land_type_uuid"),
postgresql_include=[],
postgresql_nulls_not_distinct=False,
),
sa.UniqueConstraint(
"name",
name=op.f("uq_land_asset_name"),
postgresql_include=[],
postgresql_nulls_not_distinct=False,
),
)
op.create_table(
"land_asset_version",
sa.Column("uuid", sa.UUID(), autoincrement=False, nullable=False),
sa.Column("name", sa.VARCHAR(length=100), autoincrement=False, nullable=True),
sa.Column("land_type_uuid", sa.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("archived", sa.BOOLEAN(), autoincrement=False, nullable=True),
sa.Column("farmos_uuid", sa.UUID(), autoincrement=False, nullable=True),
sa.Column("drupal_id", sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column("transaction_id", sa.BIGINT(), autoincrement=False, nullable=False),
sa.Column(
"end_transaction_id", sa.BIGINT(), autoincrement=False, nullable=True
),
sa.Column("operation_type", sa.SMALLINT(), autoincrement=False, nullable=False),
sa.PrimaryKeyConstraint(
"uuid", "transaction_id", name=op.f("pk_land_asset_version")
),
)
op.create_index(
op.f("ix_land_asset_version_transaction_id"),
"land_asset_version",
["transaction_id"],
unique=False,
)
op.create_index(
op.f("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_pk_transaction_id"),
"land_asset_version",
["uuid", sa.literal_column("transaction_id DESC")],
unique=False,
)
op.create_index(
op.f("ix_land_asset_version_operation_type"),
"land_asset_version",
["operation_type"],
unique=False,
)
op.create_index(
op.f("ix_land_asset_version_end_transaction_id"),
"land_asset_version",
["end_transaction_id"],
unique=False,
)
# land_asset_parent
op.create_table(
"land_asset_parent",
sa.Column("uuid", sa.UUID(), autoincrement=False, nullable=False),
sa.Column("land_asset_uuid", sa.UUID(), autoincrement=False, nullable=False),
sa.Column("parent_asset_uuid", sa.UUID(), autoincrement=False, nullable=False),
sa.ForeignKeyConstraint(
["land_asset_uuid"],
["land_asset.uuid"],
name=op.f("fk_land_asset_parent_land_asset_uuid_land_asset"),
),
sa.ForeignKeyConstraint(
["parent_asset_uuid"],
["land_asset.uuid"],
name=op.f("fk_land_asset_parent_parent_asset_uuid_land_asset"),
),
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_land_asset_parent")),
)
op.create_table(
"land_asset_parent_version",
sa.Column("uuid", sa.UUID(), autoincrement=False, nullable=False),
sa.Column("land_asset_uuid", sa.UUID(), autoincrement=False, nullable=True),
sa.Column("parent_asset_uuid", sa.UUID(), autoincrement=False, nullable=True),
sa.Column("transaction_id", sa.BIGINT(), autoincrement=False, nullable=False),
sa.Column(
"end_transaction_id", sa.BIGINT(), autoincrement=False, nullable=True
),
sa.Column("operation_type", sa.SMALLINT(), autoincrement=False, nullable=False),
sa.PrimaryKeyConstraint(
"uuid", "transaction_id", name=op.f("pk_land_asset_parent_version")
),
)
op.create_index(
op.f("ix_land_asset_parent_version_transaction_id"),
"land_asset_parent_version",
["transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_land_asset_parent_version_pk_validity"),
"land_asset_parent_version",
["uuid", "transaction_id", "end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_land_asset_parent_version_pk_transaction_id"),
"land_asset_parent_version",
["uuid", sa.literal_column("transaction_id DESC")],
unique=False,
)
op.create_index(
op.f("ix_land_asset_parent_version_operation_type"),
"land_asset_parent_version",
["operation_type"],
unique=False,
)
op.create_index(
op.f("ix_land_asset_parent_version_end_transaction_id"),
"land_asset_parent_version",
["end_transaction_id"],
unique=False,
)
# asset_land
op.drop_table("asset_land")
op.drop_index(
op.f("ix_asset_land_version_transaction_id"), table_name="asset_land_version"
)
op.drop_index("ix_asset_land_version_pk_validity", table_name="asset_land_version")
op.drop_index(
"ix_asset_land_version_pk_transaction_id", table_name="asset_land_version"
)
op.drop_index(
op.f("ix_asset_land_version_operation_type"), table_name="asset_land_version"
)
op.drop_index(
op.f("ix_asset_land_version_end_transaction_id"),
table_name="asset_land_version",
)
op.drop_table("asset_land_version")
# asset_parent
op.drop_index(
op.f("ix_asset_parent_version_transaction_id"),
table_name="asset_parent_version",
)
op.drop_index(
"ix_asset_parent_version_pk_validity", table_name="asset_parent_version"
)
op.drop_index(
"ix_asset_parent_version_pk_transaction_id", table_name="asset_parent_version"
)
op.drop_index(
op.f("ix_asset_parent_version_operation_type"),
table_name="asset_parent_version",
)
op.drop_index(
op.f("ix_asset_parent_version_end_transaction_id"),
table_name="asset_parent_version",
)
op.drop_table("asset_parent_version")
op.drop_table("asset_parent")

View file

@ -0,0 +1,206 @@
"""add generic log base
Revision ID: dd6351e69233
Revises: b8cd4a8f981f
Create Date: 2026-02-18 12:09:05.200134
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import wuttjamaican.db.util
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision: str = "dd6351e69233"
down_revision: Union[str, None] = "b8cd4a8f981f"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# log
op.create_table(
"log",
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
sa.Column("log_type", sa.String(length=100), 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.ForeignKeyConstraint(
["log_type"], ["log_type.drupal_id"], name=op.f("fk_log_log_type_log_type")
),
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_log")),
sa.UniqueConstraint("drupal_id", name=op.f("uq_log_drupal_id")),
sa.UniqueConstraint("farmos_uuid", name=op.f("uq_log_farmos_uuid")),
)
op.create_table(
"log_version",
sa.Column(
"uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False
),
sa.Column(
"log_type", sa.String(length=100), autoincrement=False, nullable=True
),
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_version")),
)
op.create_index(
op.f("ix_log_version_end_transaction_id"),
"log_version",
["end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_log_version_operation_type"),
"log_version",
["operation_type"],
unique=False,
)
op.create_index(
"ix_log_version_pk_transaction_id",
"log_version",
["uuid", sa.literal_column("transaction_id DESC")],
unique=False,
)
op.create_index(
"ix_log_version_pk_validity",
"log_version",
["uuid", "transaction_id", "end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_log_version_transaction_id"),
"log_version",
["transaction_id"],
unique=False,
)
# log_activity
op.drop_column("log_activity_version", "status")
op.drop_column("log_activity_version", "farmos_uuid")
op.drop_column("log_activity_version", "timestamp")
op.drop_column("log_activity_version", "message")
op.drop_column("log_activity_version", "drupal_id")
op.drop_column("log_activity_version", "notes")
op.drop_constraint(
op.f("uq_log_activity_drupal_id"), "log_activity", type_="unique"
)
op.drop_constraint(
op.f("uq_log_activity_farmos_uuid"), "log_activity", type_="unique"
)
op.create_foreign_key(
op.f("fk_log_activity_uuid_log"), "log_activity", "log", ["uuid"], ["uuid"]
)
op.drop_column("log_activity", "status")
op.drop_column("log_activity", "farmos_uuid")
op.drop_column("log_activity", "timestamp")
op.drop_column("log_activity", "message")
op.drop_column("log_activity", "drupal_id")
op.drop_column("log_activity", "notes")
def downgrade() -> None:
# log_activity
op.add_column(
"log_activity",
sa.Column("notes", sa.TEXT(), autoincrement=False, nullable=True),
)
op.add_column(
"log_activity",
sa.Column("drupal_id", sa.INTEGER(), autoincrement=False, nullable=True),
)
op.add_column(
"log_activity",
sa.Column(
"message", sa.VARCHAR(length=255), autoincrement=False, nullable=False
),
)
op.add_column(
"log_activity",
sa.Column(
"timestamp", postgresql.TIMESTAMP(), autoincrement=False, nullable=False
),
)
op.add_column(
"log_activity",
sa.Column("farmos_uuid", sa.UUID(), autoincrement=False, nullable=True),
)
op.add_column(
"log_activity",
sa.Column("status", sa.VARCHAR(length=20), autoincrement=False, nullable=False),
)
op.drop_constraint(
op.f("fk_log_activity_uuid_log"), "log_activity", type_="foreignkey"
)
op.create_unique_constraint(
op.f("uq_log_activity_farmos_uuid"),
"log_activity",
["farmos_uuid"],
postgresql_nulls_not_distinct=False,
)
op.create_unique_constraint(
op.f("uq_log_activity_drupal_id"),
"log_activity",
["drupal_id"],
postgresql_nulls_not_distinct=False,
)
op.add_column(
"log_activity_version",
sa.Column("notes", sa.TEXT(), autoincrement=False, nullable=True),
)
op.add_column(
"log_activity_version",
sa.Column("drupal_id", sa.INTEGER(), autoincrement=False, nullable=True),
)
op.add_column(
"log_activity_version",
sa.Column(
"message", sa.VARCHAR(length=255), autoincrement=False, nullable=True
),
)
op.add_column(
"log_activity_version",
sa.Column(
"timestamp", postgresql.TIMESTAMP(), autoincrement=False, nullable=True
),
)
op.add_column(
"log_activity_version",
sa.Column("farmos_uuid", sa.UUID(), autoincrement=False, nullable=True),
)
op.add_column(
"log_activity_version",
sa.Column("status", sa.VARCHAR(length=20), autoincrement=False, nullable=True),
)
# log
op.drop_index(op.f("ix_log_version_transaction_id"), table_name="log_version")
op.drop_index("ix_log_version_pk_validity", table_name="log_version")
op.drop_index("ix_log_version_pk_transaction_id", table_name="log_version")
op.drop_index(op.f("ix_log_version_operation_type"), table_name="log_version")
op.drop_index(op.f("ix_log_version_end_transaction_id"), table_name="log_version")
op.drop_table("log_version")
op.drop_table("log")

View 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")

View file

@ -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")

View file

@ -0,0 +1,102 @@
"""add Units
Revision ID: ea88e72a5fa5
Revises: 82a03f4ef1a4
Create Date: 2026-02-18 20:01:40.720138
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import wuttjamaican.db.util
# revision identifiers, used by Alembic.
revision: str = "ea88e72a5fa5"
down_revision: Union[str, None] = "82a03f4ef1a4"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# unit
op.create_table(
"unit",
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.Integer(), nullable=True),
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_unit")),
sa.UniqueConstraint("drupal_id", name=op.f("uq_unit_drupal_id")),
sa.UniqueConstraint("farmos_uuid", name=op.f("uq_unit_farmos_uuid")),
sa.UniqueConstraint("name", name=op.f("uq_unit_name")),
)
op.create_table(
"unit_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_unit_version")),
)
op.create_index(
op.f("ix_unit_version_end_transaction_id"),
"unit_version",
["end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_unit_version_operation_type"),
"unit_version",
["operation_type"],
unique=False,
)
op.create_index(
"ix_unit_version_pk_transaction_id",
"unit_version",
["uuid", sa.literal_column("transaction_id DESC")],
unique=False,
)
op.create_index(
"ix_unit_version_pk_validity",
"unit_version",
["uuid", "transaction_id", "end_transaction_id"],
unique=False,
)
op.create_index(
op.f("ix_unit_version_transaction_id"),
"unit_version",
["transaction_id"],
unique=False,
)
def downgrade() -> None:
# unit
op.drop_index(op.f("ix_unit_version_transaction_id"), table_name="unit_version")
op.drop_index("ix_unit_version_pk_validity", table_name="unit_version")
op.drop_index("ix_unit_version_pk_transaction_id", table_name="unit_version")
op.drop_index(op.f("ix_unit_version_operation_type"), table_name="unit_version")
op.drop_index(op.f("ix_unit_version_end_transaction_id"), table_name="unit_version")
op.drop_table("unit_version")
op.drop_table("unit")

View file

@ -0,0 +1,39 @@
"""add Log.is_group_assignment
Revision ID: f3c7e273bfa3
Revises: 47d0ebd84554
Create Date: 2026-02-28 20:04:40.700474
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import wuttjamaican.db.util
# revision identifiers, used by Alembic.
revision: str = "f3c7e273bfa3"
down_revision: Union[str, None] = "47d0ebd84554"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# log
op.add_column("log", sa.Column("is_group_assignment", sa.Boolean(), nullable=True))
op.add_column(
"log_version",
sa.Column(
"is_group_assignment", sa.Boolean(), autoincrement=False, nullable=True
),
)
def downgrade() -> None:
# log
op.drop_column("log_version", "is_group_assignment")
op.drop_column("log", "is_group_assignment")

View file

@ -26,4 +26,20 @@ WuttaFarm data models
# bring in all of wutta
from wuttjamaican.db.model import *
# TODO: import other/custom models here...
# wutta model extensions
from .users import WuttaFarmUser
# wuttafarm proper models
from .unit import Unit, Measure
from .quantities import QuantityType, Quantity, StandardQuantity
from .asset import AssetType, Asset, AssetParent
from .asset_land import LandType, LandAsset
from .asset_structure import StructureType, StructureAsset
from .asset_animal import AnimalType, AnimalAsset
from .asset_group import GroupAsset
from .asset_plant import PlantType, PlantAsset, PlantAssetPlantType
from .log import LogType, Log, LogAsset, LogGroup, LogLocation, LogQuantity, LogOwner
from .log_activity import ActivityLog
from .log_harvest import HarvestLog
from .log_medical import MedicalLog
from .log_observation import ObservationLog

Some files were not shown because too many files have changed in this diff Show more