added first draft of property editor
This commit is contained in:
18
cookbook/migrations/0204_propertytype_fdc_id.py
Normal file
18
cookbook/migrations/0204_propertytype_fdc_id.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
@ -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?
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
@ -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>
|
||||||
|
@ -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",
|
||||||
|
@ -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,
|
||||||
|
Reference in New Issue
Block a user