feat: add Units table, views, import/export

This commit is contained in:
Lance Edgar 2026-02-18 20:13:58 -06:00
parent bc0836fc3c
commit 0a0d43aa9f
15 changed files with 604 additions and 234 deletions

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

@ -30,6 +30,7 @@ from wuttjamaican.db.model import *
from .users import WuttaFarmUser from .users import WuttaFarmUser
# wuttafarm proper models # wuttafarm proper models
from .unit import Unit
from .asset import AssetType, Asset, AssetParent from .asset import AssetType, Asset, AssetParent
from .asset_land import LandType, LandAsset from .asset_land import LandType, LandAsset
from .asset_structure import StructureType, StructureAsset from .asset_structure import StructureType, StructureAsset

View file

@ -0,0 +1,81 @@
# -*- coding: utf-8; -*-
################################################################################
#
# WuttaFarm --Web app to integrate with and extend farmOS
# Copyright © 2026 Lance Edgar
#
# This file is part of WuttaFarm.
#
# WuttaFarm is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# WuttaFarm is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# WuttaFarm. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Model definition for Units
"""
import sqlalchemy as sa
from wuttjamaican.db import model
class Unit(model.Base):
"""
Represents an "unit" (taxonomy term) from farmOS
"""
__tablename__ = "unit"
__versioned__ = {}
__wutta_hint__ = {
"model_title": "Unit",
"model_title_plural": "Units",
}
uuid = model.uuid_column()
name = sa.Column(
sa.String(length=100),
nullable=False,
unique=True,
doc="""
Name of the unit.
""",
)
description = sa.Column(
sa.String(length=255),
nullable=True,
doc="""
Optional description for the unit.
""",
)
farmos_uuid = sa.Column(
model.UUID(),
nullable=True,
unique=True,
doc="""
UUID for the unit within farmOS.
""",
)
drupal_id = sa.Column(
sa.Integer(),
nullable=True,
unique=True,
doc="""
Drupal internal ID for the unit.
""",
)
def __str__(self):
return self.name or ""

View file

@ -64,6 +64,81 @@ class ToFarmOS(Importer):
return self.app.make_utc(dt) return self.app.make_utc(dt)
class ToFarmOSTaxonomy(ToFarmOS):
farmos_taxonomy_type = None
supported_fields = [
"uuid",
"name",
]
def get_target_objects(self, **kwargs):
result = self.farmos_client.resource.get(
"taxonomy_term", self.farmos_taxonomy_type
)
return result["data"]
def get_target_object(self, key):
# fetch from cache, if applicable
if self.caches_target:
return super().get_target_object(key)
# okay now must fetch via API
if self.get_keys() != ["uuid"]:
raise ValueError("must use uuid key for this to work")
uuid = key[0]
try:
result = self.farmos_client.resource.get_id(
"taxonomy_term", self.farmos_taxonomy_type, str(uuid)
)
except requests.HTTPError as exc:
if exc.response.status_code == 404:
return None
raise
return result["data"]
def normalize_target_object(self, obj):
return {
"uuid": UUID(obj["id"]),
"name": obj["attributes"]["name"],
}
def get_term_payload(self, source_data):
return {
"attributes": {
"name": source_data["name"],
}
}
def create_target_object(self, key, source_data):
if source_data.get("__ignoreme__"):
return None
if self.dry_run:
return source_data
payload = self.get_term_payload(source_data)
result = self.farmos_client.resource.send(
"taxonomy_term", self.farmos_taxonomy_type, payload
)
normal = self.normalize_target_object(result["data"])
normal["_new_object"] = result["data"]
return normal
def update_target_object(self, asset, source_data, target_data=None):
if self.dry_run:
return asset
payload = self.get_term_payload(source_data)
payload["id"] = str(source_data["uuid"])
result = self.farmos_client.resource.send(
"taxonomy_term", self.farmos_taxonomy_type, payload
)
return self.normalize_target_object(result["data"])
class ToFarmOSAsset(ToFarmOS): class ToFarmOSAsset(ToFarmOS):
""" """
Base class for asset data importer targeting the farmOS API. Base class for asset data importer targeting the farmOS API.
@ -151,6 +226,12 @@ class ToFarmOSAsset(ToFarmOS):
return payload return payload
class UnitImporter(ToFarmOSTaxonomy):
model_title = "Unit"
farmos_taxonomy_type = "unit"
class AnimalAssetImporter(ToFarmOSAsset): class AnimalAssetImporter(ToFarmOSAsset):
model_title = "AnimalAsset" model_title = "AnimalAsset"
@ -209,77 +290,10 @@ class AnimalAssetImporter(ToFarmOSAsset):
return payload return payload
class AnimalTypeImporter(ToFarmOS): class AnimalTypeImporter(ToFarmOSTaxonomy):
model_title = "AnimalType" model_title = "AnimalType"
farmos_taxonomy_type = "animal_type"
supported_fields = [
"uuid",
"name",
]
def get_target_objects(self, **kwargs):
result = self.farmos_client.resource.get("taxonomy_term", "animal_type")
return result["data"]
def get_target_object(self, key):
# fetch from cache, if applicable
if self.caches_target:
return super().get_target_object(key)
# okay now must fetch via API
if self.get_keys() != ["uuid"]:
raise ValueError("must use uuid key for this to work")
uuid = key[0]
try:
result = self.farmos_client.resource.get_id(
"taxonomy_term", "animal_type", str(uuid)
)
except requests.HTTPError as exc:
if exc.response.status_code == 404:
return None
raise
return result["data"]
def normalize_target_object(self, obj):
return {
"uuid": UUID(obj["id"]),
"name": obj["attributes"]["name"],
}
def get_type_payload(self, source_data):
return {
"attributes": {
"name": source_data["name"],
}
}
def create_target_object(self, key, source_data):
if source_data.get("__ignoreme__"):
return None
if self.dry_run:
return source_data
payload = self.get_type_payload(source_data)
result = self.farmos_client.resource.send(
"taxonomy_term", "animal_type", payload
)
normal = self.normalize_target_object(result["data"])
normal["_new_object"] = result["data"]
return normal
def update_target_object(self, asset, source_data, target_data=None):
if self.dry_run:
return asset
payload = self.get_type_payload(source_data)
payload["id"] = str(source_data["uuid"])
result = self.farmos_client.resource.send(
"taxonomy_term", "animal_type", payload
)
return self.normalize_target_object(result["data"])
class GroupAssetImporter(ToFarmOSAsset): class GroupAssetImporter(ToFarmOSAsset):

View file

@ -99,6 +99,7 @@ class FromWuttaFarmToFarmOS(FromWuttaFarmHandler, ToFarmOSHandler):
importers["AnimalAsset"] = AnimalAssetImporter importers["AnimalAsset"] = AnimalAssetImporter
importers["GroupAsset"] = GroupAssetImporter importers["GroupAsset"] = GroupAssetImporter
importers["PlantAsset"] = PlantAssetImporter importers["PlantAsset"] = PlantAssetImporter
importers["Unit"] = UnitImporter
importers["ActivityLog"] = ActivityLogImporter importers["ActivityLog"] = ActivityLogImporter
importers["HarvestLog"] = HarvestLogImporter importers["HarvestLog"] = HarvestLogImporter
importers["MedicalLog"] = MedicalLogImporter importers["MedicalLog"] = MedicalLogImporter
@ -184,6 +185,28 @@ class AnimalTypeImporter(FromWuttaFarm, farmos_importing.model.AnimalTypeImporte
} }
class UnitImporter(FromWuttaFarm, farmos_importing.model.UnitImporter):
"""
WuttaFarm farmOS API exporter for Units
"""
source_model_class = model.Unit
supported_fields = [
"uuid",
"name",
]
drupal_internal_id_field = "drupal_internal__tid"
def normalize_source_object(self, unit):
return {
"uuid": unit.farmos_uuid or self.app.make_true_uuid(),
"name": unit.name,
"_src_object": unit,
}
class GroupAssetImporter(FromWuttaFarm, farmos_importing.model.GroupAssetImporter): class GroupAssetImporter(FromWuttaFarm, farmos_importing.model.GroupAssetImporter):
""" """
WuttaFarm farmOS API exporter for Group Assets WuttaFarm farmOS API exporter for Group Assets

View file

@ -106,6 +106,7 @@ class FromFarmOSToWuttaFarm(FromFarmOSHandler, ToWuttaFarmHandler):
importers["GroupAsset"] = GroupAssetImporter importers["GroupAsset"] = GroupAssetImporter
importers["PlantType"] = PlantTypeImporter importers["PlantType"] = PlantTypeImporter
importers["PlantAsset"] = PlantAssetImporter importers["PlantAsset"] = PlantAssetImporter
importers["Unit"] = UnitImporter
importers["LogType"] = LogTypeImporter importers["LogType"] = LogTypeImporter
importers["ActivityLog"] = ActivityLogImporter importers["ActivityLog"] = ActivityLogImporter
importers["HarvestLog"] = HarvestLogImporter importers["HarvestLog"] = HarvestLogImporter
@ -821,6 +822,35 @@ class UserImporter(FromFarmOS, ToWutta):
############################## ##############################
class UnitImporter(FromFarmOS, ToWutta):
"""
farmOS API WuttaFarm importer for Units
"""
model_class = model.Unit
supported_fields = [
"farmos_uuid",
"drupal_id",
"name",
"description",
]
def get_source_objects(self):
""" """
result = self.farmos_client.resource.get("taxonomy_term", "unit")
return result["data"]
def normalize_source_object(self, unit):
""" """
return {
"farmos_uuid": UUID(unit["id"]),
"drupal_id": unit["attributes"]["drupal_internal__tid"],
"name": unit["attributes"]["name"],
"description": unit["attributes"]["description"],
}
class LogTypeImporter(FromFarmOS, ToWutta): class LogTypeImporter(FromFarmOS, ToWutta):
""" """
farmOS API WuttaFarm importer for Log Types farmOS API WuttaFarm importer for Log Types

View file

@ -139,6 +139,11 @@ class WuttaFarmMenuHandler(base.MenuHandler):
"route": "log_types", "route": "log_types",
"perm": "log_types.list", "perm": "log_types.list",
}, },
{
"title": "Units",
"route": "units",
"perm": "units.list",
},
], ],
} }
@ -233,6 +238,11 @@ class WuttaFarmMenuHandler(base.MenuHandler):
"route": "farmos_log_types", "route": "farmos_log_types",
"perm": "farmos_log_types.list", "perm": "farmos_log_types.list",
}, },
{
"title": "Units",
"route": "farmos_units",
"perm": "farmos_units.list",
},
{"type": "sep"}, {"type": "sep"},
{ {
"title": "Users", "title": "Users",

View file

@ -41,6 +41,7 @@ def includeme(config):
) )
# native table views # native table views
config.include("wuttafarm.web.views.units")
config.include("wuttafarm.web.views.asset_types") config.include("wuttafarm.web.views.asset_types")
config.include("wuttafarm.web.views.assets") config.include("wuttafarm.web.views.assets")
config.include("wuttafarm.web.views.land") config.include("wuttafarm.web.views.land")

View file

@ -129,6 +129,11 @@ class CommonView(base.CommonView):
"structure_assets.list", "structure_assets.list",
"structure_assets.view", "structure_assets.view",
"structure_assets.versions", "structure_assets.versions",
"units.create",
"units.edit",
"units.list",
"units.view",
"units.versions",
] ]
for perm in site_admin_perms: for perm in site_admin_perms:
auth.grant_permission(site_admin, perm) auth.grant_permission(site_admin, perm)

View file

@ -29,6 +29,7 @@ from .master import FarmOSMasterView
def includeme(config): def includeme(config):
config.include("wuttafarm.web.views.farmos.users") config.include("wuttafarm.web.views.farmos.users")
config.include("wuttafarm.web.views.farmos.asset_types") config.include("wuttafarm.web.views.farmos.asset_types")
config.include("wuttafarm.web.views.farmos.units")
config.include("wuttafarm.web.views.farmos.land_types") config.include("wuttafarm.web.views.farmos.land_types")
config.include("wuttafarm.web.views.farmos.land_assets") config.include("wuttafarm.web.views.farmos.land_assets")
config.include("wuttafarm.web.views.farmos.structure_types") config.include("wuttafarm.web.views.farmos.structure_types")

View file

@ -23,16 +23,10 @@
View for farmOS animal types View for farmOS animal types
""" """
import datetime from wuttafarm.web.views.farmos.master import TaxonomyMasterView
import colander
from wuttaweb.forms.schema import WuttaDateTime
from wuttafarm.web.views.farmos import FarmOSMasterView
class AnimalTypeView(FarmOSMasterView): class AnimalTypeView(TaxonomyMasterView):
""" """
Master view for Animal Types in farmOS. Master view for Animal Types in farmOS.
""" """
@ -44,90 +38,14 @@ class AnimalTypeView(FarmOSMasterView):
route_prefix = "farmos_animal_types" route_prefix = "farmos_animal_types"
url_prefix = "/farmOS/animal-types" url_prefix = "/farmOS/animal-types"
farmos_taxonomy_type = "animal_type"
farmos_refurl_path = "/admin/structure/taxonomy/manage/animal_type/overview" farmos_refurl_path = "/admin/structure/taxonomy/manage/animal_type/overview"
grid_columns = [
"name",
"description",
"changed",
]
sort_defaults = "name"
form_fields = [
"name",
"description",
"changed",
]
def get_grid_data(self, columns=None, session=None):
animal_types = self.farmos_client.resource.get("taxonomy_term", "animal_type")
return [self.normalize_animal_type(t) for t in animal_types["data"]]
def configure_grid(self, grid):
g = grid
super().configure_grid(g)
# name
g.set_link("name")
g.set_searchable("name")
# changed
g.set_renderer("changed", "datetime")
def get_instance(self):
animal_type = self.farmos_client.resource.get_id(
"taxonomy_term", "animal_type", self.request.matchdict["uuid"]
)
self.raw_json = animal_type
return self.normalize_animal_type(animal_type["data"])
def get_instance_title(self, animal_type):
return animal_type["name"]
def normalize_animal_type(self, animal_type):
if changed := animal_type["attributes"]["changed"]:
changed = datetime.datetime.fromisoformat(changed)
changed = self.app.localtime(changed)
if description := animal_type["attributes"]["description"]:
description = description["value"]
return {
"uuid": animal_type["id"],
"drupal_id": animal_type["attributes"]["drupal_internal__tid"],
"name": animal_type["attributes"]["name"],
"description": description or colander.null,
"changed": changed,
}
def configure_form(self, form):
f = form
super().configure_form(f)
# description
f.set_widget("description", "notes")
# changed
f.set_node("changed", WuttaDateTime())
def get_xref_buttons(self, animal_type): def get_xref_buttons(self, animal_type):
buttons = super().get_xref_buttons(animal_type)
model = self.app.model model = self.app.model
session = self.Session() session = self.Session()
buttons = [
self.make_button(
"View in farmOS",
primary=True,
url=self.app.get_farmos_url(
f"/taxonomy/term/{animal_type['drupal_id']}"
),
target="_blank",
icon_left="external-link-alt",
)
]
if wf_animal_type := ( if wf_animal_type := (
session.query(model.AnimalType) session.query(model.AnimalType)
.filter(model.AnimalType.farmos_uuid == animal_type["uuid"]) .filter(model.AnimalType.farmos_uuid == animal_type["uuid"])

View file

@ -23,11 +23,14 @@
Base class for farmOS master views Base class for farmOS master views
""" """
import datetime
import json import json
import colander
import markdown import markdown
from wuttaweb.views import MasterView from wuttaweb.views import MasterView
from wuttaweb.forms.schema import WuttaDateTime
from wuttafarm.web.util import save_farmos_oauth2_token from wuttafarm.web.util import save_farmos_oauth2_token
@ -100,3 +103,90 @@ class FarmOSMasterView(MasterView):
) )
return context return context
class TaxonomyMasterView(FarmOSMasterView):
"""
Base class for farmOS "taxonomy term" views
"""
farmos_taxonomy_type = None
grid_columns = [
"name",
"description",
"changed",
]
sort_defaults = "name"
form_fields = [
"name",
"description",
"changed",
]
def get_grid_data(self, columns=None, session=None):
result = self.farmos_client.resource.get(
"taxonomy_term", self.farmos_taxonomy_type
)
return [self.normalize_taxonomy_term(t) for t in result["data"]]
def normalize_taxonomy_term(self, term):
if changed := term["attributes"]["changed"]:
changed = datetime.datetime.fromisoformat(changed)
changed = self.app.localtime(changed)
if description := term["attributes"]["description"]:
description = description["value"]
return {
"uuid": term["id"],
"drupal_id": term["attributes"]["drupal_internal__tid"],
"name": term["attributes"]["name"],
"description": description or colander.null,
"changed": changed,
}
def configure_grid(self, grid):
g = grid
super().configure_grid(g)
# name
g.set_link("name")
g.set_searchable("name")
# changed
g.set_renderer("changed", "datetime")
def get_instance(self):
result = self.farmos_client.resource.get_id(
"taxonomy_term", self.farmos_taxonomy_type, self.request.matchdict["uuid"]
)
self.raw_json = result
return self.normalize_taxonomy_term(result["data"])
def get_instance_title(self, term):
return term["name"]
def configure_form(self, form):
f = form
super().configure_form(f)
# description
f.set_widget("description", "notes")
# changed
f.set_node("changed", WuttaDateTime())
def get_xref_buttons(self, term):
return [
self.make_button(
"View in farmOS",
primary=True,
url=self.app.get_farmos_url(f"/taxonomy/term/{term['drupal_id']}"),
target="_blank",
icon_left="external-link-alt",
)
]

View file

@ -30,12 +30,13 @@ import colander
from wuttaweb.forms.schema import WuttaDateTime from wuttaweb.forms.schema import WuttaDateTime
from wuttaweb.forms.widgets import WuttaDateTimeWidget from wuttaweb.forms.widgets import WuttaDateTimeWidget
from wuttafarm.web.views.farmos.master import TaxonomyMasterView
from wuttafarm.web.views.farmos import FarmOSMasterView from wuttafarm.web.views.farmos import FarmOSMasterView
from wuttafarm.web.forms.schema import UsersType, StructureType, FarmOSPlantTypes from wuttafarm.web.forms.schema import UsersType, StructureType, FarmOSPlantTypes
from wuttafarm.web.forms.widgets import ImageWidget from wuttafarm.web.forms.widgets import ImageWidget
class PlantTypeView(FarmOSMasterView): class PlantTypeView(TaxonomyMasterView):
""" """
Master view for Plant Types in farmOS. Master view for Plant Types in farmOS.
""" """
@ -47,90 +48,14 @@ class PlantTypeView(FarmOSMasterView):
route_prefix = "farmos_plant_types" route_prefix = "farmos_plant_types"
url_prefix = "/farmOS/plant-types" url_prefix = "/farmOS/plant-types"
farmos_taxonomy_type = "plant_type"
farmos_refurl_path = "/admin/structure/taxonomy/manage/plant_type/overview" farmos_refurl_path = "/admin/structure/taxonomy/manage/plant_type/overview"
grid_columns = [
"name",
"description",
"changed",
]
sort_defaults = "name"
form_fields = [
"name",
"description",
"changed",
]
def get_grid_data(self, columns=None, session=None):
result = self.farmos_client.resource.get("taxonomy_term", "plant_type")
return [self.normalize_plant_type(t) for t in result["data"]]
def configure_grid(self, grid):
g = grid
super().configure_grid(g)
# name
g.set_link("name")
g.set_searchable("name")
# changed
g.set_renderer("changed", "datetime")
def get_instance(self):
plant_type = self.farmos_client.resource.get_id(
"taxonomy_term", "plant_type", self.request.matchdict["uuid"]
)
self.raw_json = plant_type
return self.normalize_plant_type(plant_type["data"])
def get_instance_title(self, plant_type):
return plant_type["name"]
def normalize_plant_type(self, plant_type):
if changed := plant_type["attributes"]["changed"]:
changed = datetime.datetime.fromisoformat(changed)
changed = self.app.localtime(changed)
if description := plant_type["attributes"]["description"]:
description = description["value"]
return {
"uuid": plant_type["id"],
"drupal_id": plant_type["attributes"]["drupal_internal__tid"],
"name": plant_type["attributes"]["name"],
"description": description or colander.null,
"changed": changed,
}
def configure_form(self, form):
f = form
super().configure_form(f)
# description
f.set_widget("description", "notes")
# changed
f.set_node("changed", WuttaDateTime())
def get_xref_buttons(self, plant_type): def get_xref_buttons(self, plant_type):
buttons = super().get_xref_buttons(plant_type)
model = self.app.model model = self.app.model
session = self.Session() session = self.Session()
buttons = [
self.make_button(
"View in farmOS",
primary=True,
url=self.app.get_farmos_url(
f"/taxonomy/term/{plant_type['drupal_id']}"
),
target="_blank",
icon_left="external-link-alt",
)
]
if wf_plant_type := ( if wf_plant_type := (
session.query(model.PlantType) session.query(model.PlantType)
.filter(model.PlantType.farmos_uuid == plant_type["uuid"]) .filter(model.PlantType.farmos_uuid == plant_type["uuid"])

View file

@ -0,0 +1,74 @@
# -*- coding: utf-8; -*-
################################################################################
#
# WuttaFarm --Web app to integrate with and extend farmOS
# Copyright © 2026 Lance Edgar
#
# This file is part of WuttaFarm.
#
# WuttaFarm is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# WuttaFarm is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# WuttaFarm. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
View for farmOS units
"""
from wuttafarm.web.views.farmos.master import TaxonomyMasterView
class UnitView(TaxonomyMasterView):
"""
Master view for Units in farmOS.
"""
model_name = "farmos_unit"
model_title = "farmOS Unit"
model_title_plural = "farmOS Units"
route_prefix = "farmos_units"
url_prefix = "/farmOS/units"
farmos_taxonomy_type = "unit"
farmos_refurl_path = "/admin/structure/taxonomy/manage/unit/overview"
def get_xref_buttons(self, unit):
buttons = super().get_xref_buttons(unit)
model = self.app.model
session = self.Session()
if wf_unit := (
session.query(model.Unit)
.filter(model.Unit.farmos_uuid == unit["uuid"])
.first()
):
buttons.append(
self.make_button(
f"View {self.app.get_title()} record",
primary=True,
url=self.request.route_url("units.view", uuid=wf_unit.uuid),
icon_left="eye",
)
)
return buttons
def defaults(config, **kwargs):
base = globals()
UnitView = kwargs.get("UnitView", base["UnitView"])
UnitView.defaults(config)
def includeme(config):
defaults(config)

View file

@ -0,0 +1,95 @@
# -*- coding: utf-8; -*-
################################################################################
#
# WuttaFarm --Web app to integrate with and extend farmOS
# Copyright © 2026 Lance Edgar
#
# This file is part of WuttaFarm.
#
# WuttaFarm is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# WuttaFarm is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# WuttaFarm. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Master view for Units
"""
from wuttafarm.web.views import WuttaFarmMasterView
from wuttafarm.db.model import Unit
class UnitView(WuttaFarmMasterView):
"""
Master view for Units
"""
model_class = Unit
route_prefix = "units"
url_prefix = "/animal-types"
farmos_refurl_path = "/admin/structure/taxonomy/manage/unit/overview"
grid_columns = [
"name",
"description",
]
sort_defaults = "name"
filter_defaults = {
"name": {"active": True, "verb": "contains"},
}
form_fields = [
"name",
"description",
"farmos_uuid",
"drupal_id",
]
def configure_grid(self, grid):
g = grid
super().configure_grid(g)
# name
g.set_link("name")
def get_farmos_url(self, unit):
return self.app.get_farmos_url(f"/taxonomy/term/{unit.drupal_id}")
def get_xref_buttons(self, unit):
buttons = super().get_xref_buttons(unit)
if unit.farmos_uuid:
buttons.append(
self.make_button(
"View farmOS record",
primary=True,
url=self.request.route_url(
"farmos_units.view", uuid=unit.farmos_uuid
),
icon_left="eye",
)
)
return buttons
def defaults(config, **kwargs):
base = globals()
UnitView = kwargs.get("UnitView", base["UnitView"])
UnitView.defaults(config)
def includeme(config):
defaults(config)