Merge pull request #870 from smilerz/create_inputList

add new from ListInput
This commit is contained in:
vabene1111 2021-09-09 05:44:47 +02:00 committed by GitHub
commit 83de6bb9f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 122 additions and 46 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 = {