added first draft of property editor

This commit is contained in:
vabene1111
2023-11-27 22:20:09 +01:00
parent 977d2822bc
commit 9c74730461
6 changed files with 114 additions and 61 deletions

View File

@ -0,0 +1,18 @@
# Generated by Django 4.2.7 on 2023-11-27 21:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0203_alter_unique_contstraints'),
]
operations = [
migrations.AddField(
model_name='propertytype',
name='fdc_id',
field=models.CharField(blank=True, default=None, max_length=128, null=True),
),
]

View File

@ -767,6 +767,7 @@ class PropertyType(models.Model, PermissionModelMixin):
(PRICE, _('Price')), (GOAL, _('Goal')), (OTHER, _('Other'))), null=True, blank=True) (PRICE, _('Price')), (GOAL, _('Goal')), (OTHER, _('Other'))), null=True, blank=True)
open_data_slug = models.CharField(max_length=128, null=True, blank=True, default=None) open_data_slug = models.CharField(max_length=128, null=True, blank=True, default=None)
fdc_id = models.CharField(max_length=128, null=True, blank=True, default=None)
# TODO show if empty property? # TODO show if empty property?
# TODO formatting property? # TODO formatting property?

View File

@ -533,7 +533,7 @@ class PropertyTypeSerializer(OpenDataModelMixin, WritableNestedModelSerializer,
class Meta: class Meta:
model = PropertyType model = PropertyType
fields = ('id', 'name', 'unit', 'description', 'order', 'open_data_slug') fields = ('id', 'name', 'unit', 'description', 'order', 'open_data_slug', 'fdc_id',)
class PropertySerializer(UniqueFieldsMixin, WritableNestedModelSerializer): class PropertySerializer(UniqueFieldsMixin, WritableNestedModelSerializer):
@ -687,7 +687,7 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedR
model = Food model = Food
fields = ( fields = (
'id', 'name', 'plural_name', 'description', 'shopping', 'recipe', 'url', 'id', 'name', 'plural_name', 'description', 'shopping', 'recipe', 'url',
'properties', 'properties_food_amount', 'properties_food_unit', 'properties', 'properties_food_amount', 'properties_food_unit', 'fdc_id',
'food_onhand', 'supermarket_category', 'food_onhand', 'supermarket_category',
'image', 'parent', 'numchild', 'numrecipe', 'inherit_fields', 'full_name', 'ignore_shopping', 'image', 'parent', 'numchild', 'numrecipe', 'inherit_fields', 'full_name', 'ignore_shopping',
'substitute', 'substitute_siblings', 'substitute_children', 'substitute_onhand', 'child_inherit_fields', 'open_data_slug', 'substitute', 'substitute_siblings', 'substitute_children', 'substitute_onhand', 'child_inherit_fields', 'open_data_slug',
@ -1031,10 +1031,10 @@ class ShoppingListRecipeSerializer(serializers.ModelSerializer):
value = value.quantize( value = value.quantize(
Decimal(1)) if value == value.to_integral() else value.normalize() # strips trailing zero Decimal(1)) if value == value.to_integral() else value.normalize() # strips trailing zero
return ( return (
obj.name obj.name
or getattr(obj.mealplan, 'title', None) or getattr(obj.mealplan, 'title', None)
or (d := getattr(obj.mealplan, 'date', None)) and ': '.join([obj.mealplan.recipe.name, str(d)]) or (d := getattr(obj.mealplan, 'date', None)) and ': '.join([obj.mealplan.recipe.name, str(d)])
or obj.recipe.name or obj.recipe.name
) + f' ({value:.2g})' ) + f' ({value:.2g})'
def update(self, instance, validated_data): def update(self, instance, validated_data):

View File

@ -1,40 +1,35 @@
<template> <template>
<div id="app"> <div id="app">
<div>
<h2 v-if="recipe">{{ recipe.name}}</h2>
<beta-warning></beta-warning> <table class="table table-sm table-bordered">
<thead>
<div v-if="metadata !== undefined"> <tr>
{{ $t('Data_Import_Info') }} <td>{{ $t('Name') }}</td>
<td v-for="pt in property_types" v-bind:key="pt.id">{{ pt.name }}
<input type="text" v-model="pt.unit" @change="updatePropertyType(pt)">
<select class="form-control" v-model="selected_version"> <input v-model="pt.fdc_id" type="number" placeholder="FDC ID" @change="updatePropertyType(pt)"></td>
<option v-for="v in metadata.versions" v-bind:key="v">{{ v }}</option> </tr>
</select> </thead>
<tbody>
<b-checkbox v-model="update_existing" class="mt-1">{{ $t('Update_Existing_Data') }}</b-checkbox> <tr v-for="f in this.foods" v-bind:key="f.food.id">
<b-checkbox v-model="use_metric" class="mt-1">{{ $t('Use_Metric') }}</b-checkbox> <td>
{{ f.food.name }}
{{ $t('Property') }} / <input type="number" v-model="f.food.properties_food_amount" @change="updateFood(f.food)">
<div v-if="selected_version !== undefined" class="mt-3"> <generic-multiselect
<table class="table"> @change="f.food.properties_food_unit = $event.val; updateFood(f.food)"
<tr> :initial_selection="f.food.properties_food_unit"
<th>{{ $t('Datatype') }}</th> label="name" :model="Models.UNIT"
<th>{{ $t('Number of Objects') }}</th> :multiple="false"/>
<th>{{ $t('Imported') }}</th> <input v-model="f.food.fdc_id" placeholder="FDC ID">
</tr> <button>Load FDC</button>
<tr v-for="d in metadata.datatypes" v-bind:key="d"> </td>
<td>{{ $t(d.charAt(0).toUpperCase() + d.slice(1)) }}</td> <td v-for="p in f.properties" v-bind:key="`${f.id}_${p.property_type.id}`"><input type="number" v-model="p.property_amount"> {{ p.property_type.unit }}</td>
<td>{{ metadata[selected_version][d] }}</td> </tr>
<td> </tbody>
<template v-if="import_count !== undefined">{{ import_count[d] }}</template> </table>
</td>
</tr>
</table>
<button class="btn btn-success" @click="doImport">{{ $t('Import') }}</button>
</div>
</div> </div>
</div> </div>
</template> </template>
@ -48,6 +43,8 @@ import "bootstrap-vue/dist/bootstrap-vue.css"
import {ApiMixin, resolveDjangoUrl, StandardToasts} from "@/utils/utils"; import {ApiMixin, resolveDjangoUrl, StandardToasts} from "@/utils/utils";
import axios from "axios"; import axios from "axios";
import BetaWarning from "@/components/BetaWarning.vue"; import BetaWarning from "@/components/BetaWarning.vue";
import {ApiApiFactory} from "@/utils/openapi/api";
import GenericMultiselect from "@/components/GenericMultiselect.vue";
Vue.use(BootstrapVue) Vue.use(BootstrapVue)
@ -56,39 +53,66 @@ Vue.use(BootstrapVue)
export default { export default {
name: "TestView", name: "TestView",
mixins: [ApiMixin], mixins: [ApiMixin],
components: {BetaWarning}, components: {GenericMultiselect},
computed: {
foods: function () {
let foods = []
if (this.recipe !== null && this.property_types !== []) {
this.recipe.steps.forEach(s => {
s.ingredients.forEach(i => {
let food = {food: i.food, properties: {}}
this.property_types.forEach(pt => {
food.properties[pt.id] = {changed: false, property_amount: 0, property_type: pt}
})
i.food.properties.forEach(fp => {
food.properties[fp.property_type.id] = {changed: false, property_amount: fp.property_amount, property_type: fp.property_type}
})
foods.push(food)
})
})
}
return foods
}
},
data() { data() {
return { return {
metadata: undefined, recipe: null,
selected_version: undefined, property_types: []
update_existing: true,
use_metric: true,
import_count: undefined,
} }
}, },
mounted() { mounted() {
this.$i18n.locale = window.CUSTOM_LOCALE this.$i18n.locale = window.CUSTOM_LOCALE
axios.get(resolveDjangoUrl('api_import_open_data')).then(r => { let apiClient = new ApiApiFactory()
this.metadata = r.data
}).catch(err => { apiClient.retrieveRecipe("112").then(result => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_FETCH, err) this.recipe = result.data
})
apiClient.listPropertyTypes().then(result => {
this.property_types = result.data
}) })
}, },
methods: { methods: {
doImport: function () { updateFood: function (food) {
axios.post(resolveDjangoUrl('api_import_open_data'), { let apiClient = new ApiApiFactory()
'selected_version': this.selected_version, apiClient.partialUpdateFood(food.id, food).then(result => {
'selected_datatypes': this.metadata.datatypes, //TODO handle properly
'update_existing': this.update_existing, StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE)
'use_metric': this.use_metric, }).catch((err) => {
}).then(r => { StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_CREATE)
this.import_count = r.data
}).catch(err => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_CREATE, err)
}) })
}, },
updatePropertyType: function (pt) {
let apiClient = new ApiApiFactory()
apiClient.partialUpdatePropertyType(pt.id, pt).then(result => {
//TODO handle properly
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE)
}).catch((err) => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
})
}
}, },
} }
</script> </script>

View File

@ -80,6 +80,8 @@
"open_data_help_text": "The Tandoor Open Data project provides community contributed data for Tandoor. This field is filled automatically when importing it and allows updates in the future.", "open_data_help_text": "The Tandoor Open Data project provides community contributed data for Tandoor. This field is filled automatically when importing it and allows updates in the future.",
"Open_Data_Slug": "Open Data Slug", "Open_Data_Slug": "Open Data Slug",
"Open_Data_Import": "Open Data Import", "Open_Data_Import": "Open Data Import",
"FDC_ID": "FDC ID",
"FDC_ID_help": "FDC database ID",
"Data_Import_Info": "Enhance your Space by importing a community curated list of foods, units and more to improve your recipe collection.", "Data_Import_Info": "Enhance your Space by importing a community curated list of foods, units and more to improve your recipe collection.",
"Update_Existing_Data": "Update Existing Data", "Update_Existing_Data": "Update Existing Data",
"Use_Metric": "Use Metric Units", "Use_Metric": "Use Metric Units",

View File

@ -733,8 +733,16 @@ export class Models {
field: "order", field: "order",
label: "Order", label: "Order",
placeholder: "", placeholder: "",
optional: false,
help_text: "OrderInformation",
},
fdc_id: {
form_field: true,
type: "text",
field: "fdc_id",
label: "FDC_ID",
help_text: "FDC_ID_help",
optional: true, optional: true,
helpt_text: "OrderInformation",
}, },
open_data_slug: { open_data_slug: {
form_field: true, form_field: true,