feat: add way to create animal type when editing animal
This commit is contained in:
parent
1c0286eda0
commit
ec67340e66
7 changed files with 237 additions and 47 deletions
|
|
@ -40,6 +40,15 @@ def main(global_config, **settings):
|
||||||
"wuttaweb:templates",
|
"wuttaweb:templates",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
settings.setdefault(
|
||||||
|
"pyramid_deform.template_search_path",
|
||||||
|
" ".join(
|
||||||
|
[
|
||||||
|
"wuttafarm.web:templates/deform",
|
||||||
|
"wuttaweb:templates/deform",
|
||||||
|
]
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
# make config objects
|
# make config objects
|
||||||
wutta_config = base.make_wutta_config(settings)
|
wutta_config = base.make_wutta_config(settings)
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,12 @@ class AnimalTypeRef(ObjectRef):
|
||||||
animal_type = obj
|
animal_type = obj
|
||||||
return self.request.route_url("animal_types.view", uuid=animal_type.uuid)
|
return self.request.route_url("animal_types.view", uuid=animal_type.uuid)
|
||||||
|
|
||||||
|
def widget_maker(self, **kwargs):
|
||||||
|
from wuttafarm.web.forms.widgets import AnimalTypeRefWidget
|
||||||
|
|
||||||
|
kwargs["factory"] = AnimalTypeRefWidget
|
||||||
|
return super().widget_maker(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
class LogQuick(WuttaSet):
|
class LogQuick(WuttaSet):
|
||||||
|
|
||||||
|
|
@ -185,25 +191,6 @@ class FarmOSQuantityRefs(WuttaSet):
|
||||||
return FarmOSQuantityRefsWidget(**kwargs)
|
return FarmOSQuantityRefsWidget(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
class AnimalTypeType(colander.SchemaType):
|
|
||||||
|
|
||||||
def __init__(self, request, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.request = request
|
|
||||||
|
|
||||||
def serialize(self, node, appstruct):
|
|
||||||
if appstruct is colander.null:
|
|
||||||
return colander.null
|
|
||||||
|
|
||||||
return json.dumps(appstruct)
|
|
||||||
|
|
||||||
def widget_maker(self, **kwargs): # pylint: disable=empty-docstring
|
|
||||||
""" """
|
|
||||||
from wuttafarm.web.forms.widgets import AnimalTypeWidget
|
|
||||||
|
|
||||||
return AnimalTypeWidget(self.request, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class FarmOSPlantTypes(colander.SchemaType):
|
class FarmOSPlantTypes(colander.SchemaType):
|
||||||
|
|
||||||
def __init__(self, request, *args, **kwargs):
|
def __init__(self, request, *args, **kwargs):
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ import colander
|
||||||
from deform.widget import Widget, SelectWidget
|
from deform.widget import Widget, SelectWidget
|
||||||
from webhelpers2.html import HTML, tags
|
from webhelpers2.html import HTML, tags
|
||||||
|
|
||||||
from wuttaweb.forms.widgets import WuttaCheckboxChoiceWidget
|
from wuttaweb.forms.widgets import WuttaCheckboxChoiceWidget, ObjectRefWidget
|
||||||
from wuttaweb.db import Session
|
from wuttaweb.db import Session
|
||||||
|
|
||||||
from wuttafarm.web.util import render_quantity_objects
|
from wuttafarm.web.util import render_quantity_objects
|
||||||
|
|
@ -228,33 +228,6 @@ class FarmOSUnitRefWidget(Widget):
|
||||||
return super().serialize(field, cstruct, **kw)
|
return super().serialize(field, cstruct, **kw)
|
||||||
|
|
||||||
|
|
||||||
class AnimalTypeWidget(Widget):
|
|
||||||
"""
|
|
||||||
Widget to display an "animal type" field.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, request, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.request = request
|
|
||||||
|
|
||||||
def serialize(self, field, cstruct, **kw):
|
|
||||||
""" """
|
|
||||||
readonly = kw.get("readonly", self.readonly)
|
|
||||||
if readonly:
|
|
||||||
if cstruct in (colander.null, None):
|
|
||||||
return HTML.tag("span")
|
|
||||||
|
|
||||||
animal_type = json.loads(cstruct)
|
|
||||||
return tags.link_to(
|
|
||||||
animal_type["name"],
|
|
||||||
self.request.route_url(
|
|
||||||
"farmos_animal_types.view", uuid=animal_type["uuid"]
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
return super().serialize(field, cstruct, **kw)
|
|
||||||
|
|
||||||
|
|
||||||
class FarmOSPlantTypesWidget(Widget):
|
class FarmOSPlantTypesWidget(Widget):
|
||||||
"""
|
"""
|
||||||
Widget to display a farmOS "plant types" field.
|
Widget to display a farmOS "plant types" field.
|
||||||
|
|
@ -372,6 +345,11 @@ class UsersWidget(Widget):
|
||||||
return super().serialize(field, cstruct, **kw)
|
return super().serialize(field, cstruct, **kw)
|
||||||
|
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# native data widgets
|
||||||
|
##############################
|
||||||
|
|
||||||
|
|
||||||
class AssetParentRefsWidget(WuttaCheckboxChoiceWidget):
|
class AssetParentRefsWidget(WuttaCheckboxChoiceWidget):
|
||||||
"""
|
"""
|
||||||
Widget for Parents field which references assets.
|
Widget for Parents field which references assets.
|
||||||
|
|
@ -432,3 +410,22 @@ class LogAssetRefsWidget(WuttaCheckboxChoiceWidget):
|
||||||
return HTML.tag("ul", c=assets)
|
return HTML.tag("ul", c=assets)
|
||||||
|
|
||||||
return super().serialize(field, cstruct, **kw)
|
return super().serialize(field, cstruct, **kw)
|
||||||
|
|
||||||
|
|
||||||
|
class AnimalTypeRefWidget(ObjectRefWidget):
|
||||||
|
"""
|
||||||
|
Custom widget which uses the ``<animal-type-picker>`` component.
|
||||||
|
"""
|
||||||
|
|
||||||
|
template = "animaltyperef"
|
||||||
|
|
||||||
|
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("animal_types.create"):
|
||||||
|
values["can_create"] = True
|
||||||
|
|
||||||
|
return values
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<%inherit file="wuttaweb:templates/base.mako" />
|
<%inherit file="wuttaweb:templates/base.mako" />
|
||||||
|
<%namespace file="/wuttafarm-components.mako" import="make_wuttafarm_components" />
|
||||||
|
|
||||||
<%def name="index_title_controls()">
|
<%def name="index_title_controls()">
|
||||||
${parent.index_title_controls()}
|
${parent.index_title_controls()}
|
||||||
|
|
@ -14,3 +15,8 @@
|
||||||
% endif
|
% endif
|
||||||
|
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
|
<%def name="render_vue_templates()">
|
||||||
|
${parent.render_vue_templates()}
|
||||||
|
${make_wuttafarm_components()}
|
||||||
|
</%def>
|
||||||
|
|
|
||||||
13
src/wuttafarm/web/templates/deform/animaltyperef.pt
Normal file
13
src/wuttafarm/web/templates/deform/animaltyperef.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="">
|
||||||
|
|
||||||
|
<animal-type-picker tal:attributes="name name;
|
||||||
|
v-model vmodel;
|
||||||
|
:animal-types js_values;
|
||||||
|
:can-create str(can_create).lower();" />
|
||||||
|
|
||||||
|
</div>
|
||||||
128
src/wuttafarm/web/templates/wuttafarm-components.mako
Normal file
128
src/wuttafarm/web/templates/wuttafarm-components.mako
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
|
||||||
|
<%def name="make_wuttafarm_components()">
|
||||||
|
${self.make_animal_type_picker_component()}
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="make_animal_type_picker_component()">
|
||||||
|
<script type="text/x-template" id="animal-type-picker-template">
|
||||||
|
<div>
|
||||||
|
<div style="display: flex; gap: 0.5rem;">
|
||||||
|
|
||||||
|
<b-select :name="name"
|
||||||
|
:value="internalValue"
|
||||||
|
@input="val => $emit('input', val)"
|
||||||
|
style="flex-grow: 1;">
|
||||||
|
<option v-for="atype in internalAnimalTypes"
|
||||||
|
:value="atype[0]">
|
||||||
|
{{ atype[1] }}
|
||||||
|
</option>
|
||||||
|
</b-select>
|
||||||
|
|
||||||
|
<b-button v-if="canCreate"
|
||||||
|
type="is-primary"
|
||||||
|
icon-pack="fas"
|
||||||
|
icon-left="plus"
|
||||||
|
@click="createInit()">
|
||||||
|
New
|
||||||
|
</b-button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<${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 Animal 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 AnimalTypePicker = {
|
||||||
|
template: '#animal-type-picker-template',
|
||||||
|
mixins: [WuttaRequestMixin],
|
||||||
|
props: {
|
||||||
|
name: String,
|
||||||
|
value: String,
|
||||||
|
animalTypes: Array,
|
||||||
|
canCreate: Boolean,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
internalAnimalTypes: this.animalTypes,
|
||||||
|
internalValue: this.value,
|
||||||
|
createShowDialog: false,
|
||||||
|
createName: null,
|
||||||
|
createSaving: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
createInit(name) {
|
||||||
|
this.createName = name || null
|
||||||
|
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('animal_types.ajax_create')}"
|
||||||
|
const params = {name: this.createName}
|
||||||
|
this.wuttaPOST(url, params, response => {
|
||||||
|
this.internalAnimalTypes.push([response.data.uuid, response.data.name])
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.internalValue = response.data.uuid
|
||||||
|
this.createSaving = false
|
||||||
|
this.createShowDialog = false
|
||||||
|
})
|
||||||
|
}, response => {
|
||||||
|
this.createSaving = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Vue.component('animal-type-picker', AnimalTypePicker)
|
||||||
|
<% request.register_component('animal-type-picker', 'AnimalTypePicker') %>
|
||||||
|
</script>
|
||||||
|
</%def>
|
||||||
|
|
@ -26,6 +26,7 @@ Master view for Animals
|
||||||
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 AnimalType, AnimalAsset
|
from wuttafarm.db.model import AnimalType, AnimalAsset
|
||||||
from wuttafarm.web.views.assets import AssetTypeMasterView, AssetMasterView
|
from wuttafarm.web.views.assets import AssetTypeMasterView, AssetMasterView
|
||||||
|
|
@ -137,6 +138,55 @@ class AnimalTypeView(AssetTypeMasterView):
|
||||||
def get_row_action_url_view(self, animal, i):
|
def get_row_action_url_view(self, animal, i):
|
||||||
return self.request.route_url("animal_assets.view", uuid=animal.uuid)
|
return self.request.route_url("animal_assets.view", uuid=animal.uuid)
|
||||||
|
|
||||||
|
def ajax_create(self):
|
||||||
|
"""
|
||||||
|
AJAX view to create a new animal 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"}
|
||||||
|
|
||||||
|
animal_type = model.AnimalType(name=name)
|
||||||
|
session.add(animal_type)
|
||||||
|
session.flush()
|
||||||
|
|
||||||
|
if self.app.is_farmos_mirror():
|
||||||
|
token = self.request.session.get("farmos.oauth2.token")
|
||||||
|
self.app.auto_sync_to_farmos(animal_type, token=token)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"uuid": animal_type.uuid.hex,
|
||||||
|
"name": animal_type.name,
|
||||||
|
"farmos_uuid": animal_type.farmos_uuid.hex,
|
||||||
|
"drupal_id": animal_type.drupal_id,
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def defaults(cls, config):
|
||||||
|
""" """
|
||||||
|
cls._defaults(config)
|
||||||
|
cls._animal_type_defaults(config)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _animal_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 AnimalAssetView(AssetMasterView):
|
class AnimalAssetView(AssetMasterView):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue