feat: add support for edit, import/export of plant type data
esp. plant types for a plant asset
This commit is contained in:
parent
bdda586ccd
commit
c353d5bcef
9 changed files with 416 additions and 24 deletions
|
|
@ -25,6 +25,7 @@ Model definition for Plant Assets
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from sqlalchemy import orm
|
from sqlalchemy import orm
|
||||||
|
from sqlalchemy.ext.associationproxy import association_proxy
|
||||||
|
|
||||||
from wuttjamaican.db import model
|
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):
|
def __str__(self):
|
||||||
return self.name or ""
|
return self.name or ""
|
||||||
|
|
||||||
|
|
@ -99,9 +106,17 @@ class PlantAsset(AssetMixin, model.Base):
|
||||||
|
|
||||||
_plant_types = orm.relationship(
|
_plant_types = orm.relationship(
|
||||||
"PlantAssetPlantType",
|
"PlantAssetPlantType",
|
||||||
|
cascade="all, delete-orphan",
|
||||||
|
cascade_backrefs=False,
|
||||||
back_populates="plant_asset",
|
back_populates="plant_asset",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
plant_types = association_proxy(
|
||||||
|
"_plant_types",
|
||||||
|
"plant_type",
|
||||||
|
creator=lambda pt: PlantAssetPlantType(plant_type=pt),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
add_asset_proxies(PlantAsset)
|
add_asset_proxies(PlantAsset)
|
||||||
|
|
||||||
|
|
@ -129,4 +144,5 @@ class PlantAssetPlantType(model.Base):
|
||||||
doc="""
|
doc="""
|
||||||
Reference to the plant type.
|
Reference to the plant type.
|
||||||
""",
|
""",
|
||||||
|
back_populates="_plant_assets",
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -347,6 +347,12 @@ class LandAssetImporter(ToFarmOSAsset):
|
||||||
return payload
|
return payload
|
||||||
|
|
||||||
|
|
||||||
|
class PlantTypeImporter(ToFarmOSTaxonomy):
|
||||||
|
|
||||||
|
model_title = "PlantType"
|
||||||
|
farmos_taxonomy_type = "plant_type"
|
||||||
|
|
||||||
|
|
||||||
class PlantAssetImporter(ToFarmOSAsset):
|
class PlantAssetImporter(ToFarmOSAsset):
|
||||||
|
|
||||||
model_title = "PlantAsset"
|
model_title = "PlantAsset"
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,7 @@ class FromWuttaFarmToFarmOS(FromWuttaFarmHandler, ToFarmOSHandler):
|
||||||
importers["AnimalType"] = AnimalTypeImporter
|
importers["AnimalType"] = AnimalTypeImporter
|
||||||
importers["AnimalAsset"] = AnimalAssetImporter
|
importers["AnimalAsset"] = AnimalAssetImporter
|
||||||
importers["GroupAsset"] = GroupAssetImporter
|
importers["GroupAsset"] = GroupAssetImporter
|
||||||
|
importers["PlantType"] = PlantTypeImporter
|
||||||
importers["PlantAsset"] = PlantAssetImporter
|
importers["PlantAsset"] = PlantAssetImporter
|
||||||
importers["Unit"] = UnitImporter
|
importers["Unit"] = UnitImporter
|
||||||
importers["ActivityLog"] = ActivityLogImporter
|
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):
|
class PlantAssetImporter(FromWuttaFarm, farmos_importing.model.PlantAssetImporter):
|
||||||
"""
|
"""
|
||||||
WuttaFarm → farmOS API exporter for Plant Assets
|
WuttaFarm → farmOS API exporter for Plant Assets
|
||||||
|
|
|
||||||
|
|
@ -623,7 +623,7 @@ class PlantAssetImporter(AssetImporterBase):
|
||||||
|
|
||||||
def normalize_source_object(self, plant):
|
def normalize_source_object(self, plant):
|
||||||
""" """
|
""" """
|
||||||
plant_types = None
|
plant_types = []
|
||||||
if relationships := plant.get("relationships"):
|
if relationships := plant.get("relationships"):
|
||||||
|
|
||||||
if plant_type := relationships.get("plant_type"):
|
if plant_type := relationships.get("plant_type"):
|
||||||
|
|
@ -640,7 +640,7 @@ class PlantAssetImporter(AssetImporterBase):
|
||||||
data.update(
|
data.update(
|
||||||
{
|
{
|
||||||
"asset_type": "plant",
|
"asset_type": "plant",
|
||||||
"plant_types": plant_types,
|
"plant_types": set(plant_types),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return data
|
return data
|
||||||
|
|
@ -649,7 +649,7 @@ class PlantAssetImporter(AssetImporterBase):
|
||||||
data = super().normalize_target_object(plant)
|
data = super().normalize_target_object(plant)
|
||||||
|
|
||||||
if "plant_types" in self.fields:
|
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
|
return data
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import json
|
||||||
|
|
||||||
import colander
|
import colander
|
||||||
|
|
||||||
|
from wuttaweb.db import Session
|
||||||
from wuttaweb.forms.schema import ObjectRef, WuttaSet
|
from wuttaweb.forms.schema import ObjectRef, WuttaSet
|
||||||
from wuttaweb.forms.widgets import NotesWidget
|
from wuttaweb.forms.widgets import NotesWidget
|
||||||
|
|
||||||
|
|
@ -242,13 +243,23 @@ class PlantTypeRefs(WuttaSet):
|
||||||
|
|
||||||
def serialize(self, node, appstruct):
|
def serialize(self, node, appstruct):
|
||||||
if not appstruct:
|
if not appstruct:
|
||||||
appstruct = []
|
return colander.null
|
||||||
uuids = [u.hex for u in appstruct]
|
|
||||||
return json.dumps(uuids)
|
return [uuid.hex for uuid in appstruct]
|
||||||
|
|
||||||
def widget_maker(self, **kwargs):
|
def widget_maker(self, **kwargs):
|
||||||
from wuttafarm.web.forms.widgets import PlantTypeRefsWidget
|
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)
|
return PlantTypeRefsWidget(self.request, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ Custom form widgets for WuttaFarm
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import colander
|
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 webhelpers2.html import HTML, tags
|
||||||
|
|
||||||
from wuttaweb.forms.widgets import WuttaCheckboxChoiceWidget, ObjectRefWidget
|
from wuttaweb.forms.widgets import WuttaCheckboxChoiceWidget, ObjectRefWidget
|
||||||
|
|
@ -258,22 +258,40 @@ class FarmOSPlantTypesWidget(Widget):
|
||||||
return super().serialize(field, cstruct, **kw)
|
return super().serialize(field, cstruct, **kw)
|
||||||
|
|
||||||
|
|
||||||
class PlantTypeRefsWidget(WuttaCheckboxChoiceWidget):
|
class PlantTypeRefsWidget(Widget):
|
||||||
"""
|
"""
|
||||||
Widget for Plant Types field (on a Plant Asset).
|
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):
|
def serialize(self, field, cstruct, **kw):
|
||||||
""" """
|
""" """
|
||||||
model = self.app.model
|
model = self.app.model
|
||||||
session = Session()
|
session = Session()
|
||||||
|
|
||||||
readonly = kw.get("readonly", self.readonly)
|
if cstruct in (colander.null, None):
|
||||||
if readonly:
|
cstruct = ()
|
||||||
plant_types = []
|
|
||||||
for uuid in json.loads(cstruct):
|
if readonly := kw.get("readonly", self.readonly):
|
||||||
plant_type = session.get(model.PlantType, uuid)
|
items = []
|
||||||
plant_types.append(
|
|
||||||
|
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(
|
HTML.tag(
|
||||||
"li",
|
"li",
|
||||||
c=tags.link_to(
|
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):
|
class StructureWidget(Widget):
|
||||||
|
|
|
||||||
13
src/wuttafarm/web/templates/deform/planttyperefs.pt
Normal file
13
src/wuttafarm/web/templates/deform/planttyperefs.pt
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<div tal:define="
|
||||||
|
name name|field.name;
|
||||||
|
oid oid|field.oid;
|
||||||
|
vmodel vmodel|'modelData.'+oid;
|
||||||
|
can_create can_create|False;"
|
||||||
|
tal:omit-tag="">
|
||||||
|
|
||||||
|
<plant-types-picker tal:attributes="name name;
|
||||||
|
v-model vmodel;
|
||||||
|
:plant-types js_values;
|
||||||
|
:can-create str(can_create).lower();" />
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
|
|
||||||
<%def name="make_wuttafarm_components()">
|
<%def name="make_wuttafarm_components()">
|
||||||
${self.make_animal_type_picker_component()}
|
${self.make_animal_type_picker_component()}
|
||||||
|
${self.make_plant_types_picker_component()}
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
<%def name="make_animal_type_picker_component()">
|
<%def name="make_animal_type_picker_component()">
|
||||||
|
|
@ -126,3 +127,198 @@
|
||||||
<% request.register_component('animal-type-picker', 'AnimalTypePicker') %>
|
<% request.register_component('animal-type-picker', 'AnimalTypePicker') %>
|
||||||
</script>
|
</script>
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
|
<%def name="make_plant_types_picker_component()">
|
||||||
|
<script type="text/x-template" id="plant-types-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>
|
||||||
|
|
||||||
|
<b-button type="is-primary"
|
||||||
|
icon-pack="fas"
|
||||||
|
icon-left="plus"
|
||||||
|
@click="createInit()">
|
||||||
|
New
|
||||||
|
</b-button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<${b}-table :data="plantTypeData">
|
||||||
|
|
||||||
|
<${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="removePlantType(props.row)">
|
||||||
|
<i class="fas fa-trash" /> Remove
|
||||||
|
</a>
|
||||||
|
</${b}-table-column>
|
||||||
|
|
||||||
|
</${b}-table>
|
||||||
|
|
||||||
|
<${b}-modal v-if="canCreate"
|
||||||
|
has-modal-card
|
||||||
|
% if request.use_oruga:
|
||||||
|
v-model:active="createShowDialog"
|
||||||
|
% else:
|
||||||
|
:active.sync="createShowDialog"
|
||||||
|
% endif
|
||||||
|
>
|
||||||
|
<div class="modal-card">
|
||||||
|
|
||||||
|
<header class="modal-card-head">
|
||||||
|
<p class="modal-card-title">New Plant Type</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section class="modal-card-body">
|
||||||
|
<b-field label="Name" horizontal>
|
||||||
|
<b-input v-model="createName"
|
||||||
|
ref="createName"
|
||||||
|
expanded
|
||||||
|
@keydown.native="createNameKeydown" />
|
||||||
|
</b-field>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<footer class="modal-card-foot">
|
||||||
|
<b-button type="is-primary"
|
||||||
|
@click="createSave()"
|
||||||
|
:disabled="createSaving || !createName"
|
||||||
|
icon-pack="fas"
|
||||||
|
icon-left="save">
|
||||||
|
{{ createSaving ? "Working, please wait..." : "Save" }}
|
||||||
|
</b-button>
|
||||||
|
<b-button @click="createShowDialog = false">
|
||||||
|
Cancel
|
||||||
|
</b-button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</${b}-modal>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
const PlantTypesPicker = {
|
||||||
|
template: '#plant-types-picker-template',
|
||||||
|
mixins: [WuttaRequestMixin],
|
||||||
|
props: {
|
||||||
|
name: String,
|
||||||
|
value: Array,
|
||||||
|
plantTypes: Array,
|
||||||
|
canCreate: Boolean,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
internalPlantTypes: this.plantTypes.map((pt) => {
|
||||||
|
return {uuid: pt[0], name: pt[1]}
|
||||||
|
}),
|
||||||
|
|
||||||
|
addShowDialog: false,
|
||||||
|
addName: '',
|
||||||
|
|
||||||
|
createShowDialog: false,
|
||||||
|
createName: null,
|
||||||
|
createSaving: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
plantTypeData() {
|
||||||
|
const data = []
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
addNameData() {
|
||||||
|
if (!this.addName) {
|
||||||
|
return this.internalPlantTypes
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.internalPlantTypes.filter((ptype) => {
|
||||||
|
return ptype.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
|
||||||
|
},
|
||||||
|
|
||||||
|
createInit() {
|
||||||
|
this.createName = this.addName
|
||||||
|
this.createShowDialog = true
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.createName.focus()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
createNameKeydown(event) {
|
||||||
|
// nb. must prevent main form submit on ENTER
|
||||||
|
// (since ultimately this lives within an outer form)
|
||||||
|
// but also we can submit the modal pseudo-form
|
||||||
|
if (event.which == 13) {
|
||||||
|
event.preventDefault()
|
||||||
|
this.createSave()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
createSave() {
|
||||||
|
this.createSaving = true
|
||||||
|
const url = "${url('plant_types.ajax_create')}"
|
||||||
|
const params = {name: this.createName}
|
||||||
|
this.wuttaPOST(url, params, response => {
|
||||||
|
this.internalPlantTypes.push(response.data)
|
||||||
|
const value = Array.from(this.value || [])
|
||||||
|
value.push(response.data.uuid)
|
||||||
|
this.$emit('input', value)
|
||||||
|
this.addName = null
|
||||||
|
this.createSaving = false
|
||||||
|
this.createShowDialog = false
|
||||||
|
}, response => {
|
||||||
|
this.createSaving = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
removePlantType(ptype) {
|
||||||
|
let value = Array.from(this.value)
|
||||||
|
const i = value.indexOf(ptype.uuid)
|
||||||
|
value.splice(i, 1)
|
||||||
|
this.$emit('input', value)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Vue.component('plant-types-picker', PlantTypesPicker)
|
||||||
|
<% request.register_component('plant-types-picker', 'PlantTypesPicker') %>
|
||||||
|
</script>
|
||||||
|
</%def>
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ Master view for Plants
|
||||||
from webhelpers2.html import tags
|
from webhelpers2.html import tags
|
||||||
|
|
||||||
from wuttaweb.forms.schema import WuttaDictEnum
|
from wuttaweb.forms.schema import WuttaDictEnum
|
||||||
|
from wuttaweb.util import get_form_data
|
||||||
|
|
||||||
from wuttafarm.db.model import PlantType, PlantAsset
|
from wuttafarm.db.model import PlantType, PlantAsset
|
||||||
from wuttafarm.web.views.assets import AssetTypeMasterView, AssetMasterView
|
from wuttafarm.web.views.assets import AssetTypeMasterView, AssetMasterView
|
||||||
|
|
@ -42,8 +43,9 @@ class PlantTypeView(AssetTypeMasterView):
|
||||||
route_prefix = "plant_types"
|
route_prefix = "plant_types"
|
||||||
url_prefix = "/plant-types"
|
url_prefix = "/plant-types"
|
||||||
|
|
||||||
|
farmos_entity_type = "taxonomy_term"
|
||||||
|
farmos_bundle = "plant_type"
|
||||||
farmos_refurl_path = "/admin/structure/taxonomy/manage/plant_type/overview"
|
farmos_refurl_path = "/admin/structure/taxonomy/manage/plant_type/overview"
|
||||||
farmos_bundle = "plant"
|
|
||||||
|
|
||||||
grid_columns = [
|
grid_columns = [
|
||||||
"name",
|
"name",
|
||||||
|
|
@ -101,6 +103,19 @@ class PlantTypeView(AssetTypeMasterView):
|
||||||
|
|
||||||
return buttons
|
return buttons
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
plant_type = self.get_instance()
|
||||||
|
|
||||||
|
if plant_type._plant_assets:
|
||||||
|
self.request.session.flash(
|
||||||
|
"Cannot delete plant type which is still referenced by plant assets.",
|
||||||
|
"warning",
|
||||||
|
)
|
||||||
|
url = self.get_action_url("view", plant_type)
|
||||||
|
return self.redirect(self.request.get_referrer(default=url))
|
||||||
|
|
||||||
|
return super().delete()
|
||||||
|
|
||||||
def get_row_grid_data(self, plant_type):
|
def get_row_grid_data(self, plant_type):
|
||||||
model = self.app.model
|
model = self.app.model
|
||||||
session = self.Session()
|
session = self.Session()
|
||||||
|
|
@ -129,6 +144,55 @@ class PlantTypeView(AssetTypeMasterView):
|
||||||
def get_row_action_url_view(self, plant, i):
|
def get_row_action_url_view(self, plant, i):
|
||||||
return self.request.route_url("plant_assets.view", uuid=plant.uuid)
|
return self.request.route_url("plant_assets.view", uuid=plant.uuid)
|
||||||
|
|
||||||
|
def ajax_create(self):
|
||||||
|
"""
|
||||||
|
AJAX view to create a new plant type.
|
||||||
|
"""
|
||||||
|
model = self.app.model
|
||||||
|
session = self.Session()
|
||||||
|
data = get_form_data(self.request)
|
||||||
|
|
||||||
|
name = data.get("name")
|
||||||
|
if not name:
|
||||||
|
return {"error": "Name is required"}
|
||||||
|
|
||||||
|
plant_type = model.PlantType(name=name)
|
||||||
|
session.add(plant_type)
|
||||||
|
session.flush()
|
||||||
|
|
||||||
|
if self.app.is_farmos_mirror():
|
||||||
|
token = self.request.session.get("farmos.oauth2.token")
|
||||||
|
self.app.auto_sync_to_farmos(plant_type, token=token)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"uuid": plant_type.uuid.hex,
|
||||||
|
"name": plant_type.name,
|
||||||
|
"farmos_uuid": plant_type.farmos_uuid.hex,
|
||||||
|
"drupal_id": plant_type.drupal_id,
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def defaults(cls, config):
|
||||||
|
""" """
|
||||||
|
cls._defaults(config)
|
||||||
|
cls._plant_type_defaults(config)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _plant_type_defaults(cls, config):
|
||||||
|
route_prefix = cls.get_route_prefix()
|
||||||
|
permission_prefix = cls.get_permission_prefix()
|
||||||
|
url_prefix = cls.get_url_prefix()
|
||||||
|
|
||||||
|
# ajax_create
|
||||||
|
config.add_route(f"{route_prefix}.ajax_create", f"{url_prefix}/ajax/new")
|
||||||
|
config.add_view(
|
||||||
|
cls,
|
||||||
|
attr="ajax_create",
|
||||||
|
route_name=f"{route_prefix}.ajax_create",
|
||||||
|
permission=f"{permission_prefix}.create",
|
||||||
|
renderer="json",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class PlantAssetView(AssetMasterView):
|
class PlantAssetView(AssetMasterView):
|
||||||
"""
|
"""
|
||||||
|
|
@ -139,6 +203,7 @@ class PlantAssetView(AssetMasterView):
|
||||||
route_prefix = "plant_assets"
|
route_prefix = "plant_assets"
|
||||||
url_prefix = "/assets/plant"
|
url_prefix = "/assets/plant"
|
||||||
|
|
||||||
|
farmos_bundle = "plant"
|
||||||
farmos_refurl_path = "/assets/plant"
|
farmos_refurl_path = "/assets/plant"
|
||||||
|
|
||||||
labels = {
|
labels = {
|
||||||
|
|
@ -196,18 +261,38 @@ class PlantAssetView(AssetMasterView):
|
||||||
plant = f.model_instance
|
plant = f.model_instance
|
||||||
|
|
||||||
# plant_types
|
# plant_types
|
||||||
if self.creating or self.editing:
|
|
||||||
f.remove("plant_types") # TODO: add support for this
|
|
||||||
else:
|
|
||||||
f.set_node("plant_types", PlantTypeRefs(self.request))
|
f.set_node("plant_types", PlantTypeRefs(self.request))
|
||||||
f.set_default(
|
if not self.creating:
|
||||||
"plant_types", [t.plant_type_uuid for t in plant._plant_types]
|
# nb. must explcitly declare value for non-standard field
|
||||||
)
|
f.set_default("plant_types", [pt.uuid for pt in plant.plant_types])
|
||||||
|
|
||||||
# season
|
# season
|
||||||
if self.creating or self.editing:
|
if self.creating or self.editing:
|
||||||
f.remove("season") # TODO: add support for this
|
f.remove("season") # TODO: add support for this
|
||||||
|
|
||||||
|
def objectify(self, form):
|
||||||
|
model = self.app.model
|
||||||
|
session = self.Session()
|
||||||
|
plant = super().objectify(form)
|
||||||
|
data = form.validated
|
||||||
|
|
||||||
|
current = [pt.uuid for pt in plant.plant_types]
|
||||||
|
desired = data["plant_types"]
|
||||||
|
|
||||||
|
for uuid in desired:
|
||||||
|
if uuid not in current:
|
||||||
|
plant_type = session.get(model.PlantType, uuid)
|
||||||
|
assert plant_type
|
||||||
|
plant.plant_types.append(plant_type)
|
||||||
|
|
||||||
|
for uuid in current:
|
||||||
|
if uuid not in desired:
|
||||||
|
plant_type = session.get(model.PlantType, uuid)
|
||||||
|
assert plant_type
|
||||||
|
plant.plant_types.remove(plant_type)
|
||||||
|
|
||||||
|
return plant
|
||||||
|
|
||||||
|
|
||||||
def defaults(config, **kwargs):
|
def defaults(config, **kwargs):
|
||||||
base = globals()
|
base = globals()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue