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 .structures import StructureType, StructureAsset
from .animals import AnimalType, AnimalAsset
from .groups import Group
from .groups import GroupAsset
from .logs import LogType, ActivityLog

View file

@ -23,85 +23,23 @@
Model definition for Groups
"""
import sqlalchemy as sa
from sqlalchemy import orm
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__ = {}
__wutta_hint__ = {
"model_title": "Group",
"model_title_plural": "Groups",
"model_title": "Group Asset",
"model_title_plural": "Group Assets",
"farmos_asset_type": "group",
}
uuid = model.uuid_column()
name = sa.Column(
sa.String(length=100),
nullable=False,
unique=True,
doc="""
Name for the group.
""",
)
is_location = sa.Column(
sa.Boolean(),
nullable=False,
doc="""
Whether the group is considered to be a location.
""",
)
is_fixed = sa.Column(
sa.Boolean(),
nullable=False,
doc="""
Whether the group location is fixed.
""",
)
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 ""
add_asset_proxies(GroupAsset)

View file

@ -103,7 +103,7 @@ class FromFarmOSToWuttaFarm(FromFarmOSHandler, ToWuttaFarmHandler):
importers["StructureAsset"] = StructureAssetImporter
importers["AnimalType"] = AnimalTypeImporter
importers["AnimalAsset"] = AnimalAssetImporter
importers["Group"] = GroupImporter
importers["GroupAsset"] = GroupAssetImporter
importers["LogType"] = LogTypeImporter
importers["ActivityLog"] = ActivityLogImporter
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 = [
"farmos_uuid",
"drupal_id",
"name",
"asset_type",
"asset_name",
"is_location",
"is_fixed",
"notes",
"archived",
"image_url",
"thumbnail_url",
"parents",
]
def get_source_objects(self):
@ -484,23 +488,13 @@ class GroupImporter(FromFarmOS, ToWutta):
def normalize_source_object(self, group):
""" """
if notes := group["attributes"]["notes"]:
notes = notes["value"]
if self.farmos_4x:
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,
data = self.normalize_asset(group)
data.update(
{
"asset_type": "group",
}
)
return data
class LandAssetImporter(AssetImporterBase):

View file

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

View file

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

View file

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

View file

@ -23,40 +23,30 @@
Master view for Groups
"""
from wuttafarm.db.model.groups import Group
from wuttafarm.web.views import WuttaFarmMasterView
from wuttafarm.web.views.assets import AssetMasterView
from wuttafarm.db.model.groups import GroupAsset
class GroupView(WuttaFarmMasterView):
class GroupView(AssetMasterView):
"""
Master view for Groups
"""
model_class = Group
route_prefix = "groups"
url_prefix = "/groups"
model_class = GroupAsset
route_prefix = "group_assets"
url_prefix = "/assets/group"
farmos_refurl_path = "/assets/group"
labels = {
"name": "Asset Name",
}
grid_columns = [
"thumbnail",
"drupal_id",
"name",
"asset_name",
"archived",
]
sort_defaults = "name"
filter_defaults = {
"name": {"active": True, "verb": "contains"},
"archived": {"active": True, "verb": "is_false"},
}
form_fields = [
"name",
"asset_name",
"notes",
"asset_type",
"archived",
@ -64,56 +54,6 @@ class GroupView(WuttaFarmMasterView):
"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):
base = globals()