added recipe sage and domestica imports

This commit is contained in:
vabene1111 2021-03-28 18:58:37 +02:00
parent 738f0781b2
commit 7f8e29f1bc
7 changed files with 152 additions and 24 deletions

View File

@ -112,13 +112,15 @@ class ImportExportBase(forms.Form):
SAFRON = 'SAFRON' SAFRON = 'SAFRON'
CHEFTAP = 'CHEFTAP' CHEFTAP = 'CHEFTAP'
PEPPERPLATE = 'PEPPERPLATE' PEPPERPLATE = 'PEPPERPLATE'
RECIPESAGE = 'RECIPESAGE'
DOMESTICA = 'DOMESTICA'
type = forms.ChoiceField(choices=( type = forms.ChoiceField(choices=(
(DEFAULT, _('Default')), (PAPRIKA, 'Paprika'), (NEXTCLOUD, 'Nextcloud Cookbook'), (DEFAULT, _('Default')), (PAPRIKA, 'Paprika'), (NEXTCLOUD, 'Nextcloud Cookbook'),
(MEALIE, 'Mealie'), (CHOWDOWN, 'Chowdown'), (SAFRON, 'Safron'), (CHEFTAP, 'ChefTap'), (MEALIE, 'Mealie'), (CHOWDOWN, 'Chowdown'), (SAFRON, 'Safron'), (CHEFTAP, 'ChefTap'),
(PEPPERPLATE, 'Pepperplate'), (PEPPERPLATE, 'Pepperplate'), (RECIPESAGE, 'Recipe Sage'), (DOMESTICA, 'Domestica'),
)) ))
duplicates = forms.BooleanField(help_text=_('To prevent duplicates recipes with the same name as existing ones are ignored. Check this box to import everything.')) duplicates = forms.BooleanField(help_text=_('To prevent duplicates recipes with the same name as existing ones are ignored. Check this box to import everything.'), required=False)
class ImportForm(ImportExportBase): class ImportForm(ImportExportBase):

View File

@ -0,0 +1,51 @@
import base64
from io import BytesIO
from cookbook.helper.ingredient_parser import parse, get_food, get_unit
from cookbook.integration.integration import Integration
from cookbook.models import Recipe, Step, Ingredient
class Domestica(Integration):
def get_recipe_from_file(self, file):
recipe = Recipe.objects.create(
name=file['name'].strip(),
created_by=self.request.user, internal=True,
space=self.request.space)
if file['servings'] != '':
recipe.servings = file['servings']
if file['timeCook'] != '':
recipe.waiting_time = file['timeCook']
if file['timePrep'] != '':
recipe.working_time = file['timePrep']
recipe.save()
step = Step.objects.create(
instruction=file['directions']
)
if file['source'] != '':
step.instruction += '\n' + file['source']
for ingredient in file['ingredients'].split('\n'):
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
))
recipe.steps.add(step)
if file['image'] != '':
self.import_recipe_image(recipe, BytesIO(base64.b64decode(file['image'].replace('data:image/jpeg;base64,', ''))))
return recipe
def get_file_from_recipe(self, recipe):
raise NotImplementedError('Method not implemented in storage integration')

View File

@ -1,4 +1,5 @@
import datetime import datetime
import json
import uuid import uuid
from io import BytesIO, StringIO from io import BytesIO, StringIO
@ -19,6 +20,7 @@ class Integration:
request = None request = None
keyword = None keyword = None
files = None files = None
ignored_recipes = []
def __init__(self, request, export_type): def __init__(self, request, export_type):
""" """
@ -89,7 +91,6 @@ class Integration:
self.keyword.name = _('Import') + ' ' + str(il.pk) self.keyword.name = _('Import') + ' ' + str(il.pk)
self.keyword.save() self.keyword.save()
ignored_recipes = []
try: try:
self.files = files self.files = files
for f in files: for f in files:
@ -100,38 +101,41 @@ class Integration:
recipe = self.get_recipe_from_file(BytesIO(import_zip.read(z.filename))) recipe = self.get_recipe_from_file(BytesIO(import_zip.read(z.filename)))
recipe.keywords.add(self.keyword) recipe.keywords.add(self.keyword)
il.msg += f'{recipe.pk} - {recipe.name} \n' il.msg += f'{recipe.pk} - {recipe.name} \n'
if not import_duplicates: self.handle_duplicates(recipe, import_duplicates)
if duplicate := self.is_duplicate(recipe):
ignored_recipes.append(duplicate)
import_zip.close() import_zip.close()
elif '.json' in f['name']:
json_data = json.loads(f['file'].read().decode("utf-8"))
for d in json_data:
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)
else: else:
recipe = self.get_recipe_from_file(f['file']) recipe = self.get_recipe_from_file(f['file'])
recipe.keywords.add(self.keyword) recipe.keywords.add(self.keyword)
il.msg += f'{recipe.pk} - {recipe.name} \n' il.msg += f'{recipe.pk} - {recipe.name} \n'
if not import_duplicates: self.handle_duplicates(recipe, import_duplicates)
if duplicate := self.is_duplicate(recipe):
ignored_recipes.append(duplicate)
except BadZipFile: except BadZipFile:
il.msg += 'ERROR ' + _('Importer expected a .zip file. Did you choose the correct importer type for your data ?') + '\n' il.msg += 'ERROR ' + _('Importer expected a .zip file. Did you choose the correct importer type for your data ?') + '\n'
if len(ignored_recipes) > 0: if len(self.ignored_recipes) > 0:
il.msg += '\n' + _('The following recipes were ignored because they already existed:') + ' ' + ', '.join(ignored_recipes) + '\n\n' il.msg += '\n' + _('The following recipes were ignored because they already existed:') + ' ' + ', '.join(self.ignored_recipes) + '\n\n'
il.keyword = self.keyword il.keyword = self.keyword
il.msg += (_('Imported %s recipes.') % Recipe.objects.filter(keywords=self.keyword).count()) + '\n' il.msg += (_('Imported %s recipes.') % Recipe.objects.filter(keywords=self.keyword).count()) + '\n'
il.running = False il.running = False
il.save() il.save()
def is_duplicate(self, recipe): def handle_duplicates(self, recipe, import_duplicates):
""" """
Checks if a recipe is already present, if so deletes it Checks if a recipe is already present, if so deletes it
:param recipe: Recipe object :param recipe: Recipe object
:param import_duplicates: if duplicates should be imported
""" """
if Recipe.objects.filter(space=self.request.space, name=recipe.name).count() > 1: if Recipe.objects.filter(space=self.request.space, name=recipe.name).count() > 1 and not import_duplicates:
recipe.delete() recipe.delete()
return recipe.name self.ignored_recipes.append(recipe.name)
else:
return None
@staticmethod @staticmethod
def import_recipe_image(recipe, image_file): def import_recipe_image(recipe, image_file):

View File

@ -1,17 +1,11 @@
import base64 import base64
import gzip
import json import json
import re
from io import BytesIO from io import BytesIO
from zipfile import ZipFile
import microdata
from bs4 import BeautifulSoup
from cookbook.helper.ingredient_parser import parse, get_food, get_unit from cookbook.helper.ingredient_parser import parse, get_food, get_unit
from cookbook.helper.recipe_url_import import find_recipe_json
from cookbook.integration.integration import Integration from cookbook.integration.integration import Integration
from cookbook.models import Recipe, Step, Food, Ingredient, Unit from cookbook.models import Recipe, Step, Ingredient
import gzip
class Paprika(Integration): class Paprika(Integration):

View File

@ -0,0 +1,61 @@
import base64
from io import BytesIO
import requests
from cookbook.helper.ingredient_parser import parse, get_food, get_unit
from cookbook.integration.integration import Integration
from cookbook.models import Recipe, Step, Ingredient
class RecipeSage(Integration):
def get_recipe_from_file(self, file):
recipe = Recipe.objects.create(
name=file['name'].strip(),
created_by=self.request.user, internal=True,
space=self.request.space)
try:
if file['recipeYield'] != '':
recipe.servings = int(file['recipeYield'])
if file['totalTime'] != '':
recipe.waiting_time = int(file['totalTime']) - int(file['timePrep'])
if file['prepTime'] != '':
recipe.working_time = int(file['timePrep'])
recipe.save()
except Exception as e:
print('failed to parse yield or time ', str(e))
ingredients_added = False
for s in file['recipeInstructions']:
step = Step.objects.create(
instruction=s['text']
)
if not ingredients_added:
ingredients_added = True
for ingredient in file['recipeIngredient']:
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
))
recipe.steps.add(step)
if len(file['image']) > 0:
try:
response = requests.get(file['image'][0])
self.import_recipe_image(recipe, BytesIO(response.content))
except Exception as e:
print('failed to import image ', str(e))
return recipe
def get_file_from_recipe(self, recipe):
raise NotImplementedError('Method not implemented in storage integration')

View File

@ -14,9 +14,11 @@ from cookbook.integration.Pepperplate import Pepperplate
from cookbook.integration.cheftap import ChefTap from cookbook.integration.cheftap import ChefTap
from cookbook.integration.chowdown import Chowdown from cookbook.integration.chowdown import Chowdown
from cookbook.integration.default import Default from cookbook.integration.default import Default
from cookbook.integration.domestica import Domestica
from cookbook.integration.mealie import Mealie from cookbook.integration.mealie import Mealie
from cookbook.integration.nextcloud_cookbook import NextcloudCookbook from cookbook.integration.nextcloud_cookbook import NextcloudCookbook
from cookbook.integration.paprika import Paprika from cookbook.integration.paprika import Paprika
from cookbook.integration.recipesage import RecipeSage
from cookbook.integration.safron import Safron from cookbook.integration.safron import Safron
from cookbook.models import Recipe, ImportLog from cookbook.models import Recipe, ImportLog
@ -38,6 +40,10 @@ def get_integration(request, export_type):
return ChefTap(request, export_type) return ChefTap(request, export_type)
if export_type == ImportExportBase.PEPPERPLATE: if export_type == ImportExportBase.PEPPERPLATE:
return Pepperplate(request, export_type) return Pepperplate(request, export_type)
if export_type == ImportExportBase.DOMESTICA:
return Domestica(request, export_type)
if export_type == ImportExportBase.RECIPESAGE:
return RecipeSage(request, export_type)
@group_required('user') @group_required('user')

View File

@ -30,6 +30,8 @@ Overview of the capabilities of the different integrations.
| Paprika | ✔️ | ⌚ | ✔️ | | Paprika | ✔️ | ⌚ | ✔️ |
| ChefTap | ✔️ | ❌ | ❌️ | | ChefTap | ✔️ | ❌ | ❌️ |
| Pepperplate | ✔️ | ⌚ | ❌️ | | Pepperplate | ✔️ | ⌚ | ❌️ |
| RecipeSage | ✔️ | ⌚ | ✔️ |
| Domestica | ✔️ | ⌚ | ✔️ |
✔ = implemented, ❌ = not implemented and not possible/planned, ⌚ = not yet implemented ✔ = implemented, ❌ = not implemented and not possible/planned, ⌚ = not yet implemented
@ -40,6 +42,14 @@ It is maintained with new fields added and contains all data to transfer your re
It is also one of the few recipe formats that is actually structured in a way that allows for It is also one of the few recipe formats that is actually structured in a way that allows for
easy machine readability if you want to use the data for any other purpose. easy machine readability if you want to use the data for any other purpose.
## RecipeSage
Go to Settings > Export Recipe Data and select `EXPORT AS JSON-LD (BEST)`. Then simply upload the exported file
to Tandoor.
## Domestica
Go to Import/Export and select `Export Recipes`. Then simply upload the exported file
to Tandoor.
## Nextcloud ## Nextcloud
Importing recipes from Nextcloud cookbook is very easy and since Nextcloud Cookbook provides nice, standardized and Importing recipes from Nextcloud cookbook is very easy and since Nextcloud Cookbook provides nice, standardized and
structured information most of your recipe is going to be intact. structured information most of your recipe is going to be intact.