From 6b1217ec354fdad09f448ed092b1cef0d02b4e7a Mon Sep 17 00:00:00 2001 From: vabene1111 Date: Fri, 22 Aug 2025 16:58:12 +0200 Subject: [PATCH] first draft of a hirarchy editor --- cookbook/serializer.py | 2 +- cookbook/views/api.py | 17 +- .../components/display/ShoppingLineItem.vue | 4 +- .../components/display/ShoppingListView.vue | 5 +- .../src/components/inputs/HierarchyEditor.vue | 178 ++++++++++ .../components/model_editors/FoodEditor.vue | 30 +- .../model_editors/KeywordEditor.vue | 31 +- vue3/src/locales/ar.json | 3 + vue3/src/locales/bg.json | 3 + vue3/src/locales/ca.json | 3 + vue3/src/locales/cs.json | 3 + vue3/src/locales/da.json | 3 + vue3/src/locales/de.json | 3 + vue3/src/locales/el.json | 3 + vue3/src/locales/en.json | 3 + vue3/src/locales/es.json | 3 + vue3/src/locales/fi.json | 3 + vue3/src/locales/fr.json | 3 + vue3/src/locales/he.json | 3 + vue3/src/locales/hr.json | 3 + vue3/src/locales/hu.json | 3 + vue3/src/locales/hy.json | 3 + vue3/src/locales/id.json | 3 + vue3/src/locales/is.json | 3 + vue3/src/locales/it.json | 3 + vue3/src/locales/lt.json | 3 + vue3/src/locales/lv.json | 3 + vue3/src/locales/nb_NO.json | 3 + vue3/src/locales/nl.json | 3 + vue3/src/locales/pl.json | 3 + vue3/src/locales/pt.json | 3 + vue3/src/locales/pt_BR.json | 3 + vue3/src/locales/ro.json | 3 + vue3/src/locales/ru.json | 3 + vue3/src/locales/sl.json | 3 + vue3/src/locales/sv.json | 3 + vue3/src/locales/tr.json | 3 + vue3/src/locales/uk.json | 3 + vue3/src/locales/zh_Hans.json | 3 + vue3/src/locales/zh_Hant.json | 3 + vue3/src/openapi/.openapi-generator/FILES | 3 + vue3/src/openapi/apis/ApiApi.ts | 305 ++++++++++++++++++ vue3/src/openapi/models/EnterpriseSpace.ts | 70 ++++ .../models/PaginatedEnterpriseSpaceList.ts | 101 ++++++ .../openapi/models/PatchedEnterpriseSpace.ts | 68 ++++ vue3/src/openapi/models/index.ts | 3 + vue3/src/pages/TestPage.vue | 69 ---- vue3/src/types/Models.ts | 22 +- 48 files changed, 913 insertions(+), 94 deletions(-) create mode 100644 vue3/src/components/inputs/HierarchyEditor.vue create mode 100644 vue3/src/openapi/models/EnterpriseSpace.ts create mode 100644 vue3/src/openapi/models/PaginatedEnterpriseSpaceList.ts create mode 100644 vue3/src/openapi/models/PatchedEnterpriseSpace.ts diff --git a/cookbook/serializer.py b/cookbook/serializer.py index a05fdab18..df6d4c117 100644 --- a/cookbook/serializer.py +++ b/cookbook/serializer.py @@ -592,7 +592,7 @@ class KeywordSerializer(UniqueFieldsMixin, ExtendedRecipeMixin): fields = ( 'id', 'name', 'label', 'description', 'image', 'parent', 'numchild', 'numrecipe', 'created_at', 'updated_at', 'full_name') - read_only_fields = ('id', 'label', 'numchild', 'parent', 'image') + read_only_fields = ('id', 'label', 'numchild', 'numrecipe', 'parent', 'image') class UnitSerializer(UniqueFieldsMixin, ExtendedRecipeMixin, OpenDataModelMixin): diff --git a/cookbook/views/api.py b/cookbook/views/api.py index 689256e1b..a524ba2fe 100644 --- a/cookbook/views/api.py +++ b/cookbook/views/api.py @@ -411,6 +411,7 @@ class MergeMixin(ViewSetMixin): description='Return first level children of {obj} with ID [int]. Integer 0 will return root {obj}s.', type=int), OpenApiParameter(name='tree', description='Return all self and children of {obj} with ID [int].', type=int), + OpenApiParameter(name='root_tree', description='Return all items belonging to the tree of the given {obj} id', type=int), ]), move=extend_schema(parameters=[ OpenApiParameter(name="parent", description='The ID of the desired parent of the {obj}.', type=OpenApiTypes.INT, @@ -423,6 +424,7 @@ class TreeMixin(MergeMixin, FuzzyFilterMixin): def get_queryset(self): root = self.request.query_params.get('root', None) tree = self.request.query_params.get('tree', None) + root_tree = self.request.query_params.get('root_tree', None) if root: if root.isnumeric(): @@ -441,10 +443,23 @@ class TreeMixin(MergeMixin, FuzzyFilterMixin): self.queryset = self.model.objects.get(id=int(tree)).get_descendants_and_self() except self.model.DoesNotExist: self.queryset = self.model.objects.none() + elif root_tree: + if root_tree.isnumeric(): + try: + self.queryset = self.model.objects.get(id=int(root_tree)).get_root().get_descendants_and_self() + except self.model.DoesNotExist: + self.queryset = self.model.objects.none() + else: return self.annotate_recipe(queryset=super().get_queryset(), request=self.request, serializer=self.serializer_class, tree=True) - self.queryset = self.queryset.filter(space=self.request.space).order_by(Lower('name').asc()) + + + self.queryset = self.queryset.filter(space=self.request.space) + # only order if not root_tree or tree mde because in these modes the sorting is relevant for the client + if not root_tree and not tree: + self.queryset = self.queryset.order_by(Lower('name').asc()) + return self.annotate_recipe(queryset=self.queryset, request=self.request, serializer=self.serializer_class, tree=True) diff --git a/vue3/src/components/display/ShoppingLineItem.vue b/vue3/src/components/display/ShoppingLineItem.vue index 188c316a5..dad0e37fc 100644 --- a/vue3/src/components/display/ShoppingLineItem.vue +++ b/vue3/src/components/display/ShoppingLineItem.vue @@ -15,8 +15,8 @@ - {{ $n(a.amount) }} - {{ a.unit.name }} + {{ $n(a.amount) }} + {{ a.unit.name }} diff --git a/vue3/src/components/display/ShoppingListView.vue b/vue3/src/components/display/ShoppingListView.vue index c9ac8934a..88afeb773 100644 --- a/vue3/src/components/display/ShoppingListView.vue +++ b/vue3/src/components/display/ShoppingListView.vue @@ -123,6 +123,8 @@ + + @@ -180,7 +182,8 @@ diff --git a/vue3/src/components/inputs/HierarchyEditor.vue b/vue3/src/components/inputs/HierarchyEditor.vue new file mode 100644 index 000000000..57acc51f9 --- /dev/null +++ b/vue3/src/components/inputs/HierarchyEditor.vue @@ -0,0 +1,178 @@ + + + + + \ No newline at end of file diff --git a/vue3/src/components/model_editors/FoodEditor.vue b/vue3/src/components/model_editors/FoodEditor.vue index 01360ad57..d07e89a1f 100644 --- a/vue3/src/components/model_editors/FoodEditor.vue +++ b/vue3/src/components/model_editors/FoodEditor.vue @@ -15,6 +15,7 @@ {{ $t('Food') }} {{ $t('Properties') }} {{ $t('Conversion') }} + {{ $t('Hierarchy') }} {{ $t('Miscellaneous') }} @@ -83,7 +84,7 @@ - + @@ -97,7 +98,7 @@ - + @@ -105,7 +106,19 @@ - + + + + + + + + + + + @@ -117,14 +130,6 @@ - - - - - - @@ -154,6 +159,7 @@ import {useUserPreferenceStore} from "@/stores/UserPreferenceStore"; import FdcSearchDialog from "@/components/dialogs/FdcSearchDialog.vue"; import {openFdcPage} from "@/utils/fdc.ts"; import {DateTime} from "luxon"; +import HierarchyEditor from "@/components/inputs/HierarchyEditor.vue"; const props = defineProps({ @@ -212,7 +218,7 @@ onMounted(() => { /** * component specific state setup logic */ -function initializeEditor(){ +function initializeEditor() { setupState(props.item, props.itemId, { newItemFunction: () => { editingObj.value.propertiesFoodAmount = 100 diff --git a/vue3/src/components/model_editors/KeywordEditor.vue b/vue3/src/components/model_editors/KeywordEditor.vue index 9e5694ee8..333639d17 100644 --- a/vue3/src/components/model_editors/KeywordEditor.vue +++ b/vue3/src/components/model_editors/KeywordEditor.vue @@ -9,13 +9,29 @@ :is-changed="editingObjChanged" :model-class="modelClass" :object-name="editingObjName()"> + + + + {{ $t('Keyword') }} + {{ $t('Hierarchy') }} + + + + - + + + - - + + - + + + + + + @@ -23,10 +39,11 @@ diff --git a/vue3/src/types/Models.ts b/vue3/src/types/Models.ts index 9a83278a8..c29ad9521 100644 --- a/vue3/src/types/Models.ts +++ b/vue3/src/types/Models.ts @@ -1,6 +1,6 @@ import { AccessToken, - ApiApi, Automation, type AutomationTypeEnum, ConnectorConfig, CookLog, CustomFilter, + ApiApi, ApiKeywordMoveUpdateRequest, Automation, type AutomationTypeEnum, ConnectorConfig, CookLog, CustomFilter, Food, Ingredient, InviteLink, Keyword, @@ -85,7 +85,7 @@ type ModelTableHeaders = { * custom type containing all attributes needed by the generic model system to properly handle all functions */ export type Model = { - name: string, + name: EditorSupportedModels, localizationKey: string, localizationKeyDescription: string, icon: string, @@ -191,6 +191,7 @@ export const TFood = { isPaginated: true, isMerge: true, + isTree: true, mergeAutomation: 'FOOD_ALIAS', toStringKeys: ['name'], @@ -234,6 +235,7 @@ export const TKeyword = { isPaginated: true, isMerge: true, + isTree: true, mergeAutomation: 'KEYWORD_ALIAS', toStringKeys: ['name'], @@ -949,6 +951,22 @@ export class GenericModel { } } + /** + * move the given source object so that its parent is the given parentId. + * @param source object to change parent for + * @param parentId parent id to change the object to or 0 to remove parent + */ + move(source: EditorSupportedTypes, parentId: number) { + if (!this.model.isTree) { + throw new Error('This model does not support trees!') + } else { + let moveRequestParams: any = {id: source.id, parent: parentId} + moveRequestParams[this.model.name.charAt(0).toLowerCase() + this.model.name.slice(1)] = source + + return this.api[`api${this.model.name}MoveUpdate`](moveRequestParams) + } + } + /** * gets a label for a specific object instance using the model toStringKeys property * @param obj obj to get label for