first ideas of property editor

This commit is contained in:
vabene1111 2023-04-11 17:27:10 +02:00
parent 7d9fcac0c7
commit b275c53e5a
7 changed files with 206 additions and 54 deletions

View File

@ -651,7 +651,7 @@ class IngredientSimpleSerializer(WritableNestedModelSerializer):
uch = UnitConversionHelper(self.context['request'].space) uch = UnitConversionHelper(self.context['request'].space)
conversions = [] conversions = []
for c in uch.get_conversions(obj): for c in uch.get_conversions(obj):
conversions.append({'food': c.food.name, 'unit': c.unit.name , 'amount': c.amount}) # TODO do formatting in helper conversions.append({'food': c.food.name, 'unit': c.unit.name, 'amount': c.amount}) # TODO do formatting in helper
return conversions return conversions
else: else:
return [] return []
@ -726,7 +726,7 @@ class StepRecipeSerializer(WritableNestedModelSerializer):
class UnitConversionSerializer(WritableNestedModelSerializer): class UnitConversionSerializer(WritableNestedModelSerializer):
base_unit = UnitSerializer() base_unit = UnitSerializer()
converted_unit = UnitSerializer() converted_unit = UnitSerializer()
food = FoodSerializer(allow_null=True) food = FoodSerializer(allow_null=True, required=False)
base_amount = CustomDecimalField() base_amount = CustomDecimalField()
converted_amount = CustomDecimalField() converted_amount = CustomDecimalField()
@ -740,7 +740,7 @@ class UnitConversionSerializer(WritableNestedModelSerializer):
fields = ('id', 'base_amount', 'base_unit', 'converted_amount', 'converted_unit', 'food') fields = ('id', 'base_amount', 'base_unit', 'converted_amount', 'converted_unit', 'food')
class NutritionTypeSerializer(serializers.ModelSerializer): class FoodPropertyTypeSerializer(serializers.ModelSerializer):
def create(self, validated_data): def create(self, validated_data):
validated_data['space'] = self.context['request'].space validated_data['space'] = self.context['request'].space
return super().create(validated_data) return super().create(validated_data)
@ -750,6 +750,22 @@ class NutritionTypeSerializer(serializers.ModelSerializer):
fields = ('id', 'name', 'icon', 'unit', 'description') fields = ('id', 'name', 'icon', 'unit', 'description')
class FoodPropertySerializer(UniqueFieldsMixin, WritableNestedModelSerializer):
property_type = FoodPropertyTypeSerializer()
food = FoodSimpleSerializer()
unit = UnitSerializer()
# TODO prevent updates
def create(self, validated_data):
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
class Meta:
model = FoodProperty
fields = ('id', 'food_amount', 'food_unit', 'food', 'property_amount', 'property_type')
read_only_fields = ('id',)
class NutritionInformationSerializer(serializers.ModelSerializer): class NutritionInformationSerializer(serializers.ModelSerializer):
carbohydrates = CustomDecimalField() carbohydrates = CustomDecimalField()
fats = CustomDecimalField() fats = CustomDecimalField()

View File

@ -269,6 +269,33 @@
</div> </div>
</a> </a>
</div> </div>
<div class="col-4">
<a href="{% url 'list_food_property_type' %}" class="p-0 p-md-1">
<div class="card p-0 no-gutters border-0">
<div class="card-body text-center p-0 no-gutters">
<i class="fas fa-database fa-2x"></i>
</div>
<div class="card-body text-break text-center p-0 no-gutters text-muted menu-dropdown-text">
{% trans 'Food Properties' %}
</div>
</div>
</a>
</div>
</div>
<div class="row m-0 mt-2 mt-md-0">
<div class="col-4">
<a href="{% url 'list_unit_conversion' %}" class="p-0 p-md-1">
<div class="card p-0 no-gutters border-0">
<div class="card-body text-center p-0 no-gutters">
<i class="fas fa-exchange-alt fa-2x"></i>
</div>
<div class="card-body text-break text-center p-0 no-gutters text-muted menu-dropdown-text">
{% trans 'Unit Conversions' %}
</div>
</div>
</a>
</div>
</div> </div>
</div> </div>
</li> </li>

View File

@ -92,7 +92,7 @@ from cookbook.serializer import (AutomationSerializer, BookmarkletImportListSeri
SyncLogSerializer, SyncSerializer, UnitSerializer, SyncLogSerializer, SyncSerializer, UnitSerializer,
UserFileSerializer, UserSerializer, UserPreferenceSerializer, UserFileSerializer, UserSerializer, UserPreferenceSerializer,
UserSpaceSerializer, ViewLogSerializer, AccessTokenSerializer, FoodSimpleSerializer, UserSpaceSerializer, ViewLogSerializer, AccessTokenSerializer, FoodSimpleSerializer,
RecipeExportSerializer, UnitConversionSerializer, NutritionTypeSerializer) RecipeExportSerializer, UnitConversionSerializer, FoodPropertyTypeSerializer)
from cookbook.views.import_export import get_integration from cookbook.views.import_export import get_integration
from recipes import settings from recipes import settings
@ -971,7 +971,7 @@ class UnitConversionViewSet(viewsets.ModelViewSet):
class NutritionTypeViewSet(viewsets.ModelViewSet): class NutritionTypeViewSet(viewsets.ModelViewSet):
queryset = FoodPropertyType.objects queryset = FoodPropertyType.objects
serializer_class = NutritionTypeSerializer serializer_class = FoodPropertyTypeSerializer
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope] permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
def get_queryset(self): def get_queryset(self):

View File

@ -1,43 +1,133 @@
<template> <template>
<div id="app"> <div id="app">
<div class="row" v-if="food">
<div class="col-12">
<div class="row" v-if="recipe !== undefined"> <h2>{{ food.name }} <small class="text-muted" v-if="food.plural_name">{{ food.plural_name }}</small>
<div class="col-6"> </h2>
<div class="card">
<table class="table table-bordered table-sm">
<tr v-for="p in recipe.food_properties" v-bind:key="`id_${p.id}`">
<td>
<button class="btn btn-danger btn-sm"
@click="selected_property = p"><i
class="fas fa-exclamation-triangle"></i>
</button>
{{ p.icon }} {{ p.name }}
</td>
<td>{{ p.total_value }} {{ p.unit }}</td>
</tr>
</table>
</div>
</div> </div>
</div> </div>
<b-modal id="id_modal_property_overview" title="Property Overview" v-model="show_modal" @hidden="selected_property = undefined"> <div class="row">
<template v-if="selected_property !== undefined"> <div class="col-12">
{{ selected_property.description }} <b-form v-if="food">
<table class="table table-bordered"> <b-form-group :label="$t('Name')" description="">
<tr v-for="f in selected_property.food_values" v-bind:key="`id_${selected_property.id}_food_${f.id}`"> <b-form-input v-model="food.name"></b-form-input>
<td>{{ f.food }}</td> </b-form-group>
<td>{{ f.value }} {{ selected_property.unit }}</td> <b-form-group :label="$t('Plural')" description="">
</tr> <b-form-input v-model="food.plural_name"></b-form-input>
</table> </b-form-group>
</template>
<!-- Food properties -->
<h5><i class="fas fa-database"></i> {{ $t('Properties') }} <small class="text-muted">{{ food.name }}</small></h5>
<table class="table table-bordered" v-if="food_properties">
<tr v-for="fp in food_properties" v-bind:key="fp.id">
<td><input v-model="fp.property_amount" type="number"> {{ fp.property_type.unit }}</td>
<td><b> {{ fp.property_type.name }} </b></td>
<td> /</td>
<td><input v-model="fp.food_amount" type="number"> </td>
<td>
<generic-multiselect
@change="fp.food_unit = $event.val;"
:model="Models.UNIT"
:initial_selection="fp.food_unit"
label="name"
:multiple="false"
:placeholder="$t('Unit')"
></generic-multiselect>
</td>
</tr>
</table>
</b-modal> <!-- Unit conversion -->
<!-- ADVANCED FEATURES somehow hide this stuff -->
<b-form-group :label="$t('Recipe')" :description="$t('food_recipe_help')">
<generic-multiselect
@change="food.recipe = $event.val;"
:model="Models.RECIPE"
:initial_selection="food.recipe"
label="name"
:multiple="false"
:placeholder="$t('Recipe')"
></generic-multiselect>
</b-form-group>
<b-form-group :description="$t('OnHand_help')">
<b-form-checkbox v-model="food.food_onhand">{{ $t('OnHand') }}</b-form-checkbox>
</b-form-group>
<b-form-group :description="$t('ignore_shopping_help')">
<b-form-checkbox v-model="food.ignore_shopping">{{ $t('Ignore_Shopping') }}</b-form-checkbox>
</b-form-group>
<b-form-group :label="$t('Shopping_Category')" :description="$t('shopping_category_help')">
<generic-multiselect
@change="food.supermarket_category = $event.val;"
:model="Models.SHOPPING_CATEGORY"
:initial_selection="food.supermarket_category"
label="name"
:multiple="false"
:allow_create="true"
:placeholder="$t('Shopping_Category')"
></generic-multiselect>
</b-form-group>
<hr/>
<!-- todo add conditions if false disable dont hide -->
<b-form-group :label="$t('Substitutes')" :description="$t('substitute_help')">
<generic-multiselect
@change="food.substitute = $event.val;"
:model="Models.FOOD"
:initial_selection="food.substitute"
label="name"
:multiple="false"
:placeholder="$t('Substitutes')"
></generic-multiselect>
</b-form-group>
<b-form-group :description="$t('substitute_siblings_help')">
<b-form-checkbox v-model="food.substitute_siblings">{{
$t('substitute_siblings')
}}
</b-form-checkbox>
</b-form-group>
<b-form-group :label="$t('InheritFields')" :description="$t('InheritFields_help')">
<generic-multiselect
@change="food.inherit_fields = $event.val;"
:model="Models.FOOD_INHERIT_FIELDS"
:initial_selection="food.inherit_fields"
label="name"
:multiple="false"
:placeholder="$t('InheritFields')"
></generic-multiselect>
</b-form-group>
<b-form-group :label="$t('ChildInheritFields')" :description="$t('ChildInheritFields_help')">
<generic-multiselect
@change="food.child_inherit_fields = $event.val;"
:model="Models.FOOD_INHERIT_FIELDS"
:initial_selection="food.child_inherit_fields"
label="name"
:multiple="false"
:placeholder="$t('ChildInheritFields')"
></generic-multiselect>
</b-form-group>
<!-- TODO change to a button -->
<b-form-group :description="$t('reset_children_help')">
<b-form-checkbox v-model="food.reset_inherit">{{ $t('reset_children') }}</b-form-checkbox>
</b-form-group>
<b-button variant="primary" @click="updateFood">{{ $t('Save') }}</b-button>
</b-form>
</div>
</div>
</div> </div>
</template> </template>
@ -49,8 +139,9 @@ import {BootstrapVue} from "bootstrap-vue"
import "bootstrap-vue/dist/bootstrap-vue.css" import "bootstrap-vue/dist/bootstrap-vue.css"
import {ApiApiFactory} from "@/utils/openapi/api"; import {ApiApiFactory} from "@/utils/openapi/api";
import RecipeCard from "@/components/RecipeCard.vue";
import {ApiMixin} from "@/utils/utils"; import GenericMultiselect from "@/components/GenericMultiselect.vue";
import {ApiMixin, StandardToasts} from "@/utils/utils";
Vue.use(BootstrapVue) Vue.use(BootstrapVue)
@ -59,31 +150,48 @@ Vue.use(BootstrapVue)
export default { export default {
name: "TestView", name: "TestView",
mixins: [ApiMixin], mixins: [ApiMixin],
components: {}, components: {
GenericMultiselect
},
data() { data() {
return { return {
recipe: undefined, food: undefined,
selected_property: undefined, food_properties: [],
} }
}, },
computed: {
show_modal: function () {
return this.selected_property !== undefined
},
},
mounted() { mounted() {
this.$i18n.locale = window.CUSTOM_LOCALE this.$i18n.locale = window.CUSTOM_LOCALE
let apiClient = new ApiApiFactory() let apiClient = new ApiApiFactory()
apiClient.retrieveRecipe('1').then((r) => { apiClient.retrieveFood('1').then((r) => {
this.recipe = r.data this.food = r.data
}) })
apiClient.listFoodPropertyTypes().then((r) => {
r.data.forEach((fp) => {
this.food_properties.push({
'food_amount': 0,
'food_unit': null,
'food': this.food,
'property_amount': 0,
'property_type': fp,
})
})
})
},
methods: {
updateFood: function () {
let apiClient = new ApiApiFactory()
apiClient.updateFood(this.food.id, this.food).then((r) => {
this.food = r.data
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE)
}).catch(err => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
})
}
}, },
methods: {},
} }
</script> </script>
<style> <style>
</style> </style>

View File

@ -2,8 +2,8 @@
<div> <div>
<div class="card" v-if="recipe !== undefined"> <div class="card p-2" v-if="recipe !== undefined" >
<h5>Properties</h5> <h5><i class="fas fa-database"></i> {{$t('Properties')}}</h5>
<table class="table table-bordered table-sm"> <table class="table table-bordered table-sm">
<tr v-for="p in recipe.food_properties" v-bind:key="`id_${p.id}`"> <tr v-for="p in recipe.food_properties" v-bind:key="`id_${p.id}`">

View File

@ -168,6 +168,7 @@
"create_title": "New {type}", "create_title": "New {type}",
"edit_title": "Edit {type}", "edit_title": "Edit {type}",
"Name": "Name", "Name": "Name",
"Properties": "Properties",
"Type": "Type", "Type": "Type",
"Description": "Description", "Description": "Description",
"Recipe": "Recipe", "Recipe": "Recipe",

View File

@ -595,7 +595,7 @@ export class Models {
form_field: true, form_field: true,
type: "text", type: "text",
field: "converted_amount", field: "converted_amount",
label: "base_amount", label: "converted_amount",
placeholder: "", placeholder: "",
}, },
converted_unit: { converted_unit: {