Merge pull request #870 from smilerz/create_inputList
add new from ListInput
This commit is contained in:
commit
83de6bb9f3
@ -149,10 +149,13 @@ def search_recipes(request, queryset, params):
|
||||
|
||||
if len(search_foods) > 0:
|
||||
if search_foods_or == 'true':
|
||||
# TODO creating setting to include descendants of food a setting
|
||||
queryset = queryset.filter(steps__ingredients__food__id__in=search_foods)
|
||||
else:
|
||||
for k in search_foods:
|
||||
queryset = queryset.filter(steps__ingredients__food__id=k)
|
||||
# when performing an 'and' search returned recipes should include a parent OR any of its descedants
|
||||
# AND other foods selected so filters are appended using steps__ingredients__food__id__in the list of foods and descendants
|
||||
for fd in Food.objects.filter(pk__in=search_foods):
|
||||
queryset = queryset.filter(steps__ingredients__food__id__in=list(fd.get_descendants_and_self().values_list('pk', flat=True)))
|
||||
|
||||
if len(search_books) > 0:
|
||||
if search_books_or == 'true':
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -259,7 +259,7 @@ else:
|
||||
'USER': os.getenv('POSTGRES_USER'),
|
||||
'PASSWORD': os.getenv('POSTGRES_PASSWORD'),
|
||||
'NAME': os.getenv('POSTGRES_DB') if os.getenv('POSTGRES_DB') else 'db.sqlite3',
|
||||
'CONN_MAX_AGE': 600,
|
||||
'CONN_MAX_AGE': 60,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,7 +69,7 @@ import {BootstrapVue} from 'bootstrap-vue'
|
||||
|
||||
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
||||
|
||||
import {CardMixin, ToastMixin, ApiMixin} from "@/utils/utils";
|
||||
import {CardMixin, ApiMixin} from "@/utils/utils";
|
||||
import {StandardToasts} from "@/utils/utils";
|
||||
|
||||
import GenericSplitLists from "@/components/GenericSplitLists";
|
||||
@ -82,7 +82,7 @@ export default {
|
||||
// TODO ApiGenerator doesn't capture and share error information - would be nice to share error details when available
|
||||
// or i'm capturing it incorrectly
|
||||
name: 'ModelListView',
|
||||
mixins: [CardMixin, ToastMixin, ApiMixin],
|
||||
mixins: [CardMixin, ApiMixin],
|
||||
components: {GenericHorizontalCard, GenericSplitLists, GenericModalForm},
|
||||
data() {
|
||||
return {
|
||||
|
@ -10,11 +10,12 @@
|
||||
:label="label"
|
||||
track-by="id"
|
||||
:multiple="multiple"
|
||||
:taggable="create_new"
|
||||
:tag-placeholder="createText"
|
||||
:taggable="allow_create"
|
||||
:tag-placeholder="create_placeholder"
|
||||
:loading="loading"
|
||||
@search-change="search"
|
||||
@input="selectionChanged">
|
||||
@input="selectionChanged"
|
||||
@tag="addNew">
|
||||
</multiselect>
|
||||
</template>
|
||||
|
||||
@ -44,33 +45,22 @@ export default {
|
||||
sticky_options: {type:Array, default(){return []}},
|
||||
initial_selection: {type:Array, default(){return []}},
|
||||
multiple: {type: Boolean, default: true},
|
||||
create_new: {type: Boolean, default: false}, // TODO: this will create option to add new drop-downs
|
||||
create_text: {type: String, default: 'You Forgot to Add a Tag Placeholder'},
|
||||
allow_create: {type: Boolean, default: false}, // TODO: this will create option to add new drop-downs
|
||||
create_placeholder: {type: String, default: 'You Forgot to Add a Tag Placeholder'},
|
||||
},
|
||||
watch: {
|
||||
initial_selection: function (newVal, oldVal) { // watch it
|
||||
if (this.multiple) {
|
||||
this.selected_objects = newVal
|
||||
} else if (this.selected_objects != newVal?.[0]) {
|
||||
// when not using multiple selections need to convert array to value
|
||||
this.selected_objects = newVal?.[0] ?? null
|
||||
}
|
||||
this.selected_objects = newVal
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.search('')
|
||||
// when not using multiple selections need to convert array to value
|
||||
if (!this.multiple & this.selected_objects != this.initial_selection?.[0]) {
|
||||
this.selected_objects = this.initial_selection?.[0] ?? null
|
||||
}
|
||||
this.selected_objects = this.initial_selection
|
||||
},
|
||||
computed: {
|
||||
lookupPlaceholder() {
|
||||
return this.placeholder || this.model.name || this.$t('Search')
|
||||
},
|
||||
createText() {
|
||||
return this.create_text
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// this.genericAPI inherited from ApiMixin
|
||||
@ -85,7 +75,19 @@ export default {
|
||||
})
|
||||
},
|
||||
selectionChanged: function () {
|
||||
this.$emit('change', {var: this.parent_variable, val: this.selected_objects})
|
||||
if (this.multiple) {
|
||||
this.$emit('change', {var: this.parent_variable, val: this.selected_objects})
|
||||
} else {
|
||||
// if not multiple listener is expecting a single object, not an array
|
||||
this.$emit('change', {var: this.parent_variable, val: this.selected_objects?.[0] ?? null})
|
||||
}
|
||||
|
||||
},
|
||||
addNew(e) {
|
||||
this.$emit('new', e)
|
||||
// could refactor as Promise - seems unecessary
|
||||
setTimeout(() => { this.search(''); }, 750);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<b-modal class="modal" id="modal" @hidden="cancelAction">
|
||||
<b-modal :id="'modal_'+id" @hidden="cancelAction">
|
||||
<template v-slot:modal-title><h4>{{form.title}}</h4></template>
|
||||
<div v-for="(f, i) in form.fields" v-bind:key=i>
|
||||
<p v-if="f.type=='instruction'">{{f.label}}</p>
|
||||
@ -11,6 +11,7 @@
|
||||
:field="f.field"
|
||||
:model="listModel(f.list)"
|
||||
:sticky_options="f.sticky_options || undefined"
|
||||
:create_new="f.allow_create"
|
||||
@change="storeValue"/> <!-- TODO add ability to create new items associated with lookup -->
|
||||
<!-- TODO: add multi-selection input list -->
|
||||
<checkbox-input v-if="f.type=='checkbox'"
|
||||
@ -53,21 +54,24 @@ export default {
|
||||
name: 'GenericModalForm',
|
||||
components: {CheckboxInput, LookupInput, TextInput, EmojiInput},
|
||||
props: {
|
||||
model: {required: true, type: Object, default: function() {}},
|
||||
action: {required: true, type: Object, default: function() {}},
|
||||
item1: {type: Object, default: function() {}},
|
||||
item2: {type: Object, default: function() {}},
|
||||
model: {required: true, type: Object},
|
||||
action: {required: true, type: Object},
|
||||
item1: {type: Object, default () {return undefined}},
|
||||
item2: {type: Object, default () {return undefined}},
|
||||
show: {required: true, type: Boolean, default: false},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
id: undefined,
|
||||
form_data: {},
|
||||
form: {},
|
||||
dirty: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$root.$on('change', this.storeValue); // modal is outside Vue instance(?) so have to listen at root of component
|
||||
this.id = Math.random()
|
||||
this.$root.$on('change', this.storeValue); // boostrap modal placed at document so have to listen at root of component
|
||||
|
||||
},
|
||||
computed: {
|
||||
buttonLabel() {
|
||||
@ -79,9 +83,9 @@ export default {
|
||||
if (this.show) {
|
||||
this.form = getForm(this.model, this.action, this.item1, this.item2)
|
||||
this.dirty = true
|
||||
this.$bvModal.show('modal')
|
||||
this.$bvModal.show('modal_' + this.id)
|
||||
} else {
|
||||
this.$bvModal.hide('modal')
|
||||
this.$bvModal.hide('modal_' + this.id)
|
||||
this.form_data = {}
|
||||
}
|
||||
},
|
||||
|
@ -1,16 +1,20 @@
|
||||
<template>
|
||||
<div>
|
||||
<b-form-group
|
||||
<b-form-group
|
||||
v-bind:label="label"
|
||||
class="mb-3">
|
||||
<generic-multiselect
|
||||
@change="new_value=$event.val"
|
||||
:initial_selection="[value]"
|
||||
@remove="new_value=undefined"
|
||||
:initial_selection="initialSelection"
|
||||
:model="model"
|
||||
:multiple="false"
|
||||
:sticky_options="sticky_options"
|
||||
:allow_create="create_new"
|
||||
:create_placeholder="createPlaceholder"
|
||||
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
|
||||
:placeholder="modelName">
|
||||
:placeholder="modelName"
|
||||
@new="addNew">
|
||||
</generic-multiselect>
|
||||
</b-form-group>
|
||||
</div>
|
||||
@ -18,15 +22,18 @@
|
||||
|
||||
<script>
|
||||
import GenericMultiselect from "@/components/GenericMultiselect";
|
||||
import {StandardToasts, ApiMixin} from "@/utils/utils";
|
||||
|
||||
export default {
|
||||
name: 'LookupInput',
|
||||
components: {GenericMultiselect},
|
||||
mixins: [ApiMixin],
|
||||
props: {
|
||||
field: {type: String, default: 'You Forgot To Set Field Name'},
|
||||
label: {type: String, default: ''},
|
||||
value: {type: Object, default () {return {}}},
|
||||
model: {type: Object, default () {return {}}},
|
||||
value: {type: Object, default () {return undefined}},
|
||||
model: {type: Object, default () {return undefined}},
|
||||
create_new: {type: Boolean, default: false},
|
||||
sticky_options: {type:Array, default(){return []}},
|
||||
// TODO: include create_new and create_text props and associated functionality to create objects for drop down
|
||||
// see 'tagging' here: https://vue-multiselect.js.org/#sub-tagging
|
||||
@ -37,9 +44,27 @@ export default {
|
||||
new_value: undefined,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.new_value = this.value
|
||||
},
|
||||
computed: {
|
||||
modelName() {
|
||||
return this?.model?.name ?? this.$t('Search')
|
||||
},
|
||||
initialSelection() {
|
||||
// multiselect is expect to get an array of objects - make sure it gets one
|
||||
if (Array.isArray(this.new_value)) {
|
||||
return this.new_value
|
||||
} else if (!this.new_value) {
|
||||
return []
|
||||
} else if (this.new_value.id) {
|
||||
return [this.new_value]
|
||||
} else {
|
||||
return [{'id': -1, 'name': this.new_value}]
|
||||
}
|
||||
},
|
||||
createPlaceholder() {
|
||||
return this.$t('Create_New_' + this?.model?.name)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@ -47,5 +72,20 @@ export default {
|
||||
this.$root.$emit('change', this.field, this.new_value ?? null)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
addNew: function(e) {
|
||||
// if create a new item requires more than 1 parameter or the field 'name' is insufficient this will need reworked
|
||||
// in a perfect world this would trigger a new modal and allow editing all fields
|
||||
this.genericAPI(this.model, this.Actions.CREATE, {'name': e}).then((result) => {
|
||||
this.new_value = result.data
|
||||
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_CREATE)
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
StandardToasts.makeStandardToast(StandardToasts.FAIL_CREATE)
|
||||
})
|
||||
this.show_modal = false
|
||||
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
@ -125,5 +125,9 @@
|
||||
"Icon": "Icon",
|
||||
"Unit": "Unit",
|
||||
"No_Results": "No Results",
|
||||
"New_Unit": "New Unit"
|
||||
"New_Unit": "New Unit",
|
||||
"Create_New_Shopping Category": "Create New Shopping Category",
|
||||
"Create_New_Food": "Add New Food",
|
||||
"Create_New_Keyword": "Add New Keyword",
|
||||
"Create_New_Unit": "Add New Unit"
|
||||
}
|
||||
|
@ -100,6 +100,7 @@ export class Models {
|
||||
'field': 'supermarket_category',
|
||||
'list': 'SHOPPING_CATEGORY',
|
||||
'label': i18n.t('Shopping_Category'),
|
||||
'allow_create': true
|
||||
},
|
||||
}
|
||||
},
|
||||
@ -167,7 +168,26 @@ export class Models {
|
||||
}
|
||||
static SHOPPING_CATEGORY = {
|
||||
'name': i18n.t('Shopping_Category'),
|
||||
'apiName': 'SupermarketCategory',
|
||||
'apiName': 'SupermarketCategory',
|
||||
'create': {
|
||||
'params': [['name', 'description']],
|
||||
'form': {
|
||||
'name': {
|
||||
'form_field': true,
|
||||
'type': 'text',
|
||||
'field': 'name',
|
||||
'label': i18n.t('Name'),
|
||||
'placeholder': ''
|
||||
},
|
||||
'description': {
|
||||
'form_field': true,
|
||||
'type': 'text',
|
||||
'field': 'description',
|
||||
'label': i18n.t('Description'),
|
||||
'placeholder': ''
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
static RECIPE = {
|
||||
|
Loading…
Reference in New Issue
Block a user