foundations of recipe batch editing

This commit is contained in:
vabene1111
2025-08-20 22:55:38 +02:00
parent 5e1c804fd1
commit 98b57d2854
12 changed files with 424 additions and 269 deletions

View File

@@ -1112,6 +1112,12 @@ class RecipeImportSerializer(SpacedModelSerializer):
fields = '__all__'
class RecipeBatchUpdateSerializer(serializers.Serializer):
recipes = serializers.ListField(child=serializers.IntegerField())
keywords_add = serializers.ListField(child=serializers.IntegerField())
keywords_remove = serializers.ListField(child=serializers.IntegerField())
class CustomFilterSerializer(SpacedModelSerializer, WritableNestedModelSerializer):
shared = UserSerializer(many=True, required=False)
@@ -1480,7 +1486,7 @@ class InviteLinkSerializer(WritableNestedModelSerializer):
fields = (
'id', 'uuid', 'email', 'group', 'valid_until', 'used_by', 'reusable', 'internal_note', 'created_by',
'created_at',)
read_only_fields = ('id', 'uuid', 'used_by' ,'created_by', 'created_at',)
read_only_fields = ('id', 'uuid', 'used_by', 'created_by', 'created_at',)
# CORS, REST and Scopes aren't currently working
@@ -1770,6 +1776,7 @@ class AiImportSerializer(serializers.Serializer):
text = serializers.CharField(allow_null=True, allow_blank=True)
recipe_id = serializers.CharField(allow_null=True, allow_blank=True)
class ExportRequestSerializer(serializers.Serializer):
type = serializers.CharField()
all = serializers.BooleanField(default=False)

View File

@@ -125,10 +125,6 @@ urlpatterns = [
]
if DEBUG:
urlpatterns.append(path('test/', views.test, name='view_test'))
urlpatterns.append(path('test2/', views.test2, name='view_test2'))
# catchall view for new frontend
urlpatterns += [
path('', views.index, name='index'),

View File

@@ -110,7 +110,7 @@ from cookbook.serializer import (AccessTokenSerializer, AutomationSerializer, Au
UserSerializer, UserSpaceSerializer, ViewLogSerializer,
LocalizationSerializer, ServerSettingsSerializer, RecipeFromSourceResponseSerializer, ShoppingListEntryBulkCreateSerializer, FdcQuerySerializer,
AiImportSerializer, ImportOpenDataSerializer, ImportOpenDataMetaDataSerializer, ImportOpenDataResponseSerializer, ExportRequestSerializer,
RecipeImportSerializer, ConnectorConfigSerializer, SearchPreferenceSerializer, SearchFieldsSerializer
RecipeImportSerializer, ConnectorConfigSerializer, SearchPreferenceSerializer, SearchFieldsSerializer, RecipeBatchUpdateSerializer
)
from cookbook.version_info import TANDOOR_VERSION
from cookbook.views.import_export import get_integration
@@ -1359,6 +1359,29 @@ class RecipeViewSet(LoggingMixin, viewsets.ModelViewSet):
return Response(self.serializer_class(qs, many=True).data)
@decorators.action(detail=False, methods=['PUT'], serializer_class=RecipeBatchUpdateSerializer)
def batch_update(self, request):
serializer = self.serializer_class(data=request.data, partial=True)
if serializer.is_valid():
recipes = Recipe.objects.filter(id__in=serializer.validated_data['recipes'], space=self.request.space)
safe_recipe_ids = Recipe.objects.filter(id__in=serializer.validated_data['recipes'], space=self.request.space).values_list('id', flat=True)
if 'keywords_add' in serializer.validated_data:
keyword_relations = []
for r in recipes:
for k in serializer.validated_data['keywords_add']:
keyword_relations.append(Recipe.keywords.through(recipe_id=r.pk, keyword_id=k))
Recipe.keywords.through.objects.bulk_create(keyword_relations, ignore_conflicts=True, unique_fields=('recipe_id', 'keyword_id',))
if 'keywords_remove' in serializer.validated_data:
for k in serializer.validated_data['keywords_remove']:
Recipe.keywords.through.objects.filter(recipe_id__in=safe_recipe_ids,keyword_id=k).delete()
return Response({}, 200)
return Response(serializer.errors, 400)
@extend_schema_view(list=extend_schema(
parameters=[OpenApiParameter(name='food_id', description='ID of food to filter for', type=int), ]))

View File

@@ -0,0 +1,85 @@
<template>
<v-dialog max-width="600px" :activator="props.activator" v-model="dialog">
<v-card :loading="loading">
<v-closable-card-title
:title="$t('delete_title', {type: $t(genericModel.model.localizationKey)})"
:sub-title="genericModel.getLabel(props.source)"
:icon="genericModel.model.icon"
v-model="dialog"
></v-closable-card-title>
<v-divider></v-divider>
<v-card-text>
</v-card-text>
<v-card-actions>
<v-btn :disabled="loading" @click="dialog = false">{{ $t('Cancel') }}</v-btn>
<v-btn color="warning" :loading="loading">{{ $t('Update') }}</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script setup lang="ts">
import {onMounted, PropType, ref, watch} from "vue";
import {EditorSupportedModels, EditorSupportedTypes, getGenericModelFromString} from "@/types/Models.ts";
import VClosableCardTitle from "@/components/dialogs/VClosableCardTitle.vue";
import {useI18n} from "vue-i18n";
const emit = defineEmits(['change'])
const props = defineProps({
model: {type: String as PropType<EditorSupportedModels>, required: true},
items: {type: Array as PropType<Array<EditorSupportedTypes>>, required: true},
activator: {type: String, default: 'parent'},
})
const {t} = useI18n()
const dialog = defineModel<boolean>({default: false})
const loading = ref(false)
const genericModel = getGenericModelFromString(props.model, t)
const itemsToDelete = ref<EditorSupportedTypes[]>([])
const failedItems = ref<EditorSupportedTypes[]>([])
const updatedItems = ref<EditorSupportedTypes[]>([])
watch(dialog, (newValue, oldValue) => {
if(!oldValue && newValue){
itemsToDelete.value = JSON.parse(JSON.stringify(props.items))
}
})
/**
* loop through the items and delete them
*/
function deleteAll() {
let promises: Promise<any>[] = []
loading.value = true
itemsToDelete.value.forEach(item => {
promises.push(genericModel.destroy(item.id!).then((r: any) => {
updatedItems.value.push(item)
}).catch((err: any) => {
failedItems.value.push(item)
}))
})
Promise.allSettled(promises).then(() => {
loading.value = false
emit('change')
})
}
</script>
<style scoped>
</style>

View File

@@ -71,13 +71,6 @@ models/PaginatedInviteLinkList.ts
models/PaginatedKeywordList.ts
models/PaginatedMealPlanList.ts
models/PaginatedMealTypeList.ts
models/PaginatedOpenDataCategoryList.ts
models/PaginatedOpenDataConversionList.ts
models/PaginatedOpenDataFoodList.ts
models/PaginatedOpenDataPropertyList.ts
models/PaginatedOpenDataStoreList.ts
models/PaginatedOpenDataUnitList.ts
models/PaginatedOpenDataVersionList.ts
models/PaginatedPropertyList.ts
models/PaginatedPropertyTypeList.ts
models/PaginatedRecipeBookEntryList.ts
@@ -147,6 +140,7 @@ models/PatchedViewLog.ts
models/Property.ts
models/PropertyType.ts
models/Recipe.ts
models/RecipeBatchUpdate.ts
models/RecipeBook.ts
models/RecipeBookEntry.ts
models/RecipeFlat.ts

View File

@@ -63,13 +63,6 @@ import type {
PaginatedKeywordList,
PaginatedMealPlanList,
PaginatedMealTypeList,
PaginatedOpenDataCategoryList,
PaginatedOpenDataConversionList,
PaginatedOpenDataFoodList,
PaginatedOpenDataPropertyList,
PaginatedOpenDataStoreList,
PaginatedOpenDataUnitList,
PaginatedOpenDataVersionList,
PaginatedPropertyList,
PaginatedPropertyTypeList,
PaginatedRecipeBookEntryList,
@@ -139,6 +132,7 @@ import type {
Property,
PropertyType,
Recipe,
RecipeBatchUpdate,
RecipeBook,
RecipeBookEntry,
RecipeFlat,
@@ -269,20 +263,6 @@ import {
PaginatedMealPlanListToJSON,
PaginatedMealTypeListFromJSON,
PaginatedMealTypeListToJSON,
PaginatedOpenDataCategoryListFromJSON,
PaginatedOpenDataCategoryListToJSON,
PaginatedOpenDataConversionListFromJSON,
PaginatedOpenDataConversionListToJSON,
PaginatedOpenDataFoodListFromJSON,
PaginatedOpenDataFoodListToJSON,
PaginatedOpenDataPropertyListFromJSON,
PaginatedOpenDataPropertyListToJSON,
PaginatedOpenDataStoreListFromJSON,
PaginatedOpenDataStoreListToJSON,
PaginatedOpenDataUnitListFromJSON,
PaginatedOpenDataUnitListToJSON,
PaginatedOpenDataVersionListFromJSON,
PaginatedOpenDataVersionListToJSON,
PaginatedPropertyListFromJSON,
PaginatedPropertyListToJSON,
PaginatedPropertyTypeListFromJSON,
@@ -421,6 +401,8 @@ import {
PropertyTypeToJSON,
RecipeFromJSON,
RecipeToJSON,
RecipeBatchUpdateFromJSON,
RecipeBatchUpdateToJSON,
RecipeBookFromJSON,
RecipeBookToJSON,
RecipeBookEntryFromJSON,
@@ -738,6 +720,10 @@ export interface ApiEnterpriseSocialKeywordUpdateRequest {
keyword: Omit<Keyword, 'label'|'parent'|'numchild'|'createdAt'|'updatedAt'|'fullName'>;
}
export interface ApiEnterpriseSocialRecipeBatchUpdateUpdateRequest {
recipeBatchUpdate: RecipeBatchUpdate;
}
export interface ApiEnterpriseSocialRecipeCreateRequest {
recipe: Omit<Recipe, 'image'|'createdBy'|'createdAt'|'updatedAt'|'foodProperties'|'rating'|'lastCooked'>;
}
@@ -1151,11 +1137,6 @@ export interface ApiOpenDataCategoryDestroyRequest {
id: number;
}
export interface ApiOpenDataCategoryListRequest {
page?: number;
pageSize?: number;
}
export interface ApiOpenDataCategoryPartialUpdateRequest {
id: number;
patchedOpenDataCategory?: Omit<PatchedOpenDataCategory, 'createdBy'>;
@@ -1178,11 +1159,6 @@ export interface ApiOpenDataConversionDestroyRequest {
id: number;
}
export interface ApiOpenDataConversionListRequest {
page?: number;
pageSize?: number;
}
export interface ApiOpenDataConversionPartialUpdateRequest {
id: number;
patchedOpenDataConversion?: Omit<PatchedOpenDataConversion, 'createdBy'>;
@@ -1209,16 +1185,6 @@ export interface ApiOpenDataFoodDestroyRequest {
id: number;
}
export interface ApiOpenDataFoodFdcCreateRequest {
id: number;
openDataFood: Omit<OpenDataFood, 'createdBy'>;
}
export interface ApiOpenDataFoodListRequest {
page?: number;
pageSize?: number;
}
export interface ApiOpenDataFoodPartialUpdateRequest {
id: number;
patchedOpenDataFood?: Omit<PatchedOpenDataFood, 'createdBy'>;
@@ -1241,11 +1207,6 @@ export interface ApiOpenDataPropertyDestroyRequest {
id: number;
}
export interface ApiOpenDataPropertyListRequest {
page?: number;
pageSize?: number;
}
export interface ApiOpenDataPropertyPartialUpdateRequest {
id: number;
patchedOpenDataProperty?: Omit<PatchedOpenDataProperty, 'createdBy'>;
@@ -1268,11 +1229,6 @@ export interface ApiOpenDataStoreDestroyRequest {
id: number;
}
export interface ApiOpenDataStoreListRequest {
page?: number;
pageSize?: number;
}
export interface ApiOpenDataStorePartialUpdateRequest {
id: number;
patchedOpenDataStore?: Omit<PatchedOpenDataStore, 'createdBy'>;
@@ -1295,11 +1251,6 @@ export interface ApiOpenDataUnitDestroyRequest {
id: number;
}
export interface ApiOpenDataUnitListRequest {
page?: number;
pageSize?: number;
}
export interface ApiOpenDataUnitPartialUpdateRequest {
id: number;
patchedOpenDataUnit?: Omit<PatchedOpenDataUnit, 'createdBy'>;
@@ -1322,11 +1273,6 @@ export interface ApiOpenDataVersionDestroyRequest {
id: number;
}
export interface ApiOpenDataVersionListRequest {
page?: number;
pageSize?: number;
}
export interface ApiOpenDataVersionPartialUpdateRequest {
id: number;
patchedOpenDataVersion?: PatchedOpenDataVersion;
@@ -1396,6 +1342,10 @@ export interface ApiPropertyUpdateRequest {
property: Property;
}
export interface ApiRecipeBatchUpdateUpdateRequest {
recipeBatchUpdate: RecipeBatchUpdate;
}
export interface ApiRecipeBookCreateRequest {
recipeBook: Omit<RecipeBook, 'createdBy'>;
}
@@ -4276,6 +4226,46 @@ export class ApiApi extends runtime.BaseAPI {
return await response.value();
}
/**
* logs request counts to redis cache total/per user/
*/
async apiEnterpriseSocialRecipeBatchUpdateUpdateRaw(requestParameters: ApiEnterpriseSocialRecipeBatchUpdateUpdateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<RecipeBatchUpdate>> {
if (requestParameters['recipeBatchUpdate'] == null) {
throw new runtime.RequiredError(
'recipeBatchUpdate',
'Required parameter "recipeBatchUpdate" was null or undefined when calling apiEnterpriseSocialRecipeBatchUpdateUpdate().'
);
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
headerParameters['Content-Type'] = 'application/json';
if (this.configuration && this.configuration.apiKey) {
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
}
const response = await this.request({
path: `/api/enterprise-social-recipe/batch_update/`,
method: 'PUT',
headers: headerParameters,
query: queryParameters,
body: RecipeBatchUpdateToJSON(requestParameters['recipeBatchUpdate']),
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => RecipeBatchUpdateFromJSON(jsonValue));
}
/**
* logs request counts to redis cache total/per user/
*/
async apiEnterpriseSocialRecipeBatchUpdateUpdate(requestParameters: ApiEnterpriseSocialRecipeBatchUpdateUpdateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<RecipeBatchUpdate> {
const response = await this.apiEnterpriseSocialRecipeBatchUpdateUpdateRaw(requestParameters, initOverrides);
return await response.value();
}
/**
* logs request counts to redis cache total/per user/
*/
@@ -7778,17 +7768,9 @@ export class ApiApi extends runtime.BaseAPI {
/**
*/
async apiOpenDataCategoryListRaw(requestParameters: ApiOpenDataCategoryListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<PaginatedOpenDataCategoryList>> {
async apiOpenDataCategoryListRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<Array<OpenDataCategory>>> {
const queryParameters: any = {};
if (requestParameters['page'] != null) {
queryParameters['page'] = requestParameters['page'];
}
if (requestParameters['pageSize'] != null) {
queryParameters['page_size'] = requestParameters['pageSize'];
}
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.apiKey) {
@@ -7802,13 +7784,13 @@ export class ApiApi extends runtime.BaseAPI {
query: queryParameters,
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => PaginatedOpenDataCategoryListFromJSON(jsonValue));
return new runtime.JSONApiResponse(response, (jsonValue) => jsonValue.map(OpenDataCategoryFromJSON));
}
/**
*/
async apiOpenDataCategoryList(requestParameters: ApiOpenDataCategoryListRequest = {}, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<PaginatedOpenDataCategoryList> {
const response = await this.apiOpenDataCategoryListRaw(requestParameters, initOverrides);
async apiOpenDataCategoryList(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<Array<OpenDataCategory>> {
const response = await this.apiOpenDataCategoryListRaw(initOverrides);
return await response.value();
}
@@ -8004,17 +7986,9 @@ export class ApiApi extends runtime.BaseAPI {
/**
*/
async apiOpenDataConversionListRaw(requestParameters: ApiOpenDataConversionListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<PaginatedOpenDataConversionList>> {
async apiOpenDataConversionListRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<Array<OpenDataConversion>>> {
const queryParameters: any = {};
if (requestParameters['page'] != null) {
queryParameters['page'] = requestParameters['page'];
}
if (requestParameters['pageSize'] != null) {
queryParameters['page_size'] = requestParameters['pageSize'];
}
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.apiKey) {
@@ -8028,13 +8002,13 @@ export class ApiApi extends runtime.BaseAPI {
query: queryParameters,
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => PaginatedOpenDataConversionListFromJSON(jsonValue));
return new runtime.JSONApiResponse(response, (jsonValue) => jsonValue.map(OpenDataConversionFromJSON));
}
/**
*/
async apiOpenDataConversionList(requestParameters: ApiOpenDataConversionListRequest = {}, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<PaginatedOpenDataConversionList> {
const response = await this.apiOpenDataConversionListRaw(requestParameters, initOverrides);
async apiOpenDataConversionList(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<Array<OpenDataConversion>> {
const response = await this.apiOpenDataConversionListRaw(initOverrides);
return await response.value();
}
@@ -8263,67 +8237,12 @@ export class ApiApi extends runtime.BaseAPI {
}
/**
* updates the food with all possible data from the FDC Api if properties with a fdc_id already exist they will be overridden, if existing properties don\'t have a fdc_id they won\'t be changed
*/
async apiOpenDataFoodFdcCreateRaw(requestParameters: ApiOpenDataFoodFdcCreateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<OpenDataFood>> {
if (requestParameters['id'] == null) {
throw new runtime.RequiredError(
'id',
'Required parameter "id" was null or undefined when calling apiOpenDataFoodFdcCreate().'
);
}
if (requestParameters['openDataFood'] == null) {
throw new runtime.RequiredError(
'openDataFood',
'Required parameter "openDataFood" was null or undefined when calling apiOpenDataFoodFdcCreate().'
);
}
async apiOpenDataFoodListRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<Array<OpenDataFood>>> {
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
headerParameters['Content-Type'] = 'application/json';
if (this.configuration && this.configuration.apiKey) {
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
}
const response = await this.request({
path: `/api/open-data-food/{id}/fdc/`.replace(`{${"id"}}`, encodeURIComponent(String(requestParameters['id']))),
method: 'POST',
headers: headerParameters,
query: queryParameters,
body: OpenDataFoodToJSON(requestParameters['openDataFood']),
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => OpenDataFoodFromJSON(jsonValue));
}
/**
* updates the food with all possible data from the FDC Api if properties with a fdc_id already exist they will be overridden, if existing properties don\'t have a fdc_id they won\'t be changed
*/
async apiOpenDataFoodFdcCreate(requestParameters: ApiOpenDataFoodFdcCreateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<OpenDataFood> {
const response = await this.apiOpenDataFoodFdcCreateRaw(requestParameters, initOverrides);
return await response.value();
}
/**
*/
async apiOpenDataFoodListRaw(requestParameters: ApiOpenDataFoodListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<PaginatedOpenDataFoodList>> {
const queryParameters: any = {};
if (requestParameters['page'] != null) {
queryParameters['page'] = requestParameters['page'];
}
if (requestParameters['pageSize'] != null) {
queryParameters['page_size'] = requestParameters['pageSize'];
}
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.apiKey) {
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
}
@@ -8335,13 +8254,13 @@ export class ApiApi extends runtime.BaseAPI {
query: queryParameters,
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => PaginatedOpenDataFoodListFromJSON(jsonValue));
return new runtime.JSONApiResponse(response, (jsonValue) => jsonValue.map(OpenDataFoodFromJSON));
}
/**
*/
async apiOpenDataFoodList(requestParameters: ApiOpenDataFoodListRequest = {}, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<PaginatedOpenDataFoodList> {
const response = await this.apiOpenDataFoodListRaw(requestParameters, initOverrides);
async apiOpenDataFoodList(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<Array<OpenDataFood>> {
const response = await this.apiOpenDataFoodListRaw(initOverrides);
return await response.value();
}
@@ -8537,17 +8456,9 @@ export class ApiApi extends runtime.BaseAPI {
/**
*/
async apiOpenDataPropertyListRaw(requestParameters: ApiOpenDataPropertyListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<PaginatedOpenDataPropertyList>> {
async apiOpenDataPropertyListRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<Array<OpenDataProperty>>> {
const queryParameters: any = {};
if (requestParameters['page'] != null) {
queryParameters['page'] = requestParameters['page'];
}
if (requestParameters['pageSize'] != null) {
queryParameters['page_size'] = requestParameters['pageSize'];
}
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.apiKey) {
@@ -8561,13 +8472,13 @@ export class ApiApi extends runtime.BaseAPI {
query: queryParameters,
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => PaginatedOpenDataPropertyListFromJSON(jsonValue));
return new runtime.JSONApiResponse(response, (jsonValue) => jsonValue.map(OpenDataPropertyFromJSON));
}
/**
*/
async apiOpenDataPropertyList(requestParameters: ApiOpenDataPropertyListRequest = {}, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<PaginatedOpenDataPropertyList> {
const response = await this.apiOpenDataPropertyListRaw(requestParameters, initOverrides);
async apiOpenDataPropertyList(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<Array<OpenDataProperty>> {
const response = await this.apiOpenDataPropertyListRaw(initOverrides);
return await response.value();
}
@@ -8790,17 +8701,9 @@ export class ApiApi extends runtime.BaseAPI {
/**
*/
async apiOpenDataStoreListRaw(requestParameters: ApiOpenDataStoreListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<PaginatedOpenDataStoreList>> {
async apiOpenDataStoreListRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<Array<OpenDataStore>>> {
const queryParameters: any = {};
if (requestParameters['page'] != null) {
queryParameters['page'] = requestParameters['page'];
}
if (requestParameters['pageSize'] != null) {
queryParameters['page_size'] = requestParameters['pageSize'];
}
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.apiKey) {
@@ -8814,13 +8717,13 @@ export class ApiApi extends runtime.BaseAPI {
query: queryParameters,
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => PaginatedOpenDataStoreListFromJSON(jsonValue));
return new runtime.JSONApiResponse(response, (jsonValue) => jsonValue.map(OpenDataStoreFromJSON));
}
/**
*/
async apiOpenDataStoreList(requestParameters: ApiOpenDataStoreListRequest = {}, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<PaginatedOpenDataStoreList> {
const response = await this.apiOpenDataStoreListRaw(requestParameters, initOverrides);
async apiOpenDataStoreList(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<Array<OpenDataStore>> {
const response = await this.apiOpenDataStoreListRaw(initOverrides);
return await response.value();
}
@@ -9016,17 +8919,9 @@ export class ApiApi extends runtime.BaseAPI {
/**
*/
async apiOpenDataUnitListRaw(requestParameters: ApiOpenDataUnitListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<PaginatedOpenDataUnitList>> {
async apiOpenDataUnitListRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<Array<OpenDataUnit>>> {
const queryParameters: any = {};
if (requestParameters['page'] != null) {
queryParameters['page'] = requestParameters['page'];
}
if (requestParameters['pageSize'] != null) {
queryParameters['page_size'] = requestParameters['pageSize'];
}
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.apiKey) {
@@ -9040,13 +8935,13 @@ export class ApiApi extends runtime.BaseAPI {
query: queryParameters,
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => PaginatedOpenDataUnitListFromJSON(jsonValue));
return new runtime.JSONApiResponse(response, (jsonValue) => jsonValue.map(OpenDataUnitFromJSON));
}
/**
*/
async apiOpenDataUnitList(requestParameters: ApiOpenDataUnitListRequest = {}, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<PaginatedOpenDataUnitList> {
const response = await this.apiOpenDataUnitListRaw(requestParameters, initOverrides);
async apiOpenDataUnitList(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<Array<OpenDataUnit>> {
const response = await this.apiOpenDataUnitListRaw(initOverrides);
return await response.value();
}
@@ -9242,17 +9137,9 @@ export class ApiApi extends runtime.BaseAPI {
/**
*/
async apiOpenDataVersionListRaw(requestParameters: ApiOpenDataVersionListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<PaginatedOpenDataVersionList>> {
async apiOpenDataVersionListRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<Array<OpenDataVersion>>> {
const queryParameters: any = {};
if (requestParameters['page'] != null) {
queryParameters['page'] = requestParameters['page'];
}
if (requestParameters['pageSize'] != null) {
queryParameters['page_size'] = requestParameters['pageSize'];
}
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.apiKey) {
@@ -9266,13 +9153,13 @@ export class ApiApi extends runtime.BaseAPI {
query: queryParameters,
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => PaginatedOpenDataVersionListFromJSON(jsonValue));
return new runtime.JSONApiResponse(response, (jsonValue) => jsonValue.map(OpenDataVersionFromJSON));
}
/**
*/
async apiOpenDataVersionList(requestParameters: ApiOpenDataVersionListRequest = {}, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<PaginatedOpenDataVersionList> {
const response = await this.apiOpenDataVersionListRaw(requestParameters, initOverrides);
async apiOpenDataVersionList(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<Array<OpenDataVersion>> {
const response = await this.apiOpenDataVersionListRaw(initOverrides);
return await response.value();
}
@@ -9874,6 +9761,46 @@ export class ApiApi extends runtime.BaseAPI {
return await response.value();
}
/**
* logs request counts to redis cache total/per user/
*/
async apiRecipeBatchUpdateUpdateRaw(requestParameters: ApiRecipeBatchUpdateUpdateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<RecipeBatchUpdate>> {
if (requestParameters['recipeBatchUpdate'] == null) {
throw new runtime.RequiredError(
'recipeBatchUpdate',
'Required parameter "recipeBatchUpdate" was null or undefined when calling apiRecipeBatchUpdateUpdate().'
);
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
headerParameters['Content-Type'] = 'application/json';
if (this.configuration && this.configuration.apiKey) {
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
}
const response = await this.request({
path: `/api/recipe/batch_update/`,
method: 'PUT',
headers: headerParameters,
query: queryParameters,
body: RecipeBatchUpdateToJSON(requestParameters['recipeBatchUpdate']),
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => RecipeBatchUpdateFromJSON(jsonValue));
}
/**
* logs request counts to redis cache total/per user/
*/
async apiRecipeBatchUpdateUpdate(requestParameters: ApiRecipeBatchUpdateUpdateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<RecipeBatchUpdate> {
const response = await this.apiRecipeBatchUpdateUpdateRaw(requestParameters, initOverrides);
return await response.value();
}
/**
* logs request counts to redis cache total/per user/
*/

View File

@@ -14,47 +14,43 @@
/**
* * `g` - g
* * `kg` - kg
* * `ounce` - ounce
* * `pound` - pound
* * `ml` - ml
* * `l` - l
* * `fluid_ounce` - fluid_ounce
* * `pint` - pint
* * `quart` - quart
* * `gallon` - gallon
* * `tbsp` - tbsp
* * `tsp` - tsp
* * `us_cup` - US Cup
* * `imperial_fluid_ounce` - imperial fluid ounce
* * `imperial_pint` - imperial pint
* * `imperial_quart` - imperial quart
* * `imperial_gallon` - imperial gallon
* * `imperial_tbsp` - imperial tbsp
* * `imperial_tsp` - imperial tsp
* * `G` - g
* * `KG` - kg
* * `ML` - ml
* * `L` - l
* * `OUNCE` - ounce
* * `POUND` - pound
* * `FLUID_OUNCE` - fluid_ounce
* * `TSP` - tsp
* * `TBSP` - tbsp
* * `CUP` - cup
* * `PINT` - pint
* * `QUART` - quart
* * `GALLON` - gallon
* * `IMPERIAL_FLUID_OUNCE` - imperial fluid ounce
* * `IMPERIAL_PINT` - imperial pint
* * `IMPERIAL_QUART` - imperial quart
* * `IMPERIAL_GALLON` - imperial gallon
* @export
*/
export const BaseUnitEnum = {
G: 'g',
Kg: 'kg',
Ounce: 'ounce',
Pound: 'pound',
Ml: 'ml',
L: 'l',
FluidOunce: 'fluid_ounce',
Pint: 'pint',
Quart: 'quart',
Gallon: 'gallon',
Tbsp: 'tbsp',
Tsp: 'tsp',
UsCup: 'us_cup',
ImperialFluidOunce: 'imperial_fluid_ounce',
ImperialPint: 'imperial_pint',
ImperialQuart: 'imperial_quart',
ImperialGallon: 'imperial_gallon',
ImperialTbsp: 'imperial_tbsp',
ImperialTsp: 'imperial_tsp'
G: 'G',
Kg: 'KG',
Ml: 'ML',
L: 'L',
Ounce: 'OUNCE',
Pound: 'POUND',
FluidOunce: 'FLUID_OUNCE',
Tsp: 'TSP',
Tbsp: 'TBSP',
Cup: 'CUP',
Pint: 'PINT',
Quart: 'QUART',
Gallon: 'GALLON',
ImperialFluidOunce: 'IMPERIAL_FLUID_OUNCE',
ImperialPint: 'IMPERIAL_PINT',
ImperialQuart: 'IMPERIAL_QUART',
ImperialGallon: 'IMPERIAL_GALLON'
} as const;
export type BaseUnitEnum = typeof BaseUnitEnum[keyof typeof BaseUnitEnum];

View File

@@ -164,10 +164,10 @@ export interface OpenDataFood {
propertiesSource?: string;
/**
*
* @type {number}
* @type {string}
* @memberof OpenDataFood
*/
fdcId?: number;
fdcId: string;
/**
*
* @type {string}
@@ -193,6 +193,7 @@ export function instanceOfOpenDataFood(value: object): value is OpenDataFood {
if (!('storeCategory' in value) || value['storeCategory'] === undefined) return false;
if (!('properties' in value) || value['properties'] === undefined) return false;
if (!('propertiesFoodUnit' in value) || value['propertiesFoodUnit'] === undefined) return false;
if (!('fdcId' in value) || value['fdcId'] === undefined) return false;
if (!('createdBy' in value) || value['createdBy'] === undefined) return false;
return true;
}
@@ -221,7 +222,7 @@ export function OpenDataFoodFromJSONTyped(json: any, ignoreDiscriminator: boolea
'propertiesFoodAmount': json['properties_food_amount'] == null ? undefined : json['properties_food_amount'],
'propertiesFoodUnit': OpenDataUnitFromJSON(json['properties_food_unit']),
'propertiesSource': json['properties_source'] == null ? undefined : json['properties_source'],
'fdcId': json['fdc_id'] == null ? undefined : json['fdc_id'],
'fdcId': json['fdc_id'],
'comment': json['comment'] == null ? undefined : json['comment'],
'createdBy': json['created_by'],
};

View File

@@ -164,10 +164,10 @@ export interface PatchedOpenDataFood {
propertiesSource?: string;
/**
*
* @type {number}
* @type {string}
* @memberof PatchedOpenDataFood
*/
fdcId?: number;
fdcId?: string;
/**
*
* @type {string}

View File

@@ -0,0 +1,79 @@
/* tslint:disable */
/* eslint-disable */
/**
* Tandoor
* Tandoor API Docs
*
* The version of the OpenAPI document: 0.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime';
/**
*
* @export
* @interface RecipeBatchUpdate
*/
export interface RecipeBatchUpdate {
/**
*
* @type {Array<number>}
* @memberof RecipeBatchUpdate
*/
recipes: Array<number>;
/**
*
* @type {Array<number>}
* @memberof RecipeBatchUpdate
*/
keywordsAdd: Array<number>;
/**
*
* @type {Array<number>}
* @memberof RecipeBatchUpdate
*/
keywordsRemove: Array<number>;
}
/**
* Check if a given object implements the RecipeBatchUpdate interface.
*/
export function instanceOfRecipeBatchUpdate(value: object): value is RecipeBatchUpdate {
if (!('recipes' in value) || value['recipes'] === undefined) return false;
if (!('keywordsAdd' in value) || value['keywordsAdd'] === undefined) return false;
if (!('keywordsRemove' in value) || value['keywordsRemove'] === undefined) return false;
return true;
}
export function RecipeBatchUpdateFromJSON(json: any): RecipeBatchUpdate {
return RecipeBatchUpdateFromJSONTyped(json, false);
}
export function RecipeBatchUpdateFromJSONTyped(json: any, ignoreDiscriminator: boolean): RecipeBatchUpdate {
if (json == null) {
return json;
}
return {
'recipes': json['recipes'],
'keywordsAdd': json['keywords_add'],
'keywordsRemove': json['keywords_remove'],
};
}
export function RecipeBatchUpdateToJSON(value?: RecipeBatchUpdate | null): any {
if (value == null) {
return value;
}
return {
'recipes': value['recipes'],
'keywords_add': value['keywordsAdd'],
'keywords_remove': value['keywordsRemove'],
};
}

View File

@@ -69,13 +69,6 @@ export * from './PaginatedInviteLinkList';
export * from './PaginatedKeywordList';
export * from './PaginatedMealPlanList';
export * from './PaginatedMealTypeList';
export * from './PaginatedOpenDataCategoryList';
export * from './PaginatedOpenDataConversionList';
export * from './PaginatedOpenDataFoodList';
export * from './PaginatedOpenDataPropertyList';
export * from './PaginatedOpenDataStoreList';
export * from './PaginatedOpenDataUnitList';
export * from './PaginatedOpenDataVersionList';
export * from './PaginatedPropertyList';
export * from './PaginatedPropertyTypeList';
export * from './PaginatedRecipeBookEntryList';
@@ -145,6 +138,7 @@ export * from './PatchedViewLog';
export * from './Property';
export * from './PropertyType';
export * from './Recipe';
export * from './RecipeBatchUpdate';
export * from './RecipeBook';
export * from './RecipeBookEntry';
export * from './RecipeFlat';

View File

@@ -1,27 +1,80 @@
<template>
<v-container>
Form
<v-form>
<ModelSelect model="Food"></ModelSelect>
</v-form>
<v-btn @click="loadRecipes()" :loading="loading">Load</v-btn>
<v-row>
<v-col>
Recipe 1 - {{ recipe1.name }}
<keywords-bar :keywords="recipe1.keywords"></keywords-bar>
</v-col>
<v-col>
Recipe 2 - {{ recipe2.name }}
<keywords-bar :keywords="recipe2.keywords"></keywords-bar>
</v-col>
</v-row>
Non Form
<ModelSelect model="Food"></ModelSelect>
<model-select model="Keyword" allow-create mode="tags" v-model="keywords"></model-select>
<v-text-field :label="$t('Name')" :max-length="128" counter></v-text-field>
<v-number-input :label="$t('Servings')" :precision="2"></v-number-input>
<v-text-field :label="$t('ServingsText')" :max-length="32" counter></v-text-field>
<v-form>
<v-text-field :rules="['required', ['maxLength', 16]]" label="Email" />
</v-form>
<v-btn @click="batchUpdate()" :loading="loading">Add to recipes</v-btn>
<v-btn @click="batchRemove()" :loading="loading">Remove to recipes</v-btn>
</v-container>
</template>
<script setup lang="ts">
import {onMounted, ref} from "vue";
import {ApiApi, Keyword, Recipe} from "@/openapi";
import KeywordsBar from "@/components/display/KeywordsBar.vue";
import ModelSelect from "@/components/inputs/ModelSelect.vue";
import {ErrorMessageType, useMessageStore} from "@/stores/MessageStore.ts";
const loading = ref(false)
const recipe1 = ref({} as Recipe)
const recipe2 = ref({} as Recipe)
const keywords = ref([] as Keyword[])
onMounted(() => {
loadRecipes()
})
function loadRecipes() {
let api = new ApiApi()
loading.value = true
api.apiRecipeRetrieve({id: 231}).then(r => {
recipe1.value = r
})
api.apiRecipeRetrieve({id: 232}).then(r => {
recipe2.value = r
}).finally(() => {
loading.value = false
})
}
function batchUpdate() {
let api = new ApiApi()
loading.value = true
api.apiRecipeBatchUpdateUpdate({recipeBatchUpdate: {recipes: [recipe1.value.id!, recipe2.value.id!], keywordsAdd: keywords.value.flatMap(x => x.id!)}}).then(r => {
}).catch(err => {
useMessageStore().addError(ErrorMessageType.UPDATE_ERROR, err)
}).finally(() => {
loadRecipes()
})
}
function batchRemove() {
let api = new ApiApi()
loading.value = true
api.apiRecipeBatchUpdateUpdate({recipeBatchUpdate: {recipes: [recipe1.value.id!, recipe2.value.id!], keywordsRemove: keywords.value.flatMap(x => x.id!)}}).then(r => {
}).catch(err => {
useMessageStore().addError(ErrorMessageType.UPDATE_ERROR, err)
}).finally(() => {
loadRecipes()
})
}
</script>