food editor and property view improvements

This commit is contained in:
vabene1111 2023-05-24 13:49:29 +02:00
parent 79cd17a5ba
commit 3236b65d9e
7 changed files with 177 additions and 102 deletions

View File

@ -82,31 +82,34 @@ class TreeManager(MP_NodeManager):
# model.Manager get_or_create() is not compatible with MP_Tree # model.Manager get_or_create() is not compatible with MP_Tree
def get_or_create(self, *args, **kwargs): def get_or_create(self, *args, **kwargs):
kwargs['name'] = kwargs['name'].strip() kwargs['name'] = kwargs['name'].strip()
if hasattr(self, 'space'):
if obj := self.filter(name__iexact=kwargs['name'], space=kwargs['space']).first(): if obj := self.filter(name__iexact=kwargs['name'], space=kwargs['space']).first():
return obj, False return obj, False
else: else:
with scopes_disabled(): if obj := self.filter(name__iexact=kwargs['name']).first():
try: return obj, False
defaults = kwargs.pop('defaults', None)
if defaults: with scopes_disabled():
kwargs = {**kwargs, **defaults} try:
# ManyToMany fields can't be set this way, so pop them out to save for later defaults = kwargs.pop('defaults', None)
fields = [field.name for field in self.model._meta.get_fields() if issubclass(type(field), ManyToManyField)] if defaults:
many_to_many = {field: kwargs.pop(field) for field in list(kwargs) if field in fields} kwargs = {**kwargs, **defaults}
obj = self.model.add_root(**kwargs) # ManyToMany fields can't be set this way, so pop them out to save for later
for field in many_to_many: fields = [field.name for field in self.model._meta.get_fields() if issubclass(type(field), ManyToManyField)]
field_model = getattr(obj, field).model many_to_many = {field: kwargs.pop(field) for field in list(kwargs) if field in fields}
for related_obj in many_to_many[field]: obj = self.model.add_root(**kwargs)
if isinstance(related_obj, User): for field in many_to_many:
getattr(obj, field).add(field_model.objects.get(id=related_obj.id)) field_model = getattr(obj, field).model
else: for related_obj in many_to_many[field]:
getattr(obj, field).add(field_model.objects.get(**dict(related_obj))) if isinstance(related_obj, User):
return obj, True getattr(obj, field).add(field_model.objects.get(id=related_obj.id))
except IntegrityError as e: else:
if 'Key (path)' in e.args[0]: getattr(obj, field).add(field_model.objects.get(**dict(related_obj)))
self.model.fix_tree(fix_paths=True) return obj, True
return self.model.add_root(**kwargs), True except IntegrityError as e:
if 'Key (path)' in e.args[0]:
self.model.fix_tree(fix_paths=True)
return self.model.add_root(**kwargs), True
class TreeModel(MP_Node): class TreeModel(MP_Node):

View File

@ -250,6 +250,9 @@ class MergeMixin(ViewSetMixin):
isTree = False isTree = False
try: try:
if isinstance(source, Food):
FoodProperty.objects.filter(food=source).delete()
for link in [field for field in source._meta.get_fields() if issubclass(type(field), ForeignObjectRel)]: for link in [field for field in source._meta.get_fields() if issubclass(type(field), ForeignObjectRel)]:
linkManager = getattr(source, link.get_accessor_name()) linkManager = getattr(source, link.get_accessor_name())
related = linkManager.all() related = linkManager.all()
@ -279,6 +282,7 @@ class MergeMixin(ViewSetMixin):
source.delete() source.delete()
return Response(content, status=status.HTTP_200_OK) return Response(content, status=status.HTTP_200_OK)
except Exception: except Exception:
traceback.print_exc()
content = {'error': True, content = {'error': True,
'msg': _(f'An error occurred attempting to merge {source.name} with {target.name}')} 'msg': _(f'An error occurred attempting to merge {source.name} with {target.name}')}
return Response(content, status=status.HTTP_400_BAD_REQUEST) return Response(content, status=status.HTTP_400_BAD_REQUEST)

View File

@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/2.0/ref/settings/
""" """
import ast import ast
import json import json
import mimetypes
import os import os
import re import re
import sys import sys
@ -519,3 +520,5 @@ EMAIL_USE_SSL = bool(int(os.getenv('EMAIL_USE_SSL', False)))
DEFAULT_FROM_EMAIL = os.getenv('DEFAULT_FROM_EMAIL', 'webmaster@localhost') DEFAULT_FROM_EMAIL = os.getenv('DEFAULT_FROM_EMAIL', 'webmaster@localhost')
ACCOUNT_EMAIL_SUBJECT_PREFIX = os.getenv( ACCOUNT_EMAIL_SUBJECT_PREFIX = os.getenv(
'ACCOUNT_EMAIL_SUBJECT_PREFIX', '[Tandoor Recipes] ') # allauth sender prefix 'ACCOUNT_EMAIL_SUBJECT_PREFIX', '[Tandoor Recipes] ') # allauth sender prefix
mimetypes.add_type("text/javascript", ".js", True)

View File

@ -137,9 +137,7 @@
<div class="row" style="margin-top: 2vh; "> <div class="row" style="margin-top: 2vh; ">
<div class="col-lg-6 offset-lg-3 col-12"> <div class="col-lg-6 offset-lg-3 col-12">
<!-- <Nutrition-component :recipe="recipe" id="nutrition_container"--> <food-property-view-component :recipe="recipe" :servings="servings" @foodUpdated="loadRecipe(recipe.id)"></food-property-view-component>
<!-- :ingredient_factor="ingredient_factor"></Nutrition-component>-->
<food-property-view-component :recipe="recipe" :servings="servings"></food-property-view-component>
</div> </div>
</div> </div>
</div> </div>

View File

@ -36,12 +36,12 @@
<td><input v-model="fp.food_amount" type="number"></td> <td><input v-model="fp.food_amount" type="number"></td>
<td> <td>
<generic-multiselect <generic-multiselect
@change="fp.food_unit = $event.val;" @change="fp.food_unit = $event.val;"
:model="Models.UNIT" :model="Models.UNIT"
:initial_single_selection="fp.food_unit" :initial_single_selection="fp.food_unit"
label="name" label="name"
:multiple="false" :multiple="false"
:placeholder="$t('Unit')" :placeholder="$t('Unit')"
></generic-multiselect> ></generic-multiselect>
</td> </td>
</tr> </tr>
@ -54,12 +54,12 @@
<b-collapse id="collapse-advanced"> <b-collapse id="collapse-advanced">
<b-form-group :label="$t('Recipe')" :description="$t('food_recipe_help')"> <b-form-group :label="$t('Recipe')" :description="$t('food_recipe_help')">
<generic-multiselect <generic-multiselect
@change="food.recipe = $event.val;" @change="food.recipe = $event.val;"
:model="Models.RECIPE" :model="Models.RECIPE"
:initial_single_selection="food.recipe" :initial_single_selection="food.recipe"
label="name" label="name"
:multiple="false" :multiple="false"
:placeholder="$t('Recipe')" :placeholder="$t('Recipe')"
></generic-multiselect> ></generic-multiselect>
</b-form-group> </b-form-group>
@ -76,13 +76,13 @@
<b-form-group :label="$t('Shopping_Category')" :description="$t('shopping_category_help')"> <b-form-group :label="$t('Shopping_Category')" :description="$t('shopping_category_help')">
<generic-multiselect <generic-multiselect
@change="food.supermarket_category = $event.val;" @change="food.supermarket_category = $event.val;"
:model="Models.SHOPPING_CATEGORY" :model="Models.SHOPPING_CATEGORY"
:initial_single_selection="food.supermarket_category" :initial_single_selection="food.supermarket_category"
label="name" label="name"
:multiple="false" :multiple="false"
:allow_create="true" :allow_create="true"
:placeholder="$t('Shopping_Category')" :placeholder="$t('Shopping_Category')"
></generic-multiselect> ></generic-multiselect>
</b-form-group> </b-form-group>
@ -90,12 +90,12 @@
<!-- todo add conditions if false disable dont hide --> <!-- todo add conditions if false disable dont hide -->
<b-form-group :label="$t('Substitutes')" :description="$t('substitute_help')"> <b-form-group :label="$t('Substitutes')" :description="$t('substitute_help')">
<generic-multiselect <generic-multiselect
@change="food.substitute = $event.val;" @change="food.substitute = $event.val;"
:model="Models.FOOD" :model="Models.FOOD"
:initial_selection="food.substitute" :initial_selection="food.substitute"
label="name" label="name"
:multiple="true" :multiple="true"
:placeholder="$t('Substitutes')" :placeholder="$t('Substitutes')"
></generic-multiselect> ></generic-multiselect>
</b-form-group> </b-form-group>
@ -108,24 +108,24 @@
<b-form-group :label="$t('InheritFields')" :description="$t('InheritFields_help')"> <b-form-group :label="$t('InheritFields')" :description="$t('InheritFields_help')">
<generic-multiselect <generic-multiselect
@change="food.inherit_fields = $event.val;" @change="food.inherit_fields = $event.val;"
:model="Models.FOOD_INHERIT_FIELDS" :model="Models.FOOD_INHERIT_FIELDS"
:initial_selection="food.inherit_fields" :initial_selection="food.inherit_fields"
label="name" label="name"
:multiple="true" :multiple="true"
:placeholder="$t('InheritFields')" :placeholder="$t('InheritFields')"
></generic-multiselect> ></generic-multiselect>
</b-form-group> </b-form-group>
<b-form-group :label="$t('ChildInheritFields')" <b-form-group :label="$t('ChildInheritFields')"
:description="$t('ChildInheritFields_help')"> :description="$t('ChildInheritFields_help')">
<generic-multiselect <generic-multiselect
@change="food.child_inherit_fields = $event.val;" @change="food.child_inherit_fields = $event.val;"
:model="Models.FOOD_INHERIT_FIELDS" :model="Models.FOOD_INHERIT_FIELDS"
:initial_sselection="food.child_inherit_fields" :initial_sselection="food.child_inherit_fields"
label="name" label="name"
:multiple="true" :multiple="true"
:placeholder="$t('ChildInheritFields')" :placeholder="$t('ChildInheritFields')"
></generic-multiselect> ></generic-multiselect>
</b-form-group> </b-form-group>
@ -174,15 +174,16 @@ export default {
props: { props: {
id: {type: String, default: 'id_food_edit_modal_modal'}, id: {type: String, default: 'id_food_edit_modal_modal'},
show: {required: true, type: Boolean, default: false}, show: {required: true, type: Boolean, default: false},
item1: {
type: Object,
default: undefined
},
}, },
watch: { watch: {
show: function () { show: function () {
console.log('trigger')
if (this.show) { if (this.show) {
console.log('show modal')
this.$bvModal.show(this.id) this.$bvModal.show(this.id)
} else { } else {
console.log('show modal false')
this.$bvModal.hide(this.id) this.$bvModal.hide(this.id)
} }
}, },
@ -197,9 +198,35 @@ export default {
this.$bvModal.show(this.id) this.$bvModal.show(this.id)
this.$i18n.locale = window.CUSTOM_LOCALE this.$i18n.locale = window.CUSTOM_LOCALE
let apiClient = new ApiApiFactory() let apiClient = new ApiApiFactory()
apiClient.retrieveFood('1').then((r) => { let pf
this.food = r.data if (this.item1.id !== undefined) {
pf = apiClient.retrieveFood(this.item1.id).then((r) => {
this.food = r.data
}).catch(err => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_FETCH, err)
})
} else {
this.food = {
name: "",
plural_name: "",
description: "",
shopping: false,
recipe: null,
food_onhand: false,
supermarket_category: null,
parent: null,
numchild: 0,
inherit_fields: [],
ignore_shopping: false,
substitute: [],
substitute_siblings: false,
substitute_children: false,
substitute_onhand: false,
child_inherit_fields: [],
}
}
Promise.allSettled([pf]).then(r => {
let property_types = [] let property_types = []
let property_values = [] let property_values = []
@ -207,15 +234,18 @@ export default {
property_types = r.data property_types = r.data
}) })
let p2 = apiClient.listFoodPropertys(this.food.id).then((r) => { let p2
property_values = r.data if (this.food.id !== undefined) {
}) p2 = apiClient.listFoodPropertys(this.food.id).then((r) => {
property_values = r.data
})
}
Promise.allSettled([p1, p2]).then(r => { Promise.allSettled([p1, p2]).then(r => {
property_types.forEach(fpt => { property_types.forEach(fpt => {
let food_property = { let food_property = {
'food_amount': 0, 'food_amount': 100,
'food_unit': null, 'food_unit': {'name': 'g'},
'food': this.food, 'food': this.food,
'property_amount': 0, 'property_amount': 0,
'property_type': fpt, 'property_type': fpt,
@ -234,35 +264,46 @@ export default {
}) })
}) })
}) })
}, },
methods: { methods: {
updateFood: function () { updateFood: function () {
let apiClient = new ApiApiFactory() let apiClient = new ApiApiFactory()
apiClient.updateFood(this.food.id, this.food).then((r) => { let p
this.food = r.data if (this.food.id !== undefined) {
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE) p = apiClient.updateFood(this.food.id, this.food).then((r) => {
}).catch(err => { this.food = r.data
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err) StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE)
}).catch(err => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
})
} else {
p = apiClient.createFood(this.food).then((r) => {
this.food = r.data
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_CREATE)
}).catch(err => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
})
}
Promise.allSettled([p]).then(r => {
this.food_properties.forEach(fp => {
fp.food = this.food
if (fp.id === undefined) {
apiClient.createFoodProperty(fp).then((r) => {
fp = r.data
}).catch(err => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
})
} else {
apiClient.updateFoodProperty(fp.id, fp).then((r) => {
fp = r.data
}).catch(err => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
})
}
})
}) })
this.food_properties.forEach(fp => {
if (fp.id === undefined) {
apiClient.createFoodProperty(fp).then((r) => {
fp = r.data
}).catch(err => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
})
} else {
apiClient.updateFoodProperty(fp.id, fp).then((r) => {
fp = r.data
}).catch(err => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
})
}
})
}, },
cancelAction: function () { cancelAction: function () {
this.$emit("hidden", "") this.$emit("hidden", "")

View File

@ -49,24 +49,34 @@
<table class="table table-bordered"> <table class="table table-bordered">
<tr v-for="f in selected_property.food_values" <tr v-for="f in selected_property.food_values"
v-bind:key="`id_${selected_property.id}_food_${f.id}`"> v-bind:key="`id_${selected_property.id}_food_${f.id}`">
<td>{{ f.food }}</td> <td><a href="#" @click="openFoodEditModal(f)">{{ f.food }}</a></td>
<td>{{ f.value }} {{ selected_property.unit }}</td> <td>{{ f.value }} {{ selected_property.unit }}</td>
</tr> </tr>
</table> </table>
</template> </template>
</b-modal> </b-modal>
<generic-modal-form
:model="Models.FOOD"
:action="Actions.UPDATE"
:item1="selected_food"
:show="show_food_edit_modal"
@hidden="foodEditorHidden"
>
</generic-modal-form>
</div> </div>
</template> </template>
<script> <script>
import {ApiMixin} from "@/utils/utils"; import {ApiMixin, StandardToasts} from "@/utils/utils";
import GenericModalForm from "@/components/Modals/GenericModalForm.vue";
import {ApiApiFactory} from "@/utils/openapi/api";
export default { export default {
name: "FoodPropertyViewComponent", name: "FoodPropertyViewComponent",
mixins: [ApiMixin], mixins: [ApiMixin],
components: {}, components: {GenericModalForm},
props: { props: {
recipe: Object, recipe: Object,
servings: Number, servings: Number,
@ -74,6 +84,8 @@ export default {
data() { data() {
return { return {
selected_property: undefined, selected_property: undefined,
selected_food: undefined,
show_food_edit_modal: false,
show_total: false, show_total: false,
} }
}, },
@ -98,6 +110,20 @@ export default {
'minimumFractionDigits': 2 'minimumFractionDigits': 2
}) })
} }
},
openFoodEditModal: function (food) {
console.log(food)
let apiClient = ApiApiFactory()
apiClient.retrieveFood(food.id).then(r => {
this.selected_food = r.data;
this.show_food_edit_modal = true
}).catch(err => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_FETCH, err)
})
},
foodEditorHidden: function (){
this.show_food_edit_modal = false;
this.$emit("foodUpdated", "")
} }
}, },
} }

View File

@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<template v-if="form_component !== undefined"> <template v-if="form_component !== undefined">
<component :is="form_component" :id="'modal_' + id" :show="show" @hidden="cancelAction"></component> <component :is="form_component" :id="'modal_' + id" :show="show" @hidden="cancelAction" :item1="item1"></component>
</template> </template>
<template v-else> <template v-else>
<b-modal :id="'modal_' + id" @hidden="cancelAction" size="lg"> <b-modal :id="'modal_' + id" @hidden="cancelAction" size="lg">