diff --git a/src/wuttafarm/farmos/importing/model.py b/src/wuttafarm/farmos/importing/model.py index 54e67ac..ac4dc86 100644 --- a/src/wuttafarm/farmos/importing/model.py +++ b/src/wuttafarm/farmos/importing/model.py @@ -74,10 +74,11 @@ class ToFarmOSTaxonomy(ToFarmOS): ] def get_target_objects(self, **kwargs): - result = self.farmos_client.resource.get( - "taxonomy_term", self.farmos_taxonomy_type + return list( + self.farmos_client.resource.iterate( + "taxonomy_term", self.farmos_taxonomy_type + ) ) - return result["data"] def get_target_object(self, key): @@ -127,9 +128,9 @@ class ToFarmOSTaxonomy(ToFarmOS): normal["_new_object"] = result["data"] return normal - def update_target_object(self, asset, source_data, target_data=None): + def update_target_object(self, term, source_data, target_data=None): if self.dry_run: - return asset + return term payload = self.get_term_payload(source_data) payload["id"] = str(source_data["uuid"]) @@ -146,9 +147,12 @@ class ToFarmOSAsset(ToFarmOS): farmos_asset_type = None + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.normal = self.app.get_normalizer(self.farmos_client) + def get_target_objects(self, **kwargs): - assets = self.farmos_client.asset.get(self.farmos_asset_type) - return assets["data"] + return list(self.farmos_client.asset.iterate(self.farmos_asset_type)) def get_target_object(self, key): @@ -191,18 +195,17 @@ class ToFarmOSAsset(ToFarmOS): return self.normalize_target_object(result["data"]) def normalize_target_object(self, asset): - - if notes := asset["attributes"]["notes"]: - notes = notes["value"] - + normal = self.normal.normalize_farmos_asset(asset) return { - "uuid": UUID(asset["id"]), - "asset_name": asset["attributes"]["name"], - "is_location": asset["attributes"]["is_location"], - "is_fixed": asset["attributes"]["is_fixed"], - "produces_eggs": asset["attributes"].get("produces_eggs"), - "notes": notes, - "archived": asset["attributes"]["archived"], + "uuid": UUID(normal["uuid"]), + "asset_name": normal["asset_name"], + "is_location": normal["is_location"], + "is_fixed": normal["is_fixed"], + # nb. this is only used for certain asset types + "produces_eggs": normal["produces_eggs"], + "parents": [(p["asset_type"], UUID(p["uuid"])) for p in normal["parents"]], + "notes": normal["notes"], + "archived": normal["archived"], } def get_asset_payload(self, source_data): @@ -221,8 +224,18 @@ class ToFarmOSAsset(ToFarmOS): if "archived" in self.fields: attrs["archived"] = source_data["archived"] - payload = {"attributes": attrs} + rels = {} + if "parents" in self.fields: + rels["parent"] = {"data": []} + for asset_type, uuid in source_data["parents"]: + rels["parent"]["data"].append( + { + "id": str(uuid), + "type": f"asset--{asset_type}", + } + ) + payload = {"attributes": attrs, "relationships": rels} return payload @@ -607,8 +620,7 @@ class ToFarmOSLog(ToFarmOS): self.normal = self.app.get_normalizer(self.farmos_client) def get_target_objects(self, **kwargs): - result = self.farmos_client.log.get(self.farmos_log_type) - return result["data"] + return list(self.farmos_client.log.iterate(self.farmos_log_type)) def get_target_object(self, key): diff --git a/src/wuttafarm/farmos/importing/wuttafarm.py b/src/wuttafarm/farmos/importing/wuttafarm.py index 8cf0e92..f4f4948 100644 --- a/src/wuttafarm/farmos/importing/wuttafarm.py +++ b/src/wuttafarm/farmos/importing/wuttafarm.py @@ -134,42 +134,68 @@ class FromWuttaFarm(FromWutta): return obj -class AnimalAssetImporter(FromWuttaFarm, farmos_importing.model.AnimalAssetImporter): +class FromWuttaFarmAsset(FromWuttaFarm): + """ + Base class for WuttaFarm → farmOS API asset exporters + """ + + supported_fields = [ + "uuid", + "asset_name", + "is_location", + "is_fixed", + "parents", + "notes", + "archived", + ] + + def normalize_source_object(self, asset): + return { + "uuid": asset.farmos_uuid or self.app.make_true_uuid(), + "asset_name": asset.asset_name, + "is_location": asset.is_location, + "is_fixed": asset.is_fixed, + "parents": [(p.asset_type, p.farmos_uuid) for p in asset.parents], + "notes": asset.notes, + "archived": asset.archived, + "_src_object": asset, + } + + +class AnimalAssetImporter( + FromWuttaFarmAsset, farmos_importing.model.AnimalAssetImporter +): """ WuttaFarm → farmOS API exporter for Animal Assets """ source_model_class = model.AnimalAsset - supported_fields = [ - "uuid", - "asset_name", - "animal_type_uuid", - "sex", - "is_sterile", - "produces_eggs", - "birthdate", - "is_location", - "is_fixed", - "notes", - "archived", - ] + def get_supported_fields(self): + fields = list(super().get_supported_fields()) + fields.extend( + [ + "animal_type_uuid", + "sex", + "is_sterile", + "produces_eggs", + "birthdate", + ] + ) + return fields def normalize_source_object(self, animal): - return { - "uuid": animal.farmos_uuid or self.app.make_true_uuid(), - "asset_name": animal.asset_name, - "animal_type_uuid": animal.animal_type.farmos_uuid, - "sex": animal.sex, - "is_sterile": animal.is_sterile, - "produces_eggs": animal.produces_eggs, - "birthdate": animal.birthdate, - "is_location": animal.is_location, - "is_fixed": animal.is_fixed, - "notes": animal.notes, - "archived": animal.archived, - "_src_object": animal, - } + data = super().normalize_source_object(animal) + data.update( + { + "animal_type_uuid": animal.animal_type.farmos_uuid, + "sex": animal.sex, + "is_sterile": animal.is_sterile, + "produces_eggs": animal.produces_eggs, + "birthdate": animal.birthdate, + } + ) + return data class AnimalTypeImporter(FromWuttaFarm, farmos_importing.model.AnimalTypeImporter): @@ -216,60 +242,56 @@ class UnitImporter(FromWuttaFarm, farmos_importing.model.UnitImporter): } -class GroupAssetImporter(FromWuttaFarm, farmos_importing.model.GroupAssetImporter): +class GroupAssetImporter(FromWuttaFarmAsset, farmos_importing.model.GroupAssetImporter): """ WuttaFarm → farmOS API exporter for Group Assets """ source_model_class = model.GroupAsset - supported_fields = [ - "uuid", - "asset_name", - "produces_eggs", - "notes", - "archived", - ] + def get_supported_fields(self): + fields = list(super().get_supported_fields()) + fields.extend( + [ + "produces_eggs", + ] + ) + return fields def normalize_source_object(self, group): - return { - "uuid": group.farmos_uuid or self.app.make_true_uuid(), - "asset_name": group.asset_name, - "produces_eggs": group.produces_eggs, - "notes": group.notes, - "archived": group.archived, - "_src_object": group, - } + data = super().normalize_source_object(group) + data.update( + { + "produces_eggs": group.produces_eggs, + } + ) + return data -class LandAssetImporter(FromWuttaFarm, farmos_importing.model.LandAssetImporter): +class LandAssetImporter(FromWuttaFarmAsset, farmos_importing.model.LandAssetImporter): """ WuttaFarm → farmOS API exporter for Land Assets """ source_model_class = model.LandAsset - supported_fields = [ - "uuid", - "asset_name", - "land_type_id", - "is_location", - "is_fixed", - "notes", - "archived", - ] + def get_supported_fields(self): + fields = list(super().get_supported_fields()) + fields.extend( + [ + "land_type_id", + ] + ) + return fields def normalize_source_object(self, land): - return { - "uuid": land.farmos_uuid or self.app.make_true_uuid(), - "asset_name": land.asset_name, - "land_type_id": land.land_type.drupal_id, - "is_location": land.is_location, - "is_fixed": land.is_fixed, - "notes": land.notes, - "archived": land.archived, - "_src_object": land, - } + data = super().normalize_source_object(land) + data.update( + { + "land_type_id": land.land_type.drupal_id, + } + ) + return data class PlantTypeImporter(FromWuttaFarm, farmos_importing.model.PlantTypeImporter): @@ -294,34 +316,36 @@ class PlantTypeImporter(FromWuttaFarm, farmos_importing.model.PlantTypeImporter) } -class PlantAssetImporter(FromWuttaFarm, farmos_importing.model.PlantAssetImporter): +class PlantAssetImporter(FromWuttaFarmAsset, farmos_importing.model.PlantAssetImporter): """ WuttaFarm → farmOS API exporter for Plant Assets """ source_model_class = model.PlantAsset - supported_fields = [ - "uuid", - "asset_name", - "plant_type_uuids", - "notes", - "archived", - ] + def get_supported_fields(self): + fields = list(super().get_supported_fields()) + fields.extend( + [ + "plant_type_uuids", + ] + ) + return fields def normalize_source_object(self, plant): - return { - "uuid": plant.farmos_uuid or self.app.make_true_uuid(), - "asset_name": plant.asset_name, - "plant_type_uuids": [t.plant_type.farmos_uuid for t in plant._plant_types], - "notes": plant.notes, - "archived": plant.archived, - "_src_object": plant, - } + data = super().normalize_source_object(plant) + data.update( + { + "plant_type_uuids": [ + t.plant_type.farmos_uuid for t in plant._plant_types + ], + } + ) + return data class StructureAssetImporter( - FromWuttaFarm, farmos_importing.model.StructureAssetImporter + FromWuttaFarmAsset, farmos_importing.model.StructureAssetImporter ): """ WuttaFarm → farmOS API exporter for Structure Assets @@ -329,27 +353,23 @@ class StructureAssetImporter( source_model_class = model.StructureAsset - supported_fields = [ - "uuid", - "asset_name", - "structure_type_id", - "is_location", - "is_fixed", - "notes", - "archived", - ] + def get_supported_fields(self): + fields = list(super().get_supported_fields()) + fields.extend( + [ + "structure_type_id", + ] + ) + return fields def normalize_source_object(self, structure): - return { - "uuid": structure.farmos_uuid or self.app.make_true_uuid(), - "asset_name": structure.asset_name, - "structure_type_id": structure.structure_type.drupal_id, - "is_location": structure.is_location, - "is_fixed": structure.is_fixed, - "notes": structure.notes, - "archived": structure.archived, - "_src_object": structure, - } + data = super().normalize_source_object(structure) + data.update( + { + "structure_type_id": structure.structure_type.drupal_id, + } + ) + return data ############################## diff --git a/src/wuttafarm/importing/farmos.py b/src/wuttafarm/importing/farmos.py index 6b21090..ea1fd4b 100644 --- a/src/wuttafarm/importing/farmos.py +++ b/src/wuttafarm/importing/farmos.py @@ -330,21 +330,18 @@ class AnimalAssetImporter(AssetImporterBase): model_class = model.AnimalAsset - supported_fields = [ - "farmos_uuid", - "drupal_id", - "asset_type", - "asset_name", - "animal_type_uuid", - "sex", - "is_sterile", - "produces_eggs", - "birthdate", - "notes", - "archived", - "image_url", - "thumbnail_url", - ] + def get_supported_fields(self): + fields = list(super().get_supported_fields()) + fields.extend( + [ + "animal_type_uuid", + "sex", + "is_sterile", + "produces_eggs", + "birthdate", + ] + ) + return fields def setup(self): super().setup() @@ -415,8 +412,7 @@ class AnimalTypeImporter(FromFarmOS, ToWutta): def get_source_objects(self): """ """ - animal_types = self.farmos_client.resource.get("taxonomy_term", "animal_type") - return animal_types["data"] + return list(self.farmos_client.resource.iterate("taxonomy_term", "animal_type")) def normalize_source_object(self, animal_type): """ """ @@ -444,8 +440,7 @@ class AssetTypeImporter(FromFarmOS, ToWutta): def get_source_objects(self): """ """ - asset_types = self.farmos_client.resource.get("asset_type") - return asset_types["data"] + return list(self.farmos_client.resource.iterate("asset_type")) def normalize_source_object(self, asset_type): """ """ @@ -464,20 +459,14 @@ class GroupAssetImporter(AssetImporterBase): model_class = model.GroupAsset - supported_fields = [ - "farmos_uuid", - "drupal_id", - "asset_type", - "asset_name", - "is_location", - "is_fixed", - "produces_eggs", - "notes", - "archived", - "image_url", - "thumbnail_url", - "parents", - ] + def get_supported_fields(self): + fields = list(super().get_supported_fields()) + fields.extend( + [ + "produces_eggs", + ] + ) + return fields def normalize_source_object(self, group): """ """ @@ -497,18 +486,14 @@ class LandAssetImporter(AssetImporterBase): model_class = model.LandAsset - supported_fields = [ - "farmos_uuid", - "drupal_id", - "asset_type", - "asset_name", - "land_type_uuid", - "is_location", - "is_fixed", - "notes", - "archived", - "parents", - ] + def get_supported_fields(self): + fields = list(super().get_supported_fields()) + fields.extend( + [ + "land_type_uuid", + ] + ) + return fields def setup(self): """ """ @@ -553,8 +538,7 @@ class LandTypeImporter(FromFarmOS, ToWutta): def get_source_objects(self): """ """ - land_types = self.farmos_client.resource.get("land_type") - return land_types["data"] + return list(self.farmos_client.resource.iterate("land_type")) def normalize_source_object(self, land_type): """ """ @@ -581,8 +565,7 @@ class PlantTypeImporter(FromFarmOS, ToWutta): def get_source_objects(self): """ """ - result = self.farmos_client.resource.get("taxonomy_term", "plant_type") - return result["data"] + return list(self.farmos_client.resource.iterate("taxonomy_term", "plant_type")) def normalize_source_object(self, plant_type): """ """ @@ -601,17 +584,14 @@ class PlantAssetImporter(AssetImporterBase): model_class = model.PlantAsset - supported_fields = [ - "farmos_uuid", - "drupal_id", - "asset_type", - "asset_name", - "plant_types", - "notes", - "archived", - "image_url", - "thumbnail_url", - ] + def get_supported_fields(self): + fields = list(super().get_supported_fields()) + fields.extend( + [ + "plant_types", + ] + ) + return fields def setup(self): super().setup() @@ -624,6 +604,8 @@ class PlantAssetImporter(AssetImporterBase): def normalize_source_object(self, plant): """ """ + data = super().normalize_source_object(plant) + plant_types = [] if relationships := plant.get("relationships"): @@ -637,7 +619,6 @@ class PlantAssetImporter(AssetImporterBase): else: log.warning("plant type not found: %s", plant_type["id"]) - data = super().normalize_source_object(plant) data.update( { "plant_types": set(plant_types), @@ -693,20 +674,14 @@ class StructureAssetImporter(AssetImporterBase): model_class = model.StructureAsset - supported_fields = [ - "farmos_uuid", - "drupal_id", - "asset_type", - "asset_name", - "structure_type_uuid", - "is_location", - "is_fixed", - "notes", - "archived", - "image_url", - "thumbnail_url", - "parents", - ] + def get_supported_fields(self): + fields = list(super().get_supported_fields()) + fields.extend( + [ + "structure_type_uuid", + ] + ) + return fields def setup(self): super().setup() @@ -752,8 +727,7 @@ class StructureTypeImporter(FromFarmOS, ToWutta): def get_source_objects(self): """ """ - structure_types = self.farmos_client.resource.get("structure_type") - return structure_types["data"] + return list(self.farmos_client.resource.iterate("structure_type")) def normalize_source_object(self, structure_type): """ """ @@ -791,8 +765,7 @@ class UserImporter(FromFarmOS, ToWutta): def get_source_objects(self): """ """ - users = self.farmos_client.resource.get("user") - return users["data"] + return list(self.farmos_client.resource.iterate("user")) def normalize_source_object(self, user): """ """ @@ -869,8 +842,7 @@ class UnitImporter(FromFarmOS, ToWutta): def get_source_objects(self): """ """ - result = self.farmos_client.resource.get("taxonomy_term", "unit") - return result["data"] + return list(self.farmos_client.resource.iterate("taxonomy_term", "unit")) def normalize_source_object(self, unit): """ """ @@ -898,8 +870,7 @@ class QuantityTypeImporter(FromFarmOS, ToWutta): def get_source_objects(self): """ """ - result = self.farmos_client.resource.get("quantity_type") - return result["data"] + return list(self.farmos_client.resource.iterate("quantity_type")) def normalize_source_object(self, quantity_type): """ """ @@ -927,8 +898,7 @@ class LogTypeImporter(FromFarmOS, ToWutta): def get_source_objects(self): """ """ - log_types = self.farmos_client.resource.get("log_type") - return log_types["data"] + return list(self.farmos_client.resource.iterate("log_type")) def normalize_source_object(self, log_type): """ """ @@ -1271,8 +1241,7 @@ class QuantityImporterBase(FromFarmOS, ToWutta): def get_source_objects(self): """ """ quantity_type = self.get_farmos_quantity_type() - result = self.farmos_client.resource.get("quantity", quantity_type) - return result["data"] + return list(self.farmos_client.resource.iterate("quantity", quantity_type)) def get_quantity_type_by_farmos_uuid(self, uuid): if hasattr(self, "quantity_types_by_farmos_uuid"): diff --git a/src/wuttafarm/normal.py b/src/wuttafarm/normal.py index 4fc8796..2e38f49 100644 --- a/src/wuttafarm/normal.py +++ b/src/wuttafarm/normal.py @@ -90,10 +90,29 @@ class Normalizer(GenericHandler): if notes := asset["attributes"]["notes"]: notes = notes["value"] + parent_objects = [] + parent_uuids = [] owner_objects = [] owner_uuids = [] if relationships := asset.get("relationships"): + if parents := relationships.get("parent"): + for parent in parents["data"]: + parent_uuid = parent["id"] + parent_uuids.append(parent_uuid) + parent_object = { + "uuid": parent_uuid, + "type": parent["type"], + "asset_type": parent["type"].split("--")[1], + } + if parent := included.get(parent_uuid): + parent_object.update( + { + "name": parent["attributes"]["name"], + } + ) + parent_objects.append(parent_object) + if owners := relationships.get("owner"): for user in owners["data"]: user_uuid = user["id"] @@ -114,6 +133,10 @@ class Normalizer(GenericHandler): "is_fixed": asset["attributes"]["is_fixed"], "archived": asset["attributes"]["archived"], "notes": notes, + # nb. this is only used for certain asset types + "produces_eggs": asset["attributes"].get("produces_eggs"), + "parents": parent_objects, + "parent_uuids": parent_uuids, "owners": owner_objects, "owner_uuids": owner_uuids, } diff --git a/src/wuttafarm/web/forms/schema.py b/src/wuttafarm/web/forms/schema.py index 6bf434e..bad5670 100644 --- a/src/wuttafarm/web/forms/schema.py +++ b/src/wuttafarm/web/forms/schema.py @@ -372,37 +372,35 @@ class UsersType(colander.SchemaType): return UsersWidget(self.request, **kwargs) -class AssetParentRefs(WuttaSet): - """ - Schema type for Parents field which references assets. - """ - - def serialize(self, node, appstruct): - if not appstruct: - appstruct = [] - uuids = [u.hex for u in appstruct] - return json.dumps(uuids) - - def widget_maker(self, **kwargs): - from wuttafarm.web.forms.widgets import AssetParentRefsWidget - - return AssetParentRefsWidget(self.request, **kwargs) - - class AssetRefs(WuttaSet): """ Schema type for Assets field (on a Log record) """ + def __init__(self, request, for_asset=None, **kwargs): + super().__init__(request, **kwargs) + self.for_asset = for_asset + def serialize(self, node, appstruct): if not appstruct: return colander.null - return {asset.uuid for asset in appstruct} + return {asset.uuid.hex for asset in appstruct} def widget_maker(self, **kwargs): from wuttafarm.web.forms.widgets import AssetRefsWidget + model = self.app.model + session = Session() + + if "values" not in kwargs: + query = session.query(model.Asset) + if self.for_asset: + query = query.filter(model.Asset.uuid != self.for_asset.uuid) + query = query.order_by(model.Asset.asset_name) + values = [(asset.uuid.hex, str(asset)) for asset in query] + kwargs["values"] = values + return AssetRefsWidget(self.request, **kwargs) diff --git a/src/wuttafarm/web/forms/widgets.py b/src/wuttafarm/web/forms/widgets.py index 0a14638..d74a436 100644 --- a/src/wuttafarm/web/forms/widgets.py +++ b/src/wuttafarm/web/forms/widgets.py @@ -393,42 +393,20 @@ class UsersWidget(Widget): ############################## -class AssetParentRefsWidget(WuttaCheckboxChoiceWidget): - """ - Widget for Parents field which references assets. - """ - - def serialize(self, field, cstruct, **kw): - """ """ - model = self.app.model - session = Session() - - readonly = kw.get("readonly", self.readonly) - if readonly: - parents = [] - for uuid in json.loads(cstruct): - parent = session.get(model.Asset, uuid) - parents.append( - HTML.tag( - "li", - c=tags.link_to( - str(parent), - self.request.route_url( - f"{parent.asset_type}_assets.view", uuid=parent.uuid - ), - ), - ) - ) - return HTML.tag("ul", c=parents) - - return super().serialize(field, cstruct, **kw) - - -class AssetRefsWidget(WuttaCheckboxChoiceWidget): +class AssetRefsWidget(Widget): """ Widget for Assets field (of various kinds). """ + template = "assetrefs" + values = () + + def __init__(self, request, *args, **kwargs): + super().__init__(*args, **kwargs) + self.request = request + self.config = self.request.wutta_config + self.app = self.config.get_app() + def serialize(self, field, cstruct, **kw): """ """ model = self.app.model @@ -452,7 +430,28 @@ class AssetRefsWidget(WuttaCheckboxChoiceWidget): ) return HTML.tag("ul", c=assets) - return super().serialize(field, cstruct, **kw) + values = kw.get("values", self.values) + if not isinstance(values, sequence_types): + raise TypeError("Values must be a sequence type (list, tuple, or range).") + + kw["values"] = _normalize_choices(values) + tmpl_values = self.get_template_values(field, cstruct, kw) + return field.renderer(self.template, **tmpl_values) + + def get_template_values(self, field, cstruct, kw): + """ """ + values = super().get_template_values(field, cstruct, kw) + + values["js_values"] = json.dumps(values["values"]) + + return values + + def deserialize(self, field, pstruct): + """ """ + if not pstruct: + return colander.null + + return set(pstruct.split(",")) class LogQuantityRefsWidget(WuttaCheckboxChoiceWidget): diff --git a/src/wuttafarm/web/templates/deform/assetrefs.pt b/src/wuttafarm/web/templates/deform/assetrefs.pt new file mode 100644 index 0000000..b2e9660 --- /dev/null +++ b/src/wuttafarm/web/templates/deform/assetrefs.pt @@ -0,0 +1,11 @@ +