diff --git a/cookbook/helper/recipe_html_import.py b/cookbook/helper/recipe_html_import.py index acf72917b..7fa7beaf2 100644 --- a/cookbook/helper/recipe_html_import.py +++ b/cookbook/helper/recipe_html_import.py @@ -58,18 +58,6 @@ def get_recipe_from_source(text, url, request): }) return kid_list - recipe_json = { - 'name': '', - 'url': '', - 'description': '', - 'image': '', - 'keywords': [], - 'recipeIngredient': [], - 'recipeInstructions': '', - 'servings': '', - 'prepTime': '', - 'cookTime': '' - } recipe_tree = [] parse_list = [] html_data = [] diff --git a/cookbook/helper/recipe_url_import.py b/cookbook/helper/recipe_url_import.py index dd6c24353..3a39b403f 100644 --- a/cookbook/helper/recipe_url_import.py +++ b/cookbook/helper/recipe_url_import.py @@ -131,9 +131,7 @@ def get_from_scraper(scrape, request): recipe_json['steps'][0]['ingredients'].append( { 'amount': 0, - 'unit': { - 'name': '', - }, + 'unit': None, 'food': { 'name': x, }, @@ -275,9 +273,9 @@ def parse_keywords(keyword_json, space): kw = normalize_string(kw) if len(kw) != 0: if k := Keyword.objects.filter(name=kw, space=space).first(): - keywords.append({'name': str(k)}) + keywords.append({'label': str(k), 'name': k.name, 'id': k.id}) else: - keywords.append({'name': kw}) + keywords.append({'label': kw, 'name': kw}) return keywords diff --git a/cookbook/serializer.py b/cookbook/serializer.py index d41a47be1..3c19b7be0 100644 --- a/cookbook/serializer.py +++ b/cookbook/serializer.py @@ -621,9 +621,12 @@ class RecipeSerializer(RecipeBaseSerializer): class RecipeImageSerializer(WritableNestedModelSerializer): + image = serializers.ImageField(required=False, allow_null=True) + image_url = serializers.CharField(max_length=4096, required=False, allow_null=True) + class Meta: model = Recipe - fields = ['image', ] + fields = ['image', 'image_url', ] class RecipeImportSerializer(SpacedModelSerializer): diff --git a/cookbook/views/api.py b/cookbook/views/api.py index 9e62bbdf2..39df3b076 100644 --- a/cookbook/views/api.py +++ b/cookbook/views/api.py @@ -5,6 +5,7 @@ import uuid from collections import OrderedDict import requests +from PIL import UnidentifiedImageError from annoying.decorators import ajax_request from annoying.functions import get_object_or_None from django.contrib import messages @@ -23,6 +24,7 @@ from django.utils.translation import gettext as _ from django_scopes import scopes_disabled from icalendar import Calendar, Event from recipe_scrapers import NoSchemaFoundInWildMode, WebsiteNotImplementedError, scrape_me +from requests.exceptions import MissingSchema from rest_framework import decorators, status, viewsets from rest_framework.exceptions import APIException, PermissionDenied from rest_framework.pagination import PageNumberPagination @@ -706,20 +708,33 @@ class RecipeViewSet(viewsets.ModelViewSet): serializer = self.serializer_class(obj, data=request.data, partial=True) - if self.request.space.demo: - raise PermissionDenied(detail='Not available in demo', code=None) - if serializer.is_valid(): serializer.save() + image = None - if serializer.validated_data == {}: - obj.image = None - else: - img, filetype = handle_image(request, obj.image) + if 'image' in serializer.validated_data: + image = obj.image + elif 'image_url' in serializer.validated_data: + try: + response = requests.get(serializer.validated_data['image_url']) + image = File(io.BytesIO(response.content)) + print('test') + except UnidentifiedImageError as e: + print(e) + pass + except MissingSchema as e: + print(e) + pass + except Exception as e: + print(e) + pass + + if image is not None: + img, filetype = handle_image(request, image) obj.image = File(img, name=f'{uuid.uuid4()}_{obj.pk}{filetype}') - obj.save() + obj.save() + return Response(serializer.data) - return Response(serializer.data) return Response(serializer.errors, 400) # TODO: refactor API to use post/put/delete or leave as put and change VUE to use list_recipe after creating diff --git a/openapitools.json b/openapitools.json new file mode 100644 index 000000000..e69de29bb diff --git a/vue/src/apps/ImportView/ImportView.vue b/vue/src/apps/ImportView/ImportView.vue index adacc222d..c0d7b8d65 100644 --- a/vue/src/apps/ImportView/ImportView.vue +++ b/vue/src/apps/ImportView/ImportView.vue @@ -34,13 +34,41 @@
Images + +
+
+ +
+
+ +
+
+ Click the image you want to import for this + recipe +
+
+ +
+
+ + Keywords + unused + Steps
@@ -119,8 +147,14 @@ export default { importRecipe: function () { let apiFactory = new ApiApiFactory() apiFactory.createRecipe(this.recipe_json).then(response => { - StandardToasts.makeStandardToast(StandardToasts.SUCCESS_CREATE) - window.location = resolveDjangoUrl('edit_recipe', response.data.id) + let recipe = response.data + apiFactory.imageRecipe(response.data.id, undefined, this.recipe_json.image).then(response => { + StandardToasts.makeStandardToast(StandardToasts.SUCCESS_CREATE) + window.location = resolveDjangoUrl('edit_recipe', recipe.id) + }).catch(e => { + StandardToasts.makeStandardToast(StandardToasts.FAIL_UPDATE) + window.location = resolveDjangoUrl('edit_recipe', recipe.id) + }) }).catch(err => { StandardToasts.makeStandardToast(StandardToasts.FAIL_CREATE) }) @@ -154,6 +188,10 @@ export default { 'mode': this.mode },).then((response) => { this.recipe_json = response.data['recipe_json']; + + this.$set(this.recipe_json, 'unused_keywords', this.recipe_json.keywords.filter(k => k.id === undefined)) + this.$set(this.recipe_json, 'keywords', this.recipe_json.keywords.filter(k => k.id !== undefined)) + this.recipe_tree = response.data['recipe_tree']; this.recipe_html = response.data['recipe_html']; this.recipe_images = response.data['recipe_images']; //todo change on backend as well after old view is deprecated diff --git a/vue/src/utils/openapi/api.ts b/vue/src/utils/openapi/api.ts index dae44015d..c88cb5e21 100644 --- a/vue/src/utils/openapi/api.ts +++ b/vue/src/utils/openapi/api.ts @@ -1856,6 +1856,12 @@ export interface RecipeImage { * @memberof RecipeImage */ image?: any | null; + /** + * + * @type {string} + * @memberof RecipeImage + */ + image_url?: string | null; } /** * @@ -5227,10 +5233,11 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration) * * @param {string} id A unique integer value identifying this recipe. * @param {any} [image] + * @param {string} [imageUrl] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - imageRecipe: async (id: string, image?: any, options: any = {}): Promise => { + imageRecipe: async (id: string, image?: any, imageUrl?: string, options: any = {}): Promise => { // verify required parameter 'id' is not null or undefined assertParamExists('imageRecipe', 'id', id) const localVarPath = `/api/recipe/{id}/image/` @@ -5252,6 +5259,10 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration) localVarFormParams.append('image', image as any); } + if (imageUrl !== undefined) { + localVarFormParams.append('image_url', imageUrl as any); + } + localVarHeaderParameter['Content-Type'] = 'multipart/form-data'; @@ -10341,11 +10352,12 @@ export const ApiApiFp = function(configuration?: Configuration) { * * @param {string} id A unique integer value identifying this recipe. * @param {any} [image] + * @param {string} [imageUrl] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async imageRecipe(id: string, image?: any, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.imageRecipe(id, image, options); + async imageRecipe(id: string, image?: any, imageUrl?: string, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.imageRecipe(id, image, imageUrl, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** @@ -12174,11 +12186,12 @@ export const ApiApiFactory = function (configuration?: Configuration, basePath?: * * @param {string} id A unique integer value identifying this recipe. * @param {any} [image] + * @param {string} [imageUrl] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - imageRecipe(id: string, image?: any, options?: any): AxiosPromise { - return localVarFp.imageRecipe(id, image, options).then((request) => request(axios, basePath)); + imageRecipe(id: string, image?: any, imageUrl?: string, options?: any): AxiosPromise { + return localVarFp.imageRecipe(id, image, imageUrl, options).then((request) => request(axios, basePath)); }, /** * @@ -13992,12 +14005,13 @@ export class ApiApi extends BaseAPI { * * @param {string} id A unique integer value identifying this recipe. * @param {any} [image] + * @param {string} [imageUrl] * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof ApiApi */ - public imageRecipe(id: string, image?: any, options?: any) { - return ApiApiFp(this.configuration).imageRecipe(id, image, options).then((request) => request(this.axios, this.basePath)); + public imageRecipe(id: string, image?: any, imageUrl?: string, options?: any) { + return ApiApiFp(this.configuration).imageRecipe(id, image, imageUrl, options).then((request) => request(this.axios, this.basePath)); } /**