diff --git a/cookbook/forms.py b/cookbook/forms.py
index b21eb6ce7..4f966f00a 100644
--- a/cookbook/forms.py
+++ b/cookbook/forms.py
@@ -159,6 +159,7 @@ class ImportExportBase(forms.Form):
PLANTOEAT = 'PLANTOEAT'
COOKBOOKAPP = 'COOKBOOKAPP'
COPYMETHAT = 'COPYMETHAT'
+ COOKMATE = 'COOKMATE'
PDF = 'PDF'
type = forms.ChoiceField(choices=(
@@ -167,6 +168,7 @@ class ImportExportBase(forms.Form):
(PEPPERPLATE, 'Pepperplate'), (RECETTETEK, 'RecetteTek'), (RECIPESAGE, 'Recipe Sage'), (DOMESTICA, 'Domestica'),
(MEALMASTER, 'MealMaster'), (REZKONV, 'RezKonv'), (OPENEATS, 'Openeats'), (RECIPEKEEPER, 'Recipe Keeper'),
(PLANTOEAT, 'Plantoeat'), (COOKBOOKAPP, 'CookBookApp'), (COPYMETHAT, 'CopyMeThat'), (PDF, 'PDF'), (MELARECIPES, 'Melarecipes'),
+ (COOKMATE, 'Cookmate')
))
diff --git a/cookbook/integration/cookmate.py b/cookbook/integration/cookmate.py
new file mode 100644
index 000000000..19c0e936e
--- /dev/null
+++ b/cookbook/integration/cookmate.py
@@ -0,0 +1,77 @@
+import base64
+import json
+from io import BytesIO
+
+from gettext import gettext as _
+
+import requests
+from lxml import etree
+
+from cookbook.helper.ingredient_parser import IngredientParser
+from cookbook.helper.recipe_url_import import parse_servings, parse_time, parse_servings_text
+from cookbook.integration.integration import Integration
+from cookbook.models import Ingredient, Keyword, Recipe, Step
+
+
+class Cookmate(Integration):
+
+ def import_file_name_filter(self, zip_info_object):
+ return zip_info_object.filename.endswith('.xml')
+
+ def get_files_from_recipes(self, recipes, el, cookie):
+ raise NotImplementedError('Method not implemented in storage integration')
+
+ def get_recipe_from_file(self, file):
+ recipe_xml = file
+
+ recipe = Recipe.objects.create(
+ name=recipe_xml.find('title').text.strip(),
+ created_by=self.request.user, internal=True, space=self.request.space)
+
+ if recipe_xml.find('preptime') is not None:
+ recipe.working_time = parse_time(recipe_xml.find('preptime').text.strip())
+
+ if recipe_xml.find('cooktime') is not None:
+ recipe.waiting_time = parse_time(recipe_xml.find('cooktime').text.strip())
+
+ if recipe_xml.find('quantity') is not None:
+ recipe.servings = parse_servings(recipe_xml.find('quantity').text.strip())
+ recipe.servings_text = parse_servings_text(recipe_xml.find('quantity').text.strip())
+
+ if recipe_xml.find('url') is not None:
+ recipe.source_url = recipe_xml.find('url').text.strip()
+
+ if recipe_xml.find('description') is not None: # description is a list of
's with text
+ if len(recipe_xml.find('description')) > 0:
+ recipe.description = recipe_xml.find('description')[0].text[:512]
+
+ for step in recipe_xml.find('recipetext').getchildren():
+ step = Step.objects.create(
+ instruction=step.text.strip(), space=self.request.space,
+ )
+ recipe.steps.add(step)
+
+ ingredient_parser = IngredientParser(self.request, True)
+
+ for ingredient in recipe_xml.find('ingredient').getchildren():
+ if ingredient.text.strip() != '':
+ amount, unit, food, note = ingredient_parser.parse(ingredient.text.strip())
+ f = ingredient_parser.get_food(food)
+ u = ingredient_parser.get_unit(unit)
+ recipe.steps.first().ingredients.add(Ingredient.objects.create(
+ food=f, unit=u, amount=amount, note=note, original_text=ingredient.text.strip(), space=self.request.space,
+ ))
+
+ if recipe_xml.find('imageurl') is not None:
+ try:
+ response = requests.get(recipe_xml.find('imageurl').text.strip())
+ self.import_recipe_image(recipe, BytesIO(response.content))
+ except Exception as e:
+ print('failed to import image ', str(e))
+
+ recipe.save()
+
+ return recipe
+
+ def get_file_from_recipe(self, recipe):
+ raise NotImplementedError('Method not implemented in storage integration')
diff --git a/cookbook/integration/integration.py b/cookbook/integration/integration.py
index 65ed00aef..6493c266d 100644
--- a/cookbook/integration/integration.py
+++ b/cookbook/integration/integration.py
@@ -5,6 +5,8 @@ import traceback
import uuid
from io import BytesIO, StringIO
from zipfile import BadZipFile, ZipFile
+
+import lxml
from django.core.cache import cache
import datetime
@@ -16,6 +18,7 @@ from django.http import HttpResponse
from django.utils.formats import date_format
from django.utils.translation import gettext as _
from django_scopes import scope
+from lxml import etree
from cookbook.forms import ImportExportBase
from cookbook.helper.image_processing import get_filetype, handle_image
@@ -144,7 +147,7 @@ class Integration:
il.imported_recipes += 1
il.save()
import_zip.close()
- elif '.zip' in f['name'] or '.paprikarecipes' in f['name']:
+ elif '.zip' in f['name'] or '.paprikarecipes' in f['name'] or '.mcb' in f['name']:
import_zip = ZipFile(f['file'])
file_list = []
for z in import_zip.filelist:
@@ -157,9 +160,16 @@ class Integration:
file_list = self.split_recipe_file(BytesIO(import_zip.read('recipes.html')))
il.total_recipes += len(file_list)
+ if isinstance(self, cookbook.integration.cookmate.Cookmate):
+ new_file_list = []
+ for file in file_list:
+ new_file_list += etree.parse(BytesIO(import_zip.read(file.filename))).getroot().getchildren()
+ il.total_recipes = len(new_file_list)
+ file_list = new_file_list
+
for z in file_list:
try:
- if isinstance(z, Tag):
+ if not hasattr(z, 'filename'):
recipe = self.get_recipe_from_file(z)
else:
recipe = self.get_recipe_from_file(BytesIO(import_zip.read(z.filename)))
diff --git a/cookbook/views/import_export.py b/cookbook/views/import_export.py
index 7875fa832..daa55d432 100644
--- a/cookbook/views/import_export.py
+++ b/cookbook/views/import_export.py
@@ -15,6 +15,7 @@ from cookbook.helper.recipe_search import RecipeSearch
from cookbook.integration.cheftap import ChefTap
from cookbook.integration.chowdown import Chowdown
from cookbook.integration.cookbookapp import CookBookApp
+from cookbook.integration.cookmate import Cookmate
from cookbook.integration.copymethat import CopyMeThat
from cookbook.integration.default import Default
from cookbook.integration.domestica import Domestica
@@ -77,6 +78,8 @@ def get_integration(request, export_type):
return PDFexport(request, export_type)
if export_type == ImportExportBase.MELARECIPES:
return MelaRecipes(request, export_type)
+ if export_type == ImportExportBase.COOKMATE:
+ return Cookmate(request, export_type)
@group_required('user')
diff --git a/docs/features/import_export.md b/docs/features/import_export.md
index 18ead2820..85c3b1229 100644
--- a/docs/features/import_export.md
+++ b/docs/features/import_export.md
@@ -38,7 +38,8 @@ Overview of the capabilities of the different integrations.
| Plantoeat | ✔️ | ❌ | ✔ |
| CookBookApp | ✔️ | ⌚ | ✔️ |
| CopyMeThat | ✔️ | ❌ | ✔️ |
-| CopyMeThat | ✔️ | ⌚ | ✔️ |
+| Melarecipes | ✔️ | ⌚ | ✔️ |
+| Cookmate | ✔️ | ⌚ | ✔️ |
| PDF (experimental) | ⌚️ | ✔️ | ✔️ |
✔️ = implemented, ❌ = not implemented and not possible/planned, ⌚ = not yet implemented
diff --git a/vue/src/utils/integration.js b/vue/src/utils/integration.js
index 94ab94693..a2e6db1bb 100644
--- a/vue/src/utils/integration.js
+++ b/vue/src/utils/integration.js
@@ -5,6 +5,7 @@ export const INTEGRATIONS = [
{id: 'CHEFTAP', name: "Cheftap", import: true, export: false},
{id: 'CHOWDOWN', name: "Chowdown", import: true, export: false},
{id: 'COOKBOOKAPP', name: "CookBookApp", import: true, export: false},
+ {id: 'COOKMATE', name: "Cookmate", import: true, export: false},
{id: 'COPYMETHAT', name: "CopyMeThat", import: true, export: false},
{id: 'DOMESTICA', name: "Domestica", import: true, export: false},
{id: 'MEALIE', name: "Mealie", import: true, export: false},