feat: add schema, import support for Asset.owners
This commit is contained in:
parent
ce103137a5
commit
eb16990b0b
7 changed files with 273 additions and 72 deletions
|
|
@ -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")
|
||||
|
|
@ -193,6 +193,19 @@ class Asset(model.Base):
|
|||
creator=lambda parent: AssetParent(parent=parent),
|
||||
)
|
||||
|
||||
_owners = orm.relationship(
|
||||
"AssetOwner",
|
||||
cascade="all, delete-orphan",
|
||||
cascade_backrefs=False,
|
||||
back_populates="asset",
|
||||
)
|
||||
|
||||
owners = association_proxy(
|
||||
"_owners",
|
||||
"user",
|
||||
creator=lambda user: AssetOwner(user=user),
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.asset_name or ""
|
||||
|
||||
|
|
@ -225,6 +238,8 @@ def add_asset_proxies(subclass):
|
|||
Asset.make_proxy(subclass, "asset", "thumbnail_url")
|
||||
Asset.make_proxy(subclass, "asset", "image_url")
|
||||
Asset.make_proxy(subclass, "asset", "archived")
|
||||
Asset.make_proxy(subclass, "asset", "parents")
|
||||
Asset.make_proxy(subclass, "asset", "owners")
|
||||
|
||||
|
||||
class EggMixin:
|
||||
|
|
@ -262,3 +277,27 @@ class AssetParent(model.Base):
|
|||
Asset,
|
||||
foreign_keys=parent_uuid,
|
||||
)
|
||||
|
||||
|
||||
class AssetOwner(model.Base):
|
||||
"""
|
||||
Represents a "asset's owner relationship" from farmOS.
|
||||
"""
|
||||
|
||||
__tablename__ = "asset_owner"
|
||||
__versioned__ = {}
|
||||
|
||||
uuid = model.uuid_column()
|
||||
|
||||
asset_uuid = model.uuid_fk_column("asset.uuid", nullable=False)
|
||||
asset = orm.relationship(
|
||||
Asset,
|
||||
foreign_keys=asset_uuid,
|
||||
back_populates="_owners",
|
||||
)
|
||||
|
||||
user_uuid = model.uuid_fk_column("user.uuid", nullable=False)
|
||||
user = orm.relationship(
|
||||
model.User,
|
||||
foreign_keys=user_uuid,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -187,6 +187,7 @@ class AssetImporterBase(FromFarmOS, ToWutta):
|
|||
fields.extend(
|
||||
[
|
||||
"parents",
|
||||
"owners",
|
||||
]
|
||||
)
|
||||
return fields
|
||||
|
|
@ -194,8 +195,9 @@ class AssetImporterBase(FromFarmOS, ToWutta):
|
|||
def get_source_objects(self):
|
||||
""" """
|
||||
asset_type = self.get_farmos_asset_type()
|
||||
result = self.farmos_client.asset.get(asset_type)
|
||||
return result["data"]
|
||||
return list(
|
||||
self.farmos_client.asset.iterate(asset_type, params={"include": "image"})
|
||||
)
|
||||
|
||||
def normalize_source_data(self, **kwargs):
|
||||
""" """
|
||||
|
|
@ -208,49 +210,40 @@ class AssetImporterBase(FromFarmOS, ToWutta):
|
|||
|
||||
return data
|
||||
|
||||
def normalize_asset(self, asset):
|
||||
def normalize_source_object(self, asset):
|
||||
""" """
|
||||
image_url = None
|
||||
thumbnail_url = None
|
||||
if relationships := asset.get("relationships"):
|
||||
data = self.normal.normalize_farmos_asset(asset)
|
||||
|
||||
if image := relationships.get("image"):
|
||||
if image["data"]:
|
||||
image = self.farmos_client.resource.get_id(
|
||||
"file", "file", image["data"][0]["id"]
|
||||
)
|
||||
if image_style := image["data"]["attributes"].get(
|
||||
"image_style_uri"
|
||||
):
|
||||
image_url = image_style["large"]
|
||||
thumbnail_url = image_style["thumbnail"]
|
||||
data["farmos_uuid"] = UUID(data.pop("uuid"))
|
||||
data["asset_type"] = self.get_asset_type(asset)
|
||||
|
||||
if notes := asset["attributes"]["notes"]:
|
||||
notes = notes["value"]
|
||||
if "image_url" in self.fields or "thumbnail_url" in self.fields:
|
||||
data["image_url"] = None
|
||||
data["thumbnail_url"] = None
|
||||
if relationships := asset.get("relationships"):
|
||||
|
||||
if self.farmos_4x:
|
||||
archived = asset["attributes"]["archived"]
|
||||
else:
|
||||
archived = asset["attributes"]["status"] == "archived"
|
||||
if image := relationships.get("image"):
|
||||
if image["data"]:
|
||||
image = self.farmos_client.resource.get_id(
|
||||
"file", "file", image["data"][0]["id"]
|
||||
)
|
||||
if image_style := image["data"]["attributes"].get(
|
||||
"image_style_uri"
|
||||
):
|
||||
data["image_url"] = image_style["large"]
|
||||
data["thumbnail_url"] = image_style["thumbnail"]
|
||||
|
||||
parents = None
|
||||
if "parents" in self.fields:
|
||||
parents = []
|
||||
data["parents"] = []
|
||||
for parent in asset["relationships"]["parent"]["data"]:
|
||||
parents.append((self.get_asset_type(parent), UUID(parent["id"])))
|
||||
data["parents"].append(
|
||||
(self.get_asset_type(parent), UUID(parent["id"]))
|
||||
)
|
||||
|
||||
return {
|
||||
"farmos_uuid": UUID(asset["id"]),
|
||||
"drupal_id": asset["attributes"]["drupal_internal__id"],
|
||||
"asset_name": asset["attributes"]["name"],
|
||||
"is_location": asset["attributes"]["is_location"],
|
||||
"is_fixed": asset["attributes"]["is_fixed"],
|
||||
"archived": archived,
|
||||
"notes": notes,
|
||||
"image_url": image_url,
|
||||
"thumbnail_url": thumbnail_url,
|
||||
"parents": parents,
|
||||
}
|
||||
if "owners" in self.fields:
|
||||
data["owners"] = [UUID(uuid) for uuid in data["owner_uuids"]]
|
||||
|
||||
return data
|
||||
|
||||
def get_asset_type(self, asset):
|
||||
return asset["type"].split("--")[1]
|
||||
|
|
@ -259,10 +252,10 @@ class AssetImporterBase(FromFarmOS, ToWutta):
|
|||
data = super().normalize_target_object(asset)
|
||||
|
||||
if "parents" in self.fields:
|
||||
data["parents"] = [
|
||||
(p.parent.asset_type, p.parent.farmos_uuid)
|
||||
for p in asset.asset._parents
|
||||
]
|
||||
data["parents"] = [(p.asset_type, p.farmos_uuid) for p in asset.parents]
|
||||
|
||||
if "owners" in self.fields:
|
||||
data["owners"] = [user.farmos_uuid for user in asset.owners]
|
||||
|
||||
return data
|
||||
|
||||
|
|
@ -303,6 +296,30 @@ class AssetImporterBase(FromFarmOS, ToWutta):
|
|||
)
|
||||
self.target_session.delete(parent)
|
||||
|
||||
if "owners" in self.fields:
|
||||
if not target_data or target_data["owners"] != source_data["owners"]:
|
||||
|
||||
for farmos_uuid in source_data["owners"]:
|
||||
if not target_data or farmos_uuid not in target_data["owners"]:
|
||||
user = (
|
||||
self.target_session.query(model.User)
|
||||
.join(model.WuttaFarmUser)
|
||||
.filter(model.WuttaFarmUser.farmos_uuid == farmos_uuid)
|
||||
.one()
|
||||
)
|
||||
asset.owners.append(user)
|
||||
|
||||
if target_data:
|
||||
for farmos_uuid in target_data["owners"]:
|
||||
if farmos_uuid not in source_data["owners"]:
|
||||
user = (
|
||||
self.target_session.query(model.User)
|
||||
.join(model.WuttaFarmUser)
|
||||
.filter(model.WuttaFarmUser.farmos_uuid == farmos_uuid)
|
||||
.one()
|
||||
)
|
||||
asset.owners.remove(user)
|
||||
|
||||
return asset
|
||||
|
||||
|
||||
|
|
@ -338,11 +355,6 @@ class AnimalAssetImporter(AssetImporterBase):
|
|||
if animal_type.farmos_uuid:
|
||||
self.animal_types_by_farmos_uuid[animal_type.farmos_uuid] = animal_type
|
||||
|
||||
def get_source_objects(self):
|
||||
""" """
|
||||
animals = self.farmos_client.asset.get("animal")
|
||||
return animals["data"]
|
||||
|
||||
def normalize_source_object(self, animal):
|
||||
""" """
|
||||
animal_type_uuid = None
|
||||
|
|
@ -374,10 +386,9 @@ class AnimalAssetImporter(AssetImporterBase):
|
|||
else:
|
||||
sterile = animal["attributes"]["is_castrated"]
|
||||
|
||||
data = self.normalize_asset(animal)
|
||||
data = super().normalize_source_object(animal)
|
||||
data.update(
|
||||
{
|
||||
"asset_type": "animal",
|
||||
"animal_type_uuid": animal_type_uuid,
|
||||
"sex": animal["attributes"]["sex"],
|
||||
"is_sterile": sterile,
|
||||
|
|
@ -468,17 +479,11 @@ class GroupAssetImporter(AssetImporterBase):
|
|||
"parents",
|
||||
]
|
||||
|
||||
def get_source_objects(self):
|
||||
""" """
|
||||
groups = self.farmos_client.asset.get("group")
|
||||
return groups["data"]
|
||||
|
||||
def normalize_source_object(self, group):
|
||||
""" """
|
||||
data = self.normalize_asset(group)
|
||||
data = super().normalize_source_object(group)
|
||||
data.update(
|
||||
{
|
||||
"asset_type": "group",
|
||||
"produces_eggs": group["attributes"]["produces_eggs"],
|
||||
}
|
||||
)
|
||||
|
|
@ -514,11 +519,6 @@ class LandAssetImporter(AssetImporterBase):
|
|||
for land_type in self.target_session.query(model.LandType):
|
||||
self.land_types_by_id[land_type.drupal_id] = land_type
|
||||
|
||||
def get_source_objects(self):
|
||||
""" """
|
||||
land_assets = self.farmos_client.asset.get("land")
|
||||
return land_assets["data"]
|
||||
|
||||
def normalize_source_object(self, land):
|
||||
""" """
|
||||
land_type_id = land["attributes"]["land_type"]
|
||||
|
|
@ -529,10 +529,9 @@ class LandAssetImporter(AssetImporterBase):
|
|||
)
|
||||
return None
|
||||
|
||||
data = self.normalize_asset(land)
|
||||
data = super().normalize_source_object(land)
|
||||
data.update(
|
||||
{
|
||||
"asset_type": "land",
|
||||
"land_type_uuid": land_type.uuid,
|
||||
}
|
||||
)
|
||||
|
|
@ -638,10 +637,9 @@ class PlantAssetImporter(AssetImporterBase):
|
|||
else:
|
||||
log.warning("plant type not found: %s", plant_type["id"])
|
||||
|
||||
data = self.normalize_asset(plant)
|
||||
data = super().normalize_source_object(plant)
|
||||
data.update(
|
||||
{
|
||||
"asset_type": "plant",
|
||||
"plant_types": set(plant_types),
|
||||
}
|
||||
)
|
||||
|
|
@ -718,11 +716,6 @@ class StructureAssetImporter(AssetImporterBase):
|
|||
for structure_type in self.target_session.query(model.StructureType):
|
||||
self.structure_types_by_id[structure_type.drupal_id] = structure_type
|
||||
|
||||
def get_source_objects(self):
|
||||
""" """
|
||||
structures = self.farmos_client.asset.get("structure")
|
||||
return structures["data"]
|
||||
|
||||
def normalize_source_object(self, structure):
|
||||
""" """
|
||||
structure_type_id = structure["attributes"]["structure_type"]
|
||||
|
|
@ -735,10 +728,9 @@ class StructureAssetImporter(AssetImporterBase):
|
|||
)
|
||||
return None
|
||||
|
||||
data = self.normalize_asset(structure)
|
||||
data = super().normalize_source_object(structure)
|
||||
data.update(
|
||||
{
|
||||
"asset_type": "structure",
|
||||
"structure_type_uuid": structure_type.uuid,
|
||||
}
|
||||
)
|
||||
|
|
@ -1167,7 +1159,7 @@ class LogImporterBase(FromFarmOS, ToWutta):
|
|||
if not target_data or target_data["owners"] != source_data["owners"]:
|
||||
|
||||
for farmos_uuid in source_data["owners"]:
|
||||
if not target_data or farmos_uuid not in target_data["assets"]:
|
||||
if not target_data or farmos_uuid not in target_data["owners"]:
|
||||
user = (
|
||||
self.target_session.query(model.User)
|
||||
.join(model.WuttaFarmUser)
|
||||
|
|
|
|||
|
|
@ -84,6 +84,40 @@ class Normalizer(GenericHandler):
|
|||
self._farmos_units = units
|
||||
return self._farmos_units
|
||||
|
||||
def normalize_farmos_asset(self, asset, included={}):
|
||||
""" """
|
||||
|
||||
if notes := asset["attributes"]["notes"]:
|
||||
notes = notes["value"]
|
||||
|
||||
owner_objects = []
|
||||
owner_uuids = []
|
||||
if relationships := asset.get("relationships"):
|
||||
|
||||
if owners := relationships.get("owner"):
|
||||
for user in owners["data"]:
|
||||
user_uuid = user["id"]
|
||||
owner_uuids.append(user_uuid)
|
||||
if user := included.get(user_uuid):
|
||||
owner_objects.append(
|
||||
{
|
||||
"uuid": user["id"],
|
||||
"name": user["attributes"]["name"],
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
"uuid": asset["id"],
|
||||
"drupal_id": asset["attributes"]["drupal_internal__id"],
|
||||
"asset_name": asset["attributes"]["name"],
|
||||
"is_location": asset["attributes"]["is_location"],
|
||||
"is_fixed": asset["attributes"]["is_fixed"],
|
||||
"archived": asset["attributes"]["archived"],
|
||||
"notes": notes,
|
||||
"owners": owner_objects,
|
||||
"owner_uuids": owner_uuids,
|
||||
}
|
||||
|
||||
def normalize_farmos_log(self, log, included={}):
|
||||
|
||||
if timestamp := log["attributes"]["timestamp"]:
|
||||
|
|
|
|||
|
|
@ -228,6 +228,9 @@ class AnimalAssetView(AssetMasterView):
|
|||
"birthdate",
|
||||
"is_sterile",
|
||||
"sex",
|
||||
"group_membership",
|
||||
"owners",
|
||||
"locations",
|
||||
"archived",
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -136,6 +136,10 @@ class AssetMasterView(WuttaFarmMasterView):
|
|||
# parents
|
||||
g.set_renderer("parents", self.render_parents_for_grid)
|
||||
|
||||
# owners
|
||||
g.set_label("owners", "Owner")
|
||||
g.set_renderer("owners", self.render_owners_for_grid)
|
||||
|
||||
# archived
|
||||
g.set_renderer("archived", "boolean")
|
||||
g.set_sorter("archived", model.Asset.archived)
|
||||
|
|
@ -155,6 +159,17 @@ class AssetMasterView(WuttaFarmMasterView):
|
|||
parents = [str(p.parent) for p in asset.parents]
|
||||
return ", ".join(parents)
|
||||
|
||||
def render_owners_for_grid(self, asset, field, value):
|
||||
|
||||
if self.farmos_style_grid_links:
|
||||
links = []
|
||||
for user in asset.owners:
|
||||
url = self.request.route_url("users.view", uuid=user.uuid)
|
||||
links.append(tags.link_to(user.username, url))
|
||||
return ", ".join(links)
|
||||
|
||||
return ", ".join([user.username for user in asset.owners])
|
||||
|
||||
def grid_row_class(self, asset, data, i):
|
||||
""" """
|
||||
if asset.archived:
|
||||
|
|
@ -314,8 +329,11 @@ class AllAssetView(AssetMasterView):
|
|||
"thumbnail",
|
||||
"drupal_id",
|
||||
"asset_name",
|
||||
"group_membership",
|
||||
"asset_type",
|
||||
"parents",
|
||||
"owners",
|
||||
"locations",
|
||||
"archived",
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -160,6 +160,7 @@ class StructureAssetView(AssetMasterView):
|
|||
"asset_name",
|
||||
"structure_type",
|
||||
"parents",
|
||||
"owners",
|
||||
"archived",
|
||||
]
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue