From 3ff15b6766ffd8c0638d1777075da2cda5cf8551 Mon Sep 17 00:00:00 2001 From: vabene1111 Date: Thu, 23 Dec 2021 15:54:48 +0100 Subject: [PATCH] added copy me that importer --- cookbook/forms.py | 3 +- cookbook/integration/copymethat.py | 84 +++++++++++++++++++++++++++++ cookbook/integration/integration.py | 13 ++++- cookbook/templates/url_import.html | 1 + cookbook/views/import_export.py | 3 ++ docs/features/import_export.md | 5 ++ 6 files changed, 106 insertions(+), 3 deletions(-) create mode 100644 cookbook/integration/copymethat.py diff --git a/cookbook/forms.py b/cookbook/forms.py index 83ee6232..37a32638 100644 --- a/cookbook/forms.py +++ b/cookbook/forms.py @@ -152,13 +152,14 @@ class ImportExportBase(forms.Form): OPENEATS = 'OPENEATS' PLANTOEAT = 'PLANTOEAT' COOKBOOKAPP = 'COOKBOOKAPP' + COPYMETHAT = 'COPYMETHAT' type = forms.ChoiceField(choices=( (DEFAULT, _('Default')), (PAPRIKA, 'Paprika'), (NEXTCLOUD, 'Nextcloud Cookbook'), (MEALIE, 'Mealie'), (CHOWDOWN, 'Chowdown'), (SAFRON, 'Safron'), (CHEFTAP, 'ChefTap'), (PEPPERPLATE, 'Pepperplate'), (RECETTETEK, 'RecetteTek'), (RECIPESAGE, 'Recipe Sage'), (DOMESTICA, 'Domestica'), (MEALMASTER, 'MealMaster'), (REZKONV, 'RezKonv'), (OPENEATS, 'Openeats'), (RECIPEKEEPER, 'Recipe Keeper'), - (PLANTOEAT, 'Plantoeat'), (COOKBOOKAPP, 'CookBookApp'), + (PLANTOEAT, 'Plantoeat'), (COOKBOOKAPP, 'CookBookApp'), (COPYMETHAT, 'CopyMeThat'), )) diff --git a/cookbook/integration/copymethat.py b/cookbook/integration/copymethat.py new file mode 100644 index 00000000..4f4a217e --- /dev/null +++ b/cookbook/integration/copymethat.py @@ -0,0 +1,84 @@ +import re +from io import BytesIO +from zipfile import ZipFile + +from bs4 import BeautifulSoup + +from cookbook.helper.ingredient_parser import IngredientParser +from cookbook.helper.recipe_html_import import get_recipe_from_source +from cookbook.helper.recipe_url_import import iso_duration_to_minutes, parse_servings +from cookbook.integration.integration import Integration +from cookbook.models import Recipe, Step, Ingredient, Keyword +from recipes.settings import DEBUG + + +class CopyMeThat(Integration): + + def import_file_name_filter(self, zip_info_object): + if DEBUG: + print("testing", zip_info_object.filename, zip_info_object.filename == 'recipes.html') + return zip_info_object.filename == 'recipes.html' + + def get_recipe_from_file(self, file): + # 'file' comes is as a beautifulsoup object + recipe = Recipe.objects.create(name=file.find("div", {"id": "name"}).text.strip(), created_by=self.request.user, internal=True, space=self.request.space, ) + + for category in file.find_all("span", {"class": "recipeCategory"}): + keyword, created = Keyword.objects.get_or_create(name=category.text, space=self.request.space) + recipe.keywords.add(keyword) + + try: + recipe.servings = parse_servings(file.find("a", {"id": "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='', space=self.request.space, ) + + ingredient_parser = IngredientParser(self.request, True) + for ingredient in file.find_all("li", {"class": "recipeIngredient"}): + if ingredient.text == "": + continue + amount, unit, ingredient, note = ingredient_parser.parse(ingredient.text.strip()) + f = ingredient_parser.get_food(ingredient) + u = ingredient_parser.get_unit(unit) + step.ingredients.add(Ingredient.objects.create( + food=f, unit=u, amount=amount, note=note, space=self.request.space, + )) + + for s in file.find_all("li", {"class": "instruction"}): + if s.text == "": + continue + step.instruction += s.text.strip() + ' \n\n' + + for s in file.find_all("li", {"class": "recipeNote"}): + if s.text == "": + continue + step.instruction += s.text.strip() + ' \n\n' + + try: + if file.find("a", {"id": "original_link"}).text != '': + step.instruction += "\n\nImported from: " + file.find("a", {"id": "original_link"}).text + step.save() + except AttributeError: + pass + + recipe.steps.add(step) + + # import the Primary recipe image that is stored in the Zip + try: + for f in self.files: + if '.zip' in f['name']: + import_zip = ZipFile(f['file']) + self.import_recipe_image(recipe, BytesIO(import_zip.read(file.find("img", class_="recipeImage").get("src"))), filetype='.jpeg') + except Exception as e: + print(recipe.name, ': failed to import image ', str(e)) + + recipe.save() + return recipe + + def split_recipe_file(self, file): + soup = BeautifulSoup(file, "html.parser") + return soup.find_all("div", {"class": "recipe"}) diff --git a/cookbook/integration/integration.py b/cookbook/integration/integration.py index d9b80eb9..2e5ac851 100644 --- a/cookbook/integration/integration.py +++ b/cookbook/integration/integration.py @@ -5,6 +5,7 @@ import uuid from io import BytesIO, StringIO from zipfile import ZipFile, BadZipFile +from bs4 import Tag from django.core.exceptions import ObjectDoesNotExist from django.core.files import File from django.db import IntegrityError @@ -16,7 +17,7 @@ from django_scopes import scope from cookbook.forms import ImportExportBase from cookbook.helper.image_processing import get_filetype, handle_image from cookbook.models import Keyword, Recipe -from recipes.settings import DATABASES, DEBUG +from recipes.settings import DEBUG class Integration: @@ -153,9 +154,17 @@ class Integration: file_list.append(z) il.total_recipes += len(file_list) + import cookbook + if isinstance(self, cookbook.integration.copymethat.CopyMeThat): + file_list = self.split_recipe_file(BytesIO(import_zip.read('recipes.html'))) + il.total_recipes += len(file_list) + for z in file_list: try: - recipe = self.get_recipe_from_file(BytesIO(import_zip.read(z.filename))) + if isinstance(z, Tag): + recipe = self.get_recipe_from_file(z) + else: + recipe = self.get_recipe_from_file(BytesIO(import_zip.read(z.filename))) recipe.keywords.add(self.keyword) il.msg += f'{recipe.pk} - {recipe.name} \n' self.handle_duplicates(recipe, import_duplicates) diff --git a/cookbook/templates/url_import.html b/cookbook/templates/url_import.html index 6457f876..983f6037 100644 --- a/cookbook/templates/url_import.html +++ b/cookbook/templates/url_import.html @@ -76,6 +76,7 @@ + diff --git a/cookbook/views/import_export.py b/cookbook/views/import_export.py index 0a8e83e1..ebbef836 100644 --- a/cookbook/views/import_export.py +++ b/cookbook/views/import_export.py @@ -11,6 +11,7 @@ from django.utils.translation import gettext as _ from cookbook.forms import ExportForm, ImportForm, ImportExportBase from cookbook.helper.permission_helper import group_required from cookbook.integration.cookbookapp import CookBookApp +from cookbook.integration.copymethat import CopyMeThat from cookbook.integration.pepperplate import Pepperplate from cookbook.integration.cheftap import ChefTap from cookbook.integration.chowdown import Chowdown @@ -65,6 +66,8 @@ def get_integration(request, export_type): return Plantoeat(request, export_type) if export_type == ImportExportBase.COOKBOOKAPP: return CookBookApp(request, export_type) + if export_type == ImportExportBase.COPYMETHAT: + return CopyMeThat(request, export_type) @group_required('user') diff --git a/docs/features/import_export.md b/docs/features/import_export.md index d53c30c0..9db829ec 100644 --- a/docs/features/import_export.md +++ b/docs/features/import_export.md @@ -37,6 +37,7 @@ Overview of the capabilities of the different integrations. | OpenEats | ✔️ | ❌ | ⌚ | | Plantoeat | ✔️ | ❌ | ✔ | | CookBookApp | ✔️ | ⌚ | ✔️ | +| CopyMeThat | ✔️ | ❌ | ✔️ | ✔ = implemented, ❌ = not implemented and not possible/planned, ⌚ = not yet implemented @@ -218,3 +219,7 @@ Plan to eat allows you to export a text file containing all your recipes. Simply ## CookBookApp CookBookApp can export .zip files containing .html files. Upload the entire ZIP to Tandoor to import all included recipes. + +## CopyMeThat + +CopyMeThat can export .zip files containing an `.html` file as well as a folder containing all the images. Upload the entire ZIP to Tandoor to import all included recipes. \ No newline at end of file