diff --git a/.env.template b/.env.template index 36c2357d50..635e079c97 100644 --- a/.env.template +++ b/.env.template @@ -7,7 +7,9 @@ SQL_DEBUG=0 ALLOWED_HOSTS=* # random secret key, use for example `base64 /dev/urandom | head -c50` to generate one +# ---------------------------- REQUIRED ------------------------- SECRET_KEY= +# --------------------------------------------------------------- # your default timezone See https://timezonedb.com/time-zones for a list of timezones TIMEZONE=Europe/Berlin @@ -18,7 +20,9 @@ DB_ENGINE=django.db.backends.postgresql POSTGRES_HOST=db_recipes POSTGRES_PORT=5432 POSTGRES_USER=djangouser +# ---------------------------- REQUIRED ------------------------- POSTGRES_PASSWORD= +# --------------------------------------------------------------- POSTGRES_DB=djangodb # database connection string, when used overrides other database settings. diff --git a/cookbook/admin.py b/cookbook/admin.py index 4c4fc52206..e53a27a86d 100644 --- a/cookbook/admin.py +++ b/cookbook/admin.py @@ -257,7 +257,7 @@ class ViewLogAdmin(admin.ModelAdmin): class InviteLinkAdmin(admin.ModelAdmin): list_display = ( - 'group', 'valid_until', + 'group', 'valid_until', 'space', 'created_by', 'created_at', 'used_by' ) diff --git a/cookbook/apps.py b/cookbook/apps.py index 3297d69287..5b2777f2b2 100644 --- a/cookbook/apps.py +++ b/cookbook/apps.py @@ -24,8 +24,8 @@ def ready(self): with scopes_disabled(): try: from cookbook.models import Keyword, Food - Keyword.fix_tree(fix_paths=True) - Food.fix_tree(fix_paths=True) + #Keyword.fix_tree(fix_paths=True) # disabled for now, causes to many unknown issues + #Food.fix_tree(fix_paths=True) except OperationalError: if DEBUG: traceback.print_exc() diff --git a/cookbook/forms.py b/cookbook/forms.py index 83ee6232b9..37a3263834 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/helper/recipe_html_import.py b/cookbook/helper/recipe_html_import.py index 7b779add0d..acf72917bc 100644 --- a/cookbook/helper/recipe_html_import.py +++ b/cookbook/helper/recipe_html_import.py @@ -1,13 +1,14 @@ import json import re +from json import JSONDecodeError +from urllib.parse import unquote from bs4 import BeautifulSoup from bs4.element import Tag +from recipe_scrapers._utils import get_host_name, normalize_string + from cookbook.helper import recipe_url_import as helper from cookbook.helper.scrapers.scrapers import text_scraper -from json import JSONDecodeError -from recipe_scrapers._utils import get_host_name, normalize_string -from urllib.parse import unquote def get_recipe_from_source(text, url, request): @@ -58,7 +59,7 @@ def get_children_list(children): return kid_list recipe_json = { - 'name': '', + 'name': '', 'url': '', 'description': '', 'image': '', @@ -188,6 +189,6 @@ def remove_graph(el): for x in el['@graph']: if '@type' in x and x['@type'] == 'Recipe': el = x - except TypeError: + except (TypeError, JSONDecodeError): pass return el diff --git a/cookbook/integration/copymethat.py b/cookbook/integration/copymethat.py new file mode 100644 index 0000000000..4f4a217e5e --- /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 d9b80eb9f9..2e5ac8513c 100644 --- a/cookbook/integration/integration.py +++ b/cookbook/integration/integration.py @@ -5,6 +5,7 @@ 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 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 @@ def do_import(self, files, il, import_duplicates): 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/base.html b/cookbook/templates/base.html index 9448ac4083..b88570f4da 100644 --- a/cookbook/templates/base.html +++ b/cookbook/templates/base.html @@ -67,7 +67,7 @@ {% if not request.user.is_authenticated or request.user.userpreference.theme == request.user.userpreference.TANDOOR %} - + {% endif %} diff --git a/cookbook/templates/url_import.html b/cookbook/templates/url_import.html index 6457f8761b..983f60378f 100644 --- a/cookbook/templates/url_import.html +++ b/cookbook/templates/url_import.html @@ -76,6 +76,7 @@