diff --git a/src/wuttafarm/db/model/asset_plant.py b/src/wuttafarm/db/model/asset_plant.py index 5f10e7c..62f7e9b 100644 --- a/src/wuttafarm/db/model/asset_plant.py +++ b/src/wuttafarm/db/model/asset_plant.py @@ -25,6 +25,7 @@ Model definition for Plant Assets import sqlalchemy as sa from sqlalchemy import orm +from sqlalchemy.ext.associationproxy import association_proxy from wuttjamaican.db import model @@ -80,6 +81,12 @@ class PlantType(model.Base): """, ) + _plant_assets = orm.relationship( + "PlantAssetPlantType", + cascade_backrefs=False, + back_populates="plant_type", + ) + def __str__(self): return self.name or "" @@ -99,9 +106,17 @@ class PlantAsset(AssetMixin, model.Base): _plant_types = orm.relationship( "PlantAssetPlantType", + cascade="all, delete-orphan", + cascade_backrefs=False, back_populates="plant_asset", ) + plant_types = association_proxy( + "_plant_types", + "plant_type", + creator=lambda pt: PlantAssetPlantType(plant_type=pt), + ) + add_asset_proxies(PlantAsset) @@ -129,4 +144,5 @@ class PlantAssetPlantType(model.Base): doc=""" Reference to the plant type. """, + back_populates="_plant_assets", ) diff --git a/src/wuttafarm/farmos/importing/model.py b/src/wuttafarm/farmos/importing/model.py index 337649c..04d80c1 100644 --- a/src/wuttafarm/farmos/importing/model.py +++ b/src/wuttafarm/farmos/importing/model.py @@ -347,6 +347,12 @@ class LandAssetImporter(ToFarmOSAsset): return payload +class PlantTypeImporter(ToFarmOSTaxonomy): + + model_title = "PlantType" + farmos_taxonomy_type = "plant_type" + + class PlantAssetImporter(ToFarmOSAsset): model_title = "PlantAsset" diff --git a/src/wuttafarm/farmos/importing/wuttafarm.py b/src/wuttafarm/farmos/importing/wuttafarm.py index a39fe97..bb5350d 100644 --- a/src/wuttafarm/farmos/importing/wuttafarm.py +++ b/src/wuttafarm/farmos/importing/wuttafarm.py @@ -99,6 +99,7 @@ class FromWuttaFarmToFarmOS(FromWuttaFarmHandler, ToFarmOSHandler): importers["AnimalType"] = AnimalTypeImporter importers["AnimalAsset"] = AnimalAssetImporter importers["GroupAsset"] = GroupAssetImporter + importers["PlantType"] = PlantTypeImporter importers["PlantAsset"] = PlantAssetImporter importers["Unit"] = UnitImporter importers["ActivityLog"] = ActivityLogImporter @@ -264,6 +265,28 @@ class LandAssetImporter(FromWuttaFarm, farmos_importing.model.LandAssetImporter) } +class PlantTypeImporter(FromWuttaFarm, farmos_importing.model.PlantTypeImporter): + """ + WuttaFarm → farmOS API exporter for Plant Types + """ + + source_model_class = model.PlantType + + supported_fields = [ + "uuid", + "name", + ] + + drupal_internal_id_field = "drupal_internal__tid" + + def normalize_source_object(self, plant_type): + return { + "uuid": plant_type.farmos_uuid or self.app.make_true_uuid(), + "name": plant_type.name, + "_src_object": plant_type, + } + + class PlantAssetImporter(FromWuttaFarm, farmos_importing.model.PlantAssetImporter): """ WuttaFarm → farmOS API exporter for Plant Assets diff --git a/src/wuttafarm/importing/farmos.py b/src/wuttafarm/importing/farmos.py index a1e9631..9e922da 100644 --- a/src/wuttafarm/importing/farmos.py +++ b/src/wuttafarm/importing/farmos.py @@ -623,7 +623,7 @@ class PlantAssetImporter(AssetImporterBase): def normalize_source_object(self, plant): """ """ - plant_types = None + plant_types = [] if relationships := plant.get("relationships"): if plant_type := relationships.get("plant_type"): @@ -640,7 +640,7 @@ class PlantAssetImporter(AssetImporterBase): data.update( { "asset_type": "plant", - "plant_types": plant_types, + "plant_types": set(plant_types), } ) return data @@ -649,7 +649,7 @@ class PlantAssetImporter(AssetImporterBase): data = super().normalize_target_object(plant) if "plant_types" in self.fields: - data["plant_types"] = [t.plant_type_uuid for t in plant._plant_types] + data["plant_types"] = set([pt.uuid for pt in plant.plant_types]) return data diff --git a/src/wuttafarm/web/forms/schema.py b/src/wuttafarm/web/forms/schema.py index c6095ff..548ee81 100644 --- a/src/wuttafarm/web/forms/schema.py +++ b/src/wuttafarm/web/forms/schema.py @@ -27,6 +27,7 @@ import json import colander +from wuttaweb.db import Session from wuttaweb.forms.schema import ObjectRef, WuttaSet from wuttaweb.forms.widgets import NotesWidget @@ -242,13 +243,23 @@ class PlantTypeRefs(WuttaSet): def serialize(self, node, appstruct): if not appstruct: - appstruct = [] - uuids = [u.hex for u in appstruct] - return json.dumps(uuids) + return colander.null + + return [uuid.hex for uuid in appstruct] def widget_maker(self, **kwargs): from wuttafarm.web.forms.widgets import PlantTypeRefsWidget + model = self.app.model + session = Session() + + if "values" not in kwargs: + plant_types = ( + session.query(model.PlantType).order_by(model.PlantType.name).all() + ) + values = [(pt.uuid.hex, str(pt)) for pt in plant_types] + kwargs["values"] = values + return PlantTypeRefsWidget(self.request, **kwargs) diff --git a/src/wuttafarm/web/forms/widgets.py b/src/wuttafarm/web/forms/widgets.py index 7f5808f..ae9aa10 100644 --- a/src/wuttafarm/web/forms/widgets.py +++ b/src/wuttafarm/web/forms/widgets.py @@ -26,7 +26,7 @@ Custom form widgets for WuttaFarm import json import colander -from deform.widget import Widget, SelectWidget +from deform.widget import Widget, SelectWidget, sequence_types, _normalize_choices from webhelpers2.html import HTML, tags from wuttaweb.forms.widgets import WuttaCheckboxChoiceWidget, ObjectRefWidget @@ -258,22 +258,40 @@ class FarmOSPlantTypesWidget(Widget): return super().serialize(field, cstruct, **kw) -class PlantTypeRefsWidget(WuttaCheckboxChoiceWidget): +class PlantTypeRefsWidget(Widget): """ Widget for Plant Types field (on a Plant Asset). """ + template = "planttyperefs" + 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 session = Session() - readonly = kw.get("readonly", self.readonly) - if readonly: - plant_types = [] - for uuid in json.loads(cstruct): - plant_type = session.get(model.PlantType, uuid) - plant_types.append( + if cstruct in (colander.null, None): + cstruct = () + + if readonly := kw.get("readonly", self.readonly): + items = [] + + plant_types = ( + session.query(model.PlantType) + .filter(model.PlantType.uuid.in_(cstruct)) + .order_by(model.PlantType.name) + .all() + ) + + for plant_type in plant_types: + items.append( HTML.tag( "li", c=tags.link_to( @@ -284,9 +302,33 @@ class PlantTypeRefsWidget(WuttaCheckboxChoiceWidget): ), ) ) - return HTML.tag("ul", c=plant_types) - return super().serialize(field, cstruct, **kw) + return HTML.tag("ul", c=items) + + 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"]) + + if self.request.has_perm("plant_types.create"): + values["can_create"] = True + + return values + + def deserialize(self, field, pstruct): + if not pstruct: + return colander.null + + return set(pstruct.split(",")) class StructureWidget(Widget): diff --git a/src/wuttafarm/web/templates/deform/planttyperefs.pt b/src/wuttafarm/web/templates/deform/planttyperefs.pt new file mode 100644 index 0000000..83cb095 --- /dev/null +++ b/src/wuttafarm/web/templates/deform/planttyperefs.pt @@ -0,0 +1,13 @@ +