diff --git a/cookbook/forms.py b/cookbook/forms.py index 39a230e46f..7c40a14864 100644 --- a/cookbook/forms.py +++ b/cookbook/forms.py @@ -112,6 +112,7 @@ class ImportExportBase(forms.Form): SAFRON = 'SAFRON' CHEFTAP = 'CHEFTAP' PEPPERPLATE = 'PEPPERPLATE' + RECETTETEK = 'RECETTETEK' RECIPESAGE = 'RECIPESAGE' DOMESTICA = 'DOMESTICA' MEALMASTER = 'MEALMASTER' @@ -120,7 +121,7 @@ class ImportExportBase(forms.Form): type = forms.ChoiceField(choices=( (DEFAULT, _('Default')), (PAPRIKA, 'Paprika'), (NEXTCLOUD, 'Nextcloud Cookbook'), (MEALIE, 'Mealie'), (CHOWDOWN, 'Chowdown'), (SAFRON, 'Safron'), (CHEFTAP, 'ChefTap'), - (PEPPERPLATE, 'Pepperplate'), (RECIPESAGE, 'Recipe Sage'), (DOMESTICA, 'Domestica'), + (PEPPERPLATE, 'Pepperplate'), (RECETTETEK, 'RecetteTek'), (RECIPESAGE, 'Recipe Sage'), (DOMESTICA, 'Domestica'), (MEALMASTER, 'MealMaster'), (REZKONV, 'RezKonv'), )) diff --git a/cookbook/integration/integration.py b/cookbook/integration/integration.py index 33ed72cfce..d2024c0d4f 100644 --- a/cookbook/integration/integration.py +++ b/cookbook/integration/integration.py @@ -122,6 +122,17 @@ def do_import(self, files, il, import_duplicates): recipe.keywords.add(self.keyword) il.msg += f'{recipe.pk} - {recipe.name} \n' self.handle_duplicates(recipe, import_duplicates) + elif '.rtk' in f['name']: + import_zip = ZipFile(f['file']) + for z in import_zip.filelist: + if self.import_file_name_filter(z): + data_list = self.split_recipe_file(import_zip.read(z.filename).decode('utf-8')) + for d in data_list: + recipe = self.get_recipe_from_file(d) + recipe.keywords.add(self.keyword) + il.msg += f'{recipe.pk} - {recipe.name} \n' + self.handle_duplicates(recipe, import_duplicates) + import_zip.close() else: recipe = self.get_recipe_from_file(f['file']) recipe.keywords.add(self.keyword) diff --git a/cookbook/integration/recettetek.py b/cookbook/integration/recettetek.py new file mode 100644 index 0000000000..e0cc848221 --- /dev/null +++ b/cookbook/integration/recettetek.py @@ -0,0 +1,131 @@ +import re +import json +import base64 +import requests +from io import BytesIO +from zipfile import ZipFile +import imghdr +from django.utils.translation import gettext as _ + +from cookbook.helper.ingredient_parser import parse, get_food, get_unit +from cookbook.integration.integration import Integration +from cookbook.models import Recipe, Step, Food, Unit, Ingredient, Keyword + + +class RecetteTek(Integration): + + def import_file_name_filter(self, zip_info_object): + print("testing", zip_info_object.filename) + return re.match(r'^recipes_0.json$', zip_info_object.filename) + + def split_recipe_file(self, file): + + recipe_json = json.loads(file) + + recipe_list = [r for r in recipe_json] + + return recipe_list + + def get_recipe_from_file(self, file): + + # Create initial recipe with just a title and a decription + recipe = Recipe.objects.create(name=file['title'], created_by=self.request.user, internal=True, space=self.request.space, ) + + # set the description as an empty string for later use for the source URL, incase there is no description text. + recipe.description = '' + + try: + if file['description'] != '': + recipe.description = file['description'].strip() + except Exception as e: + print(recipe.name, ': failed to parse recipe description ', str(e)) + + step = Step.objects.create(instruction=file['instructions']) + + # Append the original import url to the step (if it exists) + try: + if file['url'] != '': + step.instruction += '\n\nImported from: ' + file['url'] + step.save() + except Exception as e: + print(recipe.name, ': failed to import source url ', str(e)) + + try: + # Process the ingredients. Assumes 1 ingredient per line. + for ingredient in file['ingredients'].split('\n'): + if len(ingredient.strip()) > 0: + amount, unit, ingredient, note = parse(ingredient) + f = get_food(ingredient, self.request.space) + u = get_unit(unit, self.request.space) + step.ingredients.add(Ingredient.objects.create( + food=f, unit=u, amount=amount, note=note + )) + except Exception as e: + print(recipe.name, ': failed to parse recipe ingredients ', str(e)) + recipe.steps.add(step) + + # Attempt to import prep/cooking times + # quick hack, this assumes only one number in the quantity field. + try: + if file['quantity'] != '': + for item in file['quantity'].split(' '): + if item.isdigit(): + recipe.servings = int(item) + break + except Exception as e: + print(recipe.name, ': failed to parse quantity ', str(e)) + + try: + if file['totalTime'] != '': + recipe.waiting_time = int(file['totalTime']) + except Exception as e: + print(recipe.name, ': failed to parse total times ', str(e)) + + try: + if file['preparationTime'] != '': + recipe.working_time = int(file['preparationTime']) + except Exception as e: + print(recipe.name, ': failed to parse prep time ', str(e)) + + try: + if file['cookingTime'] != '': + recipe.waiting_time = int(file['cookingTime']) + except Exception as e: + print(recipe.name, ': failed to parse cooking time ', str(e)) + + recipe.save() + + # Import the recipe keywords + try: + if file['keywords'] != '': + for keyword in file['keywords'].split(';'): + k, created = Keyword.objects.get_or_create(name=keyword.strip(), space=self.request.space) + recipe.keywords.add(k) + recipe.save() + except Exception as e: + pass + + # TODO: Parse Nutritional Information + + # Import the original image from the zip file, if we cannot do that, attempt to download it again. + try: + if file['pictures'][0] !='': + image_file_name = file['pictures'][0].split('/')[-1] + for f in self.files: + if '.rtk' in f['name']: + import_zip = ZipFile(f['file']) + self.import_recipe_image(recipe, BytesIO(import_zip.read(image_file_name))) + else: + if file['originalPicture'] != '': + response=requests.get(file['originalPicture']) + if imghdr.what(BytesIO(response.content)) != None: + self.import_recipe_image(recipe, BytesIO(response.content)) + else: + raise Exception("Original image failed to download.") + except Exception as e: + print(recipe.name, ': failed to import image ', str(e)) + + return recipe + + def get_file_from_recipe(self, recipe): + raise NotImplementedError('Method not implemented in storage integration') diff --git a/cookbook/views/import_export.py b/cookbook/views/import_export.py index 1276306769..19dde0ef35 100644 --- a/cookbook/views/import_export.py +++ b/cookbook/views/import_export.py @@ -19,6 +19,7 @@ from cookbook.integration.mealmaster import MealMaster from cookbook.integration.nextcloud_cookbook import NextcloudCookbook from cookbook.integration.paprika import Paprika +from cookbook.integration.recettetek import RecetteTek from cookbook.integration.recipesage import RecipeSage from cookbook.integration.rezkonv import RezKonv from cookbook.integration.safron import Safron @@ -44,6 +45,8 @@ def get_integration(request, export_type): return Pepperplate(request, export_type) if export_type == ImportExportBase.DOMESTICA: return Domestica(request, export_type) + if export_type == ImportExportBase.RECETTETEK: + return RecetteTek(request, export_type) if export_type == ImportExportBase.RECIPESAGE: return RecipeSage(request, export_type) if export_type == ImportExportBase.REZKONV: