From f7d85bb4b8710fd4fb04c87bcb24bb1400d2cad7 Mon Sep 17 00:00:00 2001 From: vabene1111 Date: Wed, 9 Jun 2021 17:23:14 +0200 Subject: [PATCH] improvements to recipekeeper importer --- cookbook/helper/recipe_url_import.py | 8 +++++ cookbook/integration/integration.py | 2 ++ cookbook/integration/recipekeeper.py | 53 ++++++++++++++-------------- cookbook/templates/url_import.html | 3 ++ docs/features/import_export.md | 7 +++- 5 files changed, 45 insertions(+), 28 deletions(-) diff --git a/cookbook/helper/recipe_url_import.py b/cookbook/helper/recipe_url_import.py index 4facaa57..e76d12e8 100644 --- a/cookbook/helper/recipe_url_import.py +++ b/cookbook/helper/recipe_url_import.py @@ -358,3 +358,11 @@ def normalize_string(string): unescaped_string = re.sub(r'\n\s*\n', '\n\n', unescaped_string) unescaped_string = unescaped_string.replace("\xa0", " ").replace("\t", " ").strip() return unescaped_string + + +def iso_duration_to_minutes(string): + match = re.match( + r'P((?P\d+)Y)?((?P\d+)M)?((?P\d+)W)?((?P\d+)D)?T((?P\d+)H)?((?P\d+)M)?((?P\d+)S)?', + string + ).groupdict() + return int(match['days'] or 0) * 24 * 60 + int(match['hours'] or 0) * 60 + int(match['minutes'] or 0) diff --git a/cookbook/integration/integration.py b/cookbook/integration/integration.py index ef24372a..fb48308c 100644 --- a/cookbook/integration/integration.py +++ b/cookbook/integration/integration.py @@ -1,5 +1,6 @@ import datetime import json +import re import uuid from io import BytesIO, StringIO from zipfile import ZipFile, BadZipFile @@ -216,3 +217,4 @@ class Integration: - data - string content for file to get created in export zip """ raise NotImplementedError('Method not implemented in integration') + diff --git a/cookbook/integration/recipekeeper.py b/cookbook/integration/recipekeeper.py index dc207916..bd5e241f 100644 --- a/cookbook/integration/recipekeeper.py +++ b/cookbook/integration/recipekeeper.py @@ -6,6 +6,7 @@ from zipfile import ZipFile from django.utils.translation import gettext as _ from cookbook.helper.ingredient_parser import parse, get_food, get_unit +from cookbook.helper.recipe_url_import import parse_servings, iso_duration_to_minutes from cookbook.integration.integration import Integration from cookbook.models import Recipe, Step, Food, Unit, Ingredient, Keyword @@ -32,39 +33,37 @@ class RecipeKeeper(Integration): keyword, created = Keyword.objects.get_or_create(name=category.get("content"), space=self.request.space) recipe.keywords.add(keyword) - # TODO: import prep and cook times - # Recipe Keeper uses ISO 8601 format for its duration periods. + try: + recipe.servings = parse_servings(file.find("span", {"itemprop": "recipeYield"}).text.strip()) + recipe.working_time = iso_duration_to_minutes(file.find("span", {"meta": "prepTime"}).text.strip()) + recipe.waiting_time = iso_duration_to_minutes(file.find("span", {"meta": "cookTime"}).text.strip()) + recipe.save() + except AttributeError: + pass + + step = Step.objects.create(instruction='') + + for ingredient in file.find("div", {"itemprop": "recipeIngredients"}).findChildren("p"): + if ingredient.text == "": + continue + amount, unit, ingredient, note = parse(ingredient.text.strip()) + 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 + )) - source_url_added = False - ingredients_added = False for s in file.find("div", {"itemprop": "recipeDirections"}).find_all("p"): - if s.text == "": continue + step.instruction += s.text + ' \n' - step = Step.objects.create( - instruction=s.text - ) + if file.find("span", {"itemprop": "recipeSource"}).text != '': + step.instruction += "\n\nImported from: " + file.find("span", {"itemprop": "recipeSource"}).text + step.save() + source_url_added = True - if not source_url_added: - # If there is a source URL, add it to the first step field. - if file.find("span", {"itemprop": "recipeSource"}).text != '': - step.instruction += "\n\nImported from: " + file.find("span", {"itemprop": "recipeSource"}).text - step.save() - source_url_added = True - - if not ingredients_added: - ingredients_added = True - for ingredient in file.find("div", {"itemprop": "recipeIngredients"}).findChildren("p"): - if ingredient.text == "": - continue - amount, unit, ingredient, note = parse(ingredient.text.strip()) - 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 - )) - recipe.steps.add(step) + recipe.steps.add(step) # import the Primary recipe image that is stored in the Zip try: diff --git a/cookbook/templates/url_import.html b/cookbook/templates/url_import.html index c1f5f67b..5b136e23 100644 --- a/cookbook/templates/url_import.html +++ b/cookbook/templates/url_import.html @@ -100,6 +100,9 @@ + diff --git a/docs/features/import_export.md b/docs/features/import_export.md index d3ea5d04..fc766bb4 100644 --- a/docs/features/import_export.md +++ b/docs/features/import_export.md @@ -35,6 +35,7 @@ Overview of the capabilities of the different integrations. | MealMaster | ✔️ | ❌ | ❌ | | RezKonv | ✔️ | ❌ | ❌ | | OpenEats | ✔️ | ❌ | ⌚ | +| OpenEats | ✔️ | ❌ | ✔ | ✔ = implemented, ❌ = not implemented and not possible/planned, ⌚ = not yet implemented @@ -161,7 +162,11 @@ The RezKonv format is primarily used in the german recipe manager RezKonv Suite. To migrate from RezKonv Suite to Tandoor select `Export > Gesamtes Kochbuch exportieren` (the last option in the export menu). The generated file can simply be imported into Tandoor. -As i only had limited sample data feel free to open an issue if your RezKonv export cannot be imported. +As I only had limited sample data feel free to open an issue if your RezKonv export cannot be imported. + +## Recipekeeper +Recipe keeper allows to export a zip file containing recipes and images using its apps. +This zip file can simply be imported into Tandoor. ## OpenEats OpenEats does not provide any way to export the data using the interface. Luckily it is relatively easy to export it from the command line.