feat: convert group assets to use common base/mixin

This commit is contained in:
Lance Edgar 2026-02-15 14:07:03 -06:00
parent 3435b4714e
commit 2fc9c88cd5
8 changed files with 239 additions and 172 deletions

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

@ -34,5 +34,5 @@ from .assets import AssetType, Asset, AssetParent
from .land import LandType, LandAsset from .land import LandType, LandAsset
from .structures import StructureType, StructureAsset from .structures import StructureType, StructureAsset
from .animals import AnimalType, AnimalAsset from .animals import AnimalType, AnimalAsset
from .groups import Group from .groups import GroupAsset
from .logs import LogType, ActivityLog from .logs import LogType, ActivityLog

View file

@ -23,85 +23,23 @@
Model definition for Groups Model definition for Groups
""" """
import sqlalchemy as sa
from sqlalchemy import orm
from wuttjamaican.db import model from wuttjamaican.db import model
from wuttafarm.db.model.assets import AssetMixin, add_asset_proxies
class Group(model.Base):
class GroupAsset(AssetMixin, model.Base):
""" """
Represents a "group" from farmOS Represents a group asset from farmOS
""" """
__tablename__ = "group" __tablename__ = "asset_group"
__versioned__ = {} __versioned__ = {}
__wutta_hint__ = { __wutta_hint__ = {
"model_title": "Group", "model_title": "Group Asset",
"model_title_plural": "Groups", "model_title_plural": "Group Assets",
"farmos_asset_type": "group",
} }
uuid = model.uuid_column()
name = sa.Column( add_asset_proxies(GroupAsset)
sa.String(length=100),
nullable=False,
unique=True,
doc="""
Name for the group.
""",
)
is_location = sa.Column(
sa.Boolean(),
nullable=False,
doc="""
Whether the group is considered to be a location.
""",
)
is_fixed = sa.Column(
sa.Boolean(),
nullable=False,
doc="""
Whether the group location is fixed.
""",
)
archived = sa.Column(
sa.Boolean(),
nullable=False,
default=False,
doc="""
Whether the group is archived.
""",
)
notes = sa.Column(
sa.Text(),
nullable=True,
doc="""
Arbitrary notes for the group.
""",
)
farmos_uuid = sa.Column(
model.UUID(),
nullable=True,
unique=True,
doc="""
UUID for the group within farmOS.
""",
)
drupal_id = sa.Column(
sa.Integer(),
nullable=True,
unique=True,
doc="""
Drupal internal ID for the group.
""",
)
def __str__(self):
return self.name or ""

View file

@ -103,7 +103,7 @@ class FromFarmOSToWuttaFarm(FromFarmOSHandler, ToWuttaFarmHandler):
importers["StructureAsset"] = StructureAssetImporter importers["StructureAsset"] = StructureAssetImporter
importers["AnimalType"] = AnimalTypeImporter importers["AnimalType"] = AnimalTypeImporter
importers["AnimalAsset"] = AnimalAssetImporter importers["AnimalAsset"] = AnimalAssetImporter
importers["Group"] = GroupImporter importers["GroupAsset"] = GroupAssetImporter
importers["LogType"] = LogTypeImporter importers["LogType"] = LogTypeImporter
importers["ActivityLog"] = ActivityLogImporter importers["ActivityLog"] = ActivityLogImporter
return importers return importers
@ -460,21 +460,25 @@ class AssetTypeImporter(FromFarmOS, ToWutta):
} }
class GroupImporter(FromFarmOS, ToWutta): class GroupAssetImporter(AssetImporterBase):
""" """
farmOS API WuttaFarm importer for Groups farmOS API WuttaFarm importer for Group Assets
""" """
model_class = model.Group model_class = model.GroupAsset
supported_fields = [ supported_fields = [
"farmos_uuid", "farmos_uuid",
"drupal_id", "drupal_id",
"name", "asset_type",
"asset_name",
"is_location", "is_location",
"is_fixed", "is_fixed",
"notes", "notes",
"archived", "archived",
"image_url",
"thumbnail_url",
"parents",
] ]
def get_source_objects(self): def get_source_objects(self):
@ -484,23 +488,13 @@ class GroupImporter(FromFarmOS, ToWutta):
def normalize_source_object(self, group): def normalize_source_object(self, group):
""" """ """ """
if notes := group["attributes"]["notes"]: data = self.normalize_asset(group)
notes = notes["value"] data.update(
{
if self.farmos_4x: "asset_type": "group",
archived = group["attributes"]["archived"]
else:
archived = group["attributes"]["status"] == "archived"
return {
"farmos_uuid": UUID(group["id"]),
"drupal_id": group["attributes"]["drupal_internal__id"],
"name": group["attributes"]["name"],
"is_location": group["attributes"]["is_location"],
"is_fixed": group["attributes"]["is_fixed"],
"archived": archived,
"notes": notes,
} }
)
return data
class LandAssetImporter(AssetImporterBase): class LandAssetImporter(AssetImporterBase):

View file

@ -54,6 +54,11 @@ class WuttaFarmMenuHandler(base.MenuHandler):
"route": "animal_assets", "route": "animal_assets",
"perm": "animal_assets.list", "perm": "animal_assets.list",
}, },
{
"title": "Group",
"route": "group_assets",
"perm": "group_assets.list",
},
{ {
"title": "Land", "title": "Land",
"route": "land_assets", "route": "land_assets",
@ -65,12 +70,6 @@ class WuttaFarmMenuHandler(base.MenuHandler):
"perm": "structure_assets.list", "perm": "structure_assets.list",
}, },
{"type": "sep"}, {"type": "sep"},
{
"title": "Groups",
"route": "groups",
"perm": "groups.list",
},
{"type": "sep"},
{ {
"title": "Animal Types", "title": "Animal Types",
"route": "animal_types", "route": "animal_types",

View file

@ -239,6 +239,8 @@ class AssetMasterView(WuttaFarmMasterView):
route = None route = None
if asset.asset_type == "animal": if asset.asset_type == "animal":
route = "farmos_animals.view" route = "farmos_animals.view"
elif asset.asset_type == "group":
route = "farmos_groups.view"
elif asset.asset_type == "land": elif asset.asset_type == "land":
route = "farmos_land_assets.view" route = "farmos_land_assets.view"
elif asset.asset_type == "structure": elif asset.asset_type == "structure":

View file

@ -166,15 +166,15 @@ class GroupView(FarmOSMasterView):
] ]
if wf_group := ( if wf_group := (
session.query(model.Group) session.query(model.GroupAsset)
.filter(model.Group.farmos_uuid == group["uuid"]) .filter(model.GroupAsset.farmos_uuid == group["uuid"])
.first() .first()
): ):
buttons.append( buttons.append(
self.make_button( self.make_button(
f"View {self.app.get_title()} record", f"View {self.app.get_title()} record",
primary=True, primary=True,
url=self.request.route_url("groups.view", uuid=wf_group.uuid), url=self.request.route_url("group_assets.view", uuid=wf_group.uuid),
icon_left="eye", icon_left="eye",
) )
) )

View file

@ -23,40 +23,30 @@
Master view for Groups Master view for Groups
""" """
from wuttafarm.db.model.groups import Group from wuttafarm.web.views.assets import AssetMasterView
from wuttafarm.web.views import WuttaFarmMasterView from wuttafarm.db.model.groups import GroupAsset
class GroupView(WuttaFarmMasterView): class GroupView(AssetMasterView):
""" """
Master view for Groups Master view for Groups
""" """
model_class = Group model_class = GroupAsset
route_prefix = "groups" route_prefix = "group_assets"
url_prefix = "/groups" url_prefix = "/assets/group"
farmos_refurl_path = "/assets/group" farmos_refurl_path = "/assets/group"
labels = {
"name": "Asset Name",
}
grid_columns = [ grid_columns = [
"thumbnail",
"drupal_id", "drupal_id",
"name", "asset_name",
"archived", "archived",
] ]
sort_defaults = "name"
filter_defaults = {
"name": {"active": True, "verb": "contains"},
"archived": {"active": True, "verb": "is_false"},
}
form_fields = [ form_fields = [
"name", "asset_name",
"notes", "notes",
"asset_type", "asset_type",
"archived", "archived",
@ -64,56 +54,6 @@ class GroupView(WuttaFarmMasterView):
"drupal_id", "drupal_id",
] ]
def configure_grid(self, grid):
g = grid
super().configure_grid(g)
# drupal_id
g.set_label("drupal_id", "ID", column_only=True)
# name
g.set_link("name")
def grid_row_class(self, group, data, i):
""" """
if group.archived:
return "has-background-warning"
return None
def configure_form(self, form):
f = form
super().configure_form(f)
# notes
f.set_widget("notes", "notes")
# asset_type
if self.creating:
f.remove("asset_type")
else:
f.set_default("asset_type", "Group")
f.set_readonly("asset_type")
def get_farmos_url(self, group):
return self.app.get_farmos_url(f"/asset/{group.drupal_id}")
def get_xref_buttons(self, group):
buttons = super().get_xref_buttons(group)
if group.farmos_uuid:
buttons.append(
self.make_button(
"View farmOS record",
primary=True,
url=self.request.route_url(
"farmos_groups.view", uuid=group.farmos_uuid
),
icon_left="eye",
)
)
return buttons
def defaults(config, **kwargs): def defaults(config, **kwargs):
base = globals() base = globals()