feat: add edit/sync support for asset parents

plus several changes to API calls, use iterate() instead of get()

also some changes to better share code for asset importers
This commit is contained in:
Lance Edgar 2026-03-06 19:52:20 -06:00
parent d46ba43d11
commit e61043b9d9
11 changed files with 437 additions and 293 deletions

View file

@ -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):

View file

@ -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
##############################

View file

@ -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"):

View file

@ -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,
}

View file

@ -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)

View file

@ -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):

View file

@ -0,0 +1,11 @@
<div tal:define="
name name|field.name;
oid oid|field.oid;
vmodel vmodel|'modelData.'+oid;"
tal:omit-tag="">
<assets-picker tal:attributes="name name;
v-model vmodel;
:assets js_values;" />
</div>

View file

@ -1,9 +1,116 @@
<%def name="make_wuttafarm_components()">
${self.make_assets_picker_component()}
${self.make_animal_type_picker_component()}
${self.make_plant_types_picker_component()}
</%def>
<%def name="make_assets_picker_component()">
<script type="text/x-template" id="assets-picker-template">
<div>
<input type="hidden" :name="name" :value="value" />
<div style="display: flex; gap: 0.5rem; align-items: center;">
<span>Add:</span>
<b-autocomplete v-model="addName"
ref="addName"
:data="addNameData"
field="name"
open-on-focus
keep-first
@select="addNameSelected"
clear-on-select
style="flex-grow: 1;">
<template #empty>No results found</template>
</b-autocomplete>
</div>
<${b}-table :data="assetData">
<${b}-table-column field="name" v-slot="props">
<span>{{ props.row.name }}</span>
</${b}-table-column>
<${b}-table-column v-slot="props">
<a href="#"
class="has-text-danger"
@click.prevent="removeAsset(props.row)">
<i class="fas fa-trash" /> &nbsp; Remove
</a>
</${b}-table-column>
</${b}-table>
</div>
</script>
<script>
const AssetsPicker = {
template: '#assets-picker-template',
props: {
name: String,
value: Array,
assets: Array,
},
data() {
return {
addName: '',
internalAssets: this.assets.map((asset) => {
return {uuid: asset[0], name: asset[1]}
}),
}
},
computed: {
assetData() {
const data = []
if (this.value) {
for (let asset of this.internalAssets) {
if (this.value.includes(asset.uuid)) {
data.push(asset)
}
}
}
return data
},
addNameData() {
if (!this.addName) {
return this.internalAssets
}
return this.internalAssets.filter((asset) => {
return asset.name.toLowerCase().indexOf(this.addName.toLowerCase()) >= 0
})
},
},
methods: {
addNameSelected(option) {
const value = Array.from(this.value || [])
if (!value.includes(option.uuid)) {
value.push(option.uuid)
this.$emit('input', value)
}
this.addName = null
},
removeAsset(asset) {
let value = Array.from(this.value)
const i = value.indexOf(asset.uuid)
value.splice(i, 1)
this.$emit('input', value)
},
},
}
Vue.component('assets-picker', AssetsPicker)
<% request.register_component('assets-picker', 'AssetsPicker') %>
</script>
</%def>
<%def name="make_animal_type_picker_component()">
<script type="text/x-template" id="animal-type-picker-template">
<div>
@ -108,7 +215,10 @@
createSave() {
this.createSaving = true
## TODO
% if not app.is_farmos_wrapper():
const url = "${url('animal_types.ajax_create')}"
% endif
const params = {name: this.createName}
this.wuttaPOST(url, params, response => {
this.internalAnimalTypes.push([response.data.uuid, response.data.name])
@ -229,7 +339,6 @@
return {uuid: pt[0], name: pt[1]}
}),
addShowDialog: false,
addName: '',
createShowDialog: false,
@ -243,7 +352,6 @@
if (this.value) {
for (let ptype of this.internalPlantTypes) {
// ptype = {uuid: ptype[0], name: ptype[1]}
if (this.value.includes(ptype.uuid)) {
data.push(ptype)
}
@ -295,7 +403,10 @@
createSave() {
this.createSaving = true
## TODO
% if not app.is_farmos_wrapper():
const url = "${url('plant_types.ajax_create')}"
% endif
const params = {name: this.createName}
this.wuttaPOST(url, params, response => {
this.internalPlantTypes.push(response.data)

View file

@ -32,7 +32,7 @@ from wuttaweb.db import Session
from wuttafarm.web.views import WuttaFarmMasterView
from wuttafarm.db.model import Asset, Log
from wuttafarm.web.forms.schema import AssetParentRefs, OwnerRefs, AssetRefs
from wuttafarm.web.forms.schema import OwnerRefs, AssetRefs
from wuttafarm.web.forms.widgets import ImageWidget
from wuttafarm.util import get_log_type_enum
from wuttafarm.web.util import get_farmos_client_for_user
@ -79,6 +79,7 @@ class AssetMasterView(WuttaFarmMasterView):
form_fields = [
"asset_name",
"parents",
"notes",
"asset_type",
"owners",
@ -279,11 +280,11 @@ class AssetMasterView(WuttaFarmMasterView):
f.set_default("groups", asset_handler.get_groups(asset))
# parents
if self.creating or self.editing:
f.remove("parents") # TODO: add support for this
else:
f.set_node("parents", AssetParentRefs(self.request))
f.set_default("parents", [p.uuid for p in asset.parents])
f.set_node("parents", AssetRefs(self.request, for_asset=asset))
f.set_required("parents", False)
if not self.creating:
# nb. must explicity declare value for non-standard field
f.set_default("parents", asset.parents)
# notes
f.set_widget("notes", "notes")
@ -311,11 +312,29 @@ class AssetMasterView(WuttaFarmMasterView):
f.set_default("image", asset.image_url)
def objectify(self, form):
model = self.app.model
session = self.Session()
asset = super().objectify(form)
data = form.validated
if self.creating:
asset.asset_type = self.get_asset_type()
current = [p.uuid for p in asset.parents]
desired = data["parents"] or []
for uuid in desired:
if uuid not in current:
parent = session.get(model.Asset, uuid)
assert parent
asset.parents.append(parent)
for uuid in current:
if uuid not in desired:
parent = session.get(model.Asset, uuid)
assert parent
asset.parents.remove(parent)
return asset
def get_asset_type(self):

View file

@ -47,15 +47,11 @@ class GroupView(AssetMasterView):
"archived",
]
form_fields = [
"asset_name",
"notes",
"asset_type",
"produces_eggs",
"archived",
"drupal_id",
"farmos_uuid",
]
def configure_form(self, f):
super().configure_form(f)
# produces_eggs
f.fields.insert_after("asset_type", "produces_eggs")
def defaults(config, **kwargs):

View file

@ -220,21 +220,6 @@ class PlantAssetView(AssetMasterView):
"archived",
]
form_fields = [
"asset_name",
"plant_types",
"season",
"notes",
"asset_type",
"archived",
"drupal_id",
"farmos_uuid",
"thumbnail_url",
"image_url",
"thumbnail",
"image",
]
def configure_grid(self, grid):
g = grid
super().configure_grid(g)
@ -262,14 +247,15 @@ class PlantAssetView(AssetMasterView):
plant = f.model_instance
# plant_types
f.fields.insert_after("asset_name", "plant_types")
f.set_node("plant_types", PlantTypeRefs(self.request))
if not self.creating:
# nb. must explcitly declare value for non-standard field
f.set_default("plant_types", [pt.uuid for pt in plant.plant_types])
# season
if self.creating or self.editing:
f.remove("season") # TODO: add support for this
if not (self.creating or self.editing): # TODO
f.fields.insert_after("plant_types", "season")
def objectify(self, form):
model = self.app.model