From d5016030b6ab3c028d0bfca7fc4a7ba86a8b40d3 Mon Sep 17 00:00:00 2001 From: Dallin Miner Date: Sun, 9 Feb 2025 17:30:01 +0000 Subject: [PATCH] Add cookn migration --- .../documentation/getting-started/features.md | 1 + .../getting-started/introduction.md | 1 + docs/docs/overrides/api.html | 2 +- frontend/lang/messages/af-ZA.json | 4 + frontend/lang/messages/ar-SA.json | 4 + frontend/lang/messages/bg-BG.json | 4 + frontend/lang/messages/ca-ES.json | 4 + frontend/lang/messages/cs-CZ.json | 4 + frontend/lang/messages/da-DK.json | 4 + frontend/lang/messages/de-DE.json | 4 + frontend/lang/messages/el-GR.json | 4 + frontend/lang/messages/en-GB.json | 4 + frontend/lang/messages/en-US.json | 4 + frontend/lang/messages/es-ES.json | 4 + frontend/lang/messages/et-EE.json | 4 + frontend/lang/messages/fi-FI.json | 4 + frontend/lang/messages/fr-BE.json | 4 + frontend/lang/messages/fr-CA.json | 4 + frontend/lang/messages/fr-FR.json | 4 + frontend/lang/messages/gl-ES.json | 4 + frontend/lang/messages/he-IL.json | 4 + frontend/lang/messages/hr-HR.json | 4 + frontend/lang/messages/hu-HU.json | 4 + frontend/lang/messages/is-IS.json | 4 + frontend/lang/messages/it-IT.json | 4 + frontend/lang/messages/ja-JP.json | 4 + frontend/lang/messages/ko-KR.json | 4 + frontend/lang/messages/lt-LT.json | 4 + frontend/lang/messages/lv-LV.json | 4 + frontend/lang/messages/nl-NL.json | 4 + frontend/lang/messages/no-NO.json | 4 + frontend/lang/messages/pl-PL.json | 4 + frontend/lang/messages/pt-BR.json | 4 + frontend/lang/messages/pt-PT.json | 4 + frontend/lang/messages/ro-RO.json | 4 + frontend/lang/messages/ru-RU.json | 4 + frontend/lang/messages/sk-SK.json | 4 + frontend/lang/messages/sl-SI.json | 4 + frontend/lang/messages/sr-SP.json | 4 + frontend/lang/messages/sv-SE.json | 4 + frontend/lang/messages/tr-TR.json | 4 + frontend/lang/messages/uk-UA.json | 4 + frontend/lang/messages/vi-VN.json | 4 + frontend/lang/messages/zh-CN.json | 4 + frontend/lang/messages/zh-TW.json | 4 + frontend/lib/api/types/group.ts | 3 +- frontend/pages/group/migrations.vue | 34 +++ mealie/routes/groups/controller_migrations.py | 2 + mealie/schema/group/group_migration.py | 1 + mealie/services/migrations/__init__.py | 1 + mealie/services/migrations/cookn.py | 239 ++++++++++++++++++ tests/data/__init__.py | 2 + .../test_recipe_migrations.py | 7 + 53 files changed, 459 insertions(+), 2 deletions(-) create mode 100644 mealie/services/migrations/cookn.py diff --git a/docs/docs/documentation/getting-started/features.md b/docs/docs/documentation/getting-started/features.md index 7962bb292..2bdc7cb61 100644 --- a/docs/docs/documentation/getting-started/features.md +++ b/docs/docs/documentation/getting-started/features.md @@ -22,6 +22,7 @@ Mealie supports importing recipes from a few other sources besides websites. Cur - Recipe Keeper - Copy Me That - My Recipe Box +- DVO Cook'n X3 You can access these options on your installation at the `/group/migrations` page on your installation. If you'd like to see another source added, feel free to request so on Github. diff --git a/docs/docs/documentation/getting-started/introduction.md b/docs/docs/documentation/getting-started/introduction.md index 25648deaf..7503bdea8 100644 --- a/docs/docs/documentation/getting-started/introduction.md +++ b/docs/docs/documentation/getting-started/introduction.md @@ -28,6 +28,7 @@ Mealie is a self hosted recipe manager and meal planner with a RestAPI backend a - Copy Me That - Paprika - Tandoor Recipes + - DVO Cook'n X3 - Random Meal Plan generation - Advanced rule configuration to fine tune random recipes diff --git a/docs/docs/overrides/api.html b/docs/docs/overrides/api.html index a6f596205..59339890d 100644 --- a/docs/docs/overrides/api.html +++ b/docs/docs/overrides/api.html @@ -14,7 +14,7 @@
diff --git a/frontend/lang/messages/af-ZA.json b/frontend/lang/messages/af-ZA.json index 53d694a44..05da7144a 100644 --- a/frontend/lang/messages/af-ZA.json +++ b/frontend/lang/messages/af-ZA.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "Recipe Keeper", "description-long": "Mealie can import recipes from Recipe Keeper. Export your recipes in zip format, then upload the .zip file below." + }, + "cookn": { + "description-long": "Maaltie kan resepte van DVO Cook'n x3 invoer. \nVoer 'n kookboek of spyskaart uit in die \"Cook'n\" -formaat, hernoem die uitvoeruitbreiding na .zip en laai dan die .zip hieronder op.", + "title": "Dvo cook'n x3" } }, "new-recipe": { diff --git a/frontend/lang/messages/ar-SA.json b/frontend/lang/messages/ar-SA.json index 1d2efefbc..07af7fd1f 100644 --- a/frontend/lang/messages/ar-SA.json +++ b/frontend/lang/messages/ar-SA.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "مدير الوصفة", "description-long": "Mealie can import recipes from Recipe Keeper. Export your recipes in zip format, then upload the .zip file below." + }, + "cookn": { + "description-long": "يمكن لوجبة الاستيراد وصفات من DVO Cook'n X3. \nقم بتصدير كتاب طبخ أو قائمة بتنسيق \"cook'n\" ، وقم بإعادة تسمية امتداد التصدير إلى .zip ، ثم قم بتحميل .zip أدناه.", + "title": "DVO Cook'n x3" } }, "new-recipe": { diff --git a/frontend/lang/messages/bg-BG.json b/frontend/lang/messages/bg-BG.json index f3f8e332d..07585ee05 100644 --- a/frontend/lang/messages/bg-BG.json +++ b/frontend/lang/messages/bg-BG.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "Recipe Keeper", "description-long": "Mealie can import recipes from Recipe Keeper. Export your recipes in zip format, then upload the .zip file below." + }, + "cookn": { + "description-long": "Meadie може да импортира рецепти от DVO Cook'n X3. \nЕкспортирайте готварска книга или меню във формат \"Cook'n\", преименувайте разширението за експортиране на .zip, след което качете .zip по -долу.", + "title": "DVO Cook'n X3" } }, "new-recipe": { diff --git a/frontend/lang/messages/ca-ES.json b/frontend/lang/messages/ca-ES.json index 7c1a73ccd..259b30334 100644 --- a/frontend/lang/messages/ca-ES.json +++ b/frontend/lang/messages/ca-ES.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "Recipe Keeper", "description-long": "Mealie pot importar receptes de RecipeKeeper. Exporta les teves receptes en format zip, després puja el fitxer .zip aquí sota." + }, + "cookn": { + "description-long": "Mealie pot importar receptes de DVO Cook'n X3. \nExporteu un llibre de cuina o un menú en el format \"Cook'n\", canvieu el nom de l'extensió d'exportació a .zip i, a continuació, pengeu el .zip següent.", + "title": "Dvo cuin'n x3" } }, "new-recipe": { diff --git a/frontend/lang/messages/cs-CZ.json b/frontend/lang/messages/cs-CZ.json index 230e509ea..2db72974b 100644 --- a/frontend/lang/messages/cs-CZ.json +++ b/frontend/lang/messages/cs-CZ.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "Recipe Keeper", "description-long": "Mealie může importovat recepty z Recipe Keeper. Exportujte recepty v zip formátu, poté nahrajte .zip soubor níže." + }, + "cookn": { + "description-long": "Mearie může importovat recepty z DVO Cook'n X3. \nExportujte kuchařku nebo nabídku ve formátu „Cook'n“, přejmenujte rozšíření exportu na .zip a poté nahrajte níže uvedený .ZIP.", + "title": "Dvo Cook'n X3" } }, "new-recipe": { diff --git a/frontend/lang/messages/da-DK.json b/frontend/lang/messages/da-DK.json index 2da980f33..6354ca437 100644 --- a/frontend/lang/messages/da-DK.json +++ b/frontend/lang/messages/da-DK.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "Recipe Keeper", "description-long": "Mealie kan importere opskrifter fra Recipe Keeper. Eksportér dine opskrifter i zip-format, og upload derefter .zip-filen nedenfor." + }, + "cookn": { + "description-long": "Mealie kan importere opskrifter fra Dvo Cook'n X3. \nEksporter en kogebog eller menu i formatet \"Cook'n\", omdøb eksportforlængelsen til .zip, og upload derefter .zip nedenfor.", + "title": "Dvo Cook'n X3" } }, "new-recipe": { diff --git a/frontend/lang/messages/de-DE.json b/frontend/lang/messages/de-DE.json index d1ce0ae27..5578df0dd 100644 --- a/frontend/lang/messages/de-DE.json +++ b/frontend/lang/messages/de-DE.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "Recipe Keeper", "description-long": "Mealie kann Rezepte von Recipe Keeper importieren. Exportiere deine Rezepte im ZIP-Format und lade dann unten die .zip Datei hoch.\n" + }, + "cookn": { + "description-long": "Mahlzeit kann Rezepte von DVO Cook'n x3 importieren. \nExportieren Sie ein Kochbuch oder Menü im Format \"Cook'n\", benennen Sie die Export -Erweiterung in .zip um und laden Sie den unten stehenden .zip hoch.", + "title": "DVO Cook'n x3" } }, "new-recipe": { diff --git a/frontend/lang/messages/el-GR.json b/frontend/lang/messages/el-GR.json index 595b3e01d..29ced6025 100644 --- a/frontend/lang/messages/el-GR.json +++ b/frontend/lang/messages/el-GR.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "Recipe Keeper", "description-long": "Το Mealie μπορεί να εισάγει συνταγές από το Recipe Keeper. Εξάγετε τις συνταγές σας σε μορφή zip, στη συνέχεια, ανεβάστε το αρχείο .zip παρακάτω." + }, + "cookn": { + "description-long": "Το Mealie μπορεί να εισάγει συνταγές από το DVO Cook'n X3. \nΕξαγωγή βιβλίου μαγειρικής ή μενού στη μορφή \"Cook'n\", μετονομάστε την επέκταση εξαγωγής σε .zip και στη συνέχεια μεταφορτώστε το .zip παρακάτω.", + "title": "Dvo cook'n x3" } }, "new-recipe": { diff --git a/frontend/lang/messages/en-GB.json b/frontend/lang/messages/en-GB.json index c4e8f9209..313931564 100644 --- a/frontend/lang/messages/en-GB.json +++ b/frontend/lang/messages/en-GB.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "Recipe Keeper", "description-long": "Mealie can import recipes from Recipe Keeper. Export your recipes in zip format, then upload the .zip file below." + }, + "cookn": { + "description-long": "Mealie can import recipes from DVO Cook'n X3. Export a cookbook or menu in the \"Cook'n\" format, rename the export extension to .zip, then upload the .zip below.", + "title": "DVO Cook'n X3" } }, "new-recipe": { diff --git a/frontend/lang/messages/en-US.json b/frontend/lang/messages/en-US.json index fb25f51f3..77dd600ff 100644 --- a/frontend/lang/messages/en-US.json +++ b/frontend/lang/messages/en-US.json @@ -397,6 +397,10 @@ "description-long": "Mealie can import recipes from Tandoor. Export your data in the \"Default\" format, then upload the .zip below.", "title": "Tandoor Recipes" }, + "cookn": { + "description-long": "Mealie can import recipes from DVO Cook'n X3. Export a cookbook or menu in the \"Cook'n\" format, rename the export extension to .zip, then upload the .zip below.", + "title": "DVO Cook'n X3" + }, "recipe-data-migrations": "Recipe Data Migrations", "recipe-data-migrations-explanation": "Recipes can be migrated from another supported application to Mealie. This is a great way to get started with Mealie.", "coming-from-another-application-or-an-even-older-version-of-mealie": "Coming from another application or an even older version of Mealie? Check out migrations and see if your data can be imported.", diff --git a/frontend/lang/messages/es-ES.json b/frontend/lang/messages/es-ES.json index 39effa9a8..b46e8ce31 100644 --- a/frontend/lang/messages/es-ES.json +++ b/frontend/lang/messages/es-ES.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "Recipe Keeper", "description-long": "Mealie puede importar recetas de Recipe Keeper. Exporta tus recetas en formato zip y luego sube el archivo a continuación." + }, + "cookn": { + "description-long": "Mealie puede importar recetas de Dvo Cook'n X3. \nExporte un libro de cocina o menú en el formato \"Cook'n\", cambie el nombre de la extensión de exportación a .zip, luego cargue el .zip a continuación.", + "title": "Dvo Cook'n x3" } }, "new-recipe": { diff --git a/frontend/lang/messages/et-EE.json b/frontend/lang/messages/et-EE.json index 4da61f761..843d6bb45 100644 --- a/frontend/lang/messages/et-EE.json +++ b/frontend/lang/messages/et-EE.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "Recipe Keeper", "description-long": "Mealie saab importida retsepte Recipe Keeper-st. Ekspordi oma retsept .zip formaadis ning lae .zip fail allolevasse kasti." + }, + "cookn": { + "description-long": "Söögikord saab importida retsepte DVO Cook'n X3 -st. \nEksportige kokaraamat või menüü \"Cook'n\" vormingus, nimetage ekspordi laiend ümber .zip, seejärel laadige .zip üles allpool.", + "title": "Dvo Cook'n x3" } }, "new-recipe": { diff --git a/frontend/lang/messages/fi-FI.json b/frontend/lang/messages/fi-FI.json index 9722dd059..82b4ca537 100644 --- a/frontend/lang/messages/fi-FI.json +++ b/frontend/lang/messages/fi-FI.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "Reseptinsäilyttäjä", "description-long": "Mealieen voi tuoda reseptejä reseptinsäilyttäjästä. Tuo reseptit zip-muodossa ja sitten lataa zip-tiedosto alempaa." + }, + "cookn": { + "description-long": "Mearie voi tuoda reseptejä DVO Cook'n X3: lta. \nVie keittokirja tai -valikko \"Cook'n\" -muodossa, nimeä vienti laajennus .zipiksi ja lataa sitten alla oleva .zip.", + "title": "DVO Cook'n x3" } }, "new-recipe": { diff --git a/frontend/lang/messages/fr-BE.json b/frontend/lang/messages/fr-BE.json index fb4ea1ea2..be595d8f8 100644 --- a/frontend/lang/messages/fr-BE.json +++ b/frontend/lang/messages/fr-BE.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "Recipe Keeper", "description-long": "Mealie peut importer des recettes depuis Recipe Keeper. Exportez vos recettes au format Zip, puis téléversez le fichier .zip ci-dessous." + }, + "cookn": { + "description-long": "Le repas peut importer des recettes à partir de DVO Cook'n x3. \nExportez un livre de cuisine ou un menu au format \"Cook'n\", renommez l'extension d'exportation vers .zip, puis téléchargez le .zip ci-dessous.", + "title": "DVO Cook'n x3" } }, "new-recipe": { diff --git a/frontend/lang/messages/fr-CA.json b/frontend/lang/messages/fr-CA.json index bd4f8b366..791eb0412 100644 --- a/frontend/lang/messages/fr-CA.json +++ b/frontend/lang/messages/fr-CA.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "Recipe Keeper", "description-long": "Mealie peut importer des recettes depuis Recipe Keeper. Exportez vos recettes au format Zip, puis téléversez le fichier .zip ci-dessous." + }, + "cookn": { + "description-long": "Le repas peut importer des recettes à partir de DVO Cook'n x3. \nExportez un livre de cuisine ou un menu au format \"Cook'n\", renommez l'extension d'exportation vers .zip, puis téléchargez le .zip ci-dessous.", + "title": "DVO Cook'n x3" } }, "new-recipe": { diff --git a/frontend/lang/messages/fr-FR.json b/frontend/lang/messages/fr-FR.json index fa0ba8917..480ebd5c6 100644 --- a/frontend/lang/messages/fr-FR.json +++ b/frontend/lang/messages/fr-FR.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "Recipe Keeper", "description-long": "Mealie peut importer des recettes depuis Recipe Keeper. Exportez vos recettes au format Zip, puis téléversez le fichier .zip ci-dessous." + }, + "cookn": { + "description-long": "Le repas peut importer des recettes à partir de DVO Cook'n x3. \nExportez un livre de cuisine ou un menu au format \"Cook'n\", renommez l'extension d'exportation vers .zip, puis téléchargez le .zip ci-dessous.", + "title": "DVO Cook'n x3" } }, "new-recipe": { diff --git a/frontend/lang/messages/gl-ES.json b/frontend/lang/messages/gl-ES.json index 36611346d..959cce56b 100644 --- a/frontend/lang/messages/gl-ES.json +++ b/frontend/lang/messages/gl-ES.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "Garda Receitas", "description-long": "Mealie pode importar receitas do Garda Receitas. Exporta as túas receitas en formato zip e, a continuación, carga o ficheiro .zip abaixo." + }, + "cookn": { + "description-long": "A comida pode importar receitas de DVO Cook'n X3. \nExportar un libro de receitas ou menú no formato \"Cook'n\", cambiar o nome da extensión de exportación a .zip e logo cargar o .zip a continuación.", + "title": "DVO Cook'n x3" } }, "new-recipe": { diff --git a/frontend/lang/messages/he-IL.json b/frontend/lang/messages/he-IL.json index 5dff37dd7..52b9f6973 100644 --- a/frontend/lang/messages/he-IL.json +++ b/frontend/lang/messages/he-IL.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "שומר המתכון", "description-long": "Mealie יכולה לייבא מתכונים מ-Recipe Keeper. יש לייצא את המתכונים בפורמט zip ולהעלות אותו כאן." + }, + "cookn": { + "description-long": "SELEI יכול לייבא מתכונים מ- DVO COOK'N X3. \nייצא ספר בישול או תפריט בפורמט \"Cook'n\", שנה את שם סיומת הייצוא ל- .zip, ואז העלה את ה- .zip למטה.", + "title": "DVO COOK'N X3" } }, "new-recipe": { diff --git a/frontend/lang/messages/hr-HR.json b/frontend/lang/messages/hr-HR.json index 690730c55..f31e1e4d1 100644 --- a/frontend/lang/messages/hr-HR.json +++ b/frontend/lang/messages/hr-HR.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "Recipe Keeper", "description-long": "Mealie can import recipes from Recipe Keeper. Export your recipes in zip format, then upload the .zip file below." + }, + "cookn": { + "description-long": "Obloge može uvesti recepte iz DVO Cook'n X3. \nIzvezite kuharsku knjigu ili izbornik u formatu \"Cook'n\", preimenujte ekstenziju izvoza u .zip, a zatim prenesite .zip u nastavku.", + "title": "Dvo kuhar x3" } }, "new-recipe": { diff --git a/frontend/lang/messages/hu-HU.json b/frontend/lang/messages/hu-HU.json index de8374d5c..2dfc0cfb7 100644 --- a/frontend/lang/messages/hu-HU.json +++ b/frontend/lang/messages/hu-HU.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "Recipe Keeper", "description-long": "A Mealie képes recepteket importálni a Recipe Keeperből. Exportálja a receptjeit zip formátumban, majd töltse fel a .zip fájlt az oldal alján." + }, + "cookn": { + "description-long": "A Mealie recepteket importálhat a DVO Cook'n X3 -ból. \nExportáljon szakácskönyvet vagy menüt a \"Cook'n\" formátumban, nevezze át az export kiterjesztést .zip -re, majd töltse fel az alábbi .zip -t.", + "title": "Dvo cook'n x3" } }, "new-recipe": { diff --git a/frontend/lang/messages/is-IS.json b/frontend/lang/messages/is-IS.json index 8b5067d57..05227767d 100644 --- a/frontend/lang/messages/is-IS.json +++ b/frontend/lang/messages/is-IS.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "Recipe Keeper", "description-long": "Mealie can import recipes from Recipe Keeper. Export your recipes in zip format, then upload the .zip file below." + }, + "cookn": { + "description-long": "Mealie getur flutt inn uppskriftir frá DVO Cook'n x3. \nFlyttu út matreiðslubók eða valmynd á „Cook'n“ sniði, endurnefndu útflutningsframlenginguna í .zip og hlaðið síðan upp .zipinu hér að neðan.", + "title": "Dvo cook'n x3" } }, "new-recipe": { diff --git a/frontend/lang/messages/it-IT.json b/frontend/lang/messages/it-IT.json index 658d59cc9..c203ca2e7 100644 --- a/frontend/lang/messages/it-IT.json +++ b/frontend/lang/messages/it-IT.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "Recipe Keeper", "description-long": "Mealie può importare ricette da Recipe Keeper. Esporta le tue ricette in formato zip, quindi carica lo .zip qui sotto." + }, + "cookn": { + "description-long": "Mealie può importare ricette da Dvo Cook'n X3. \nEsporta un libro di cucina o un menu nel formato \"Cook'n\", rinomina l'estensione di esportazione in .Zip, quindi caricare il .zip di seguito.", + "title": "Dvo cook'n x3" } }, "new-recipe": { diff --git a/frontend/lang/messages/ja-JP.json b/frontend/lang/messages/ja-JP.json index 4742eb304..f2aa637e1 100644 --- a/frontend/lang/messages/ja-JP.json +++ b/frontend/lang/messages/ja-JP.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "Recipe Keeper", "description-long": "MealieはRecipe Keeperからレシピをインポートできます。レシピをzip形式でエクスポートし、以下に.zipファイルをアップロードしてください。" + }, + "cookn": { + "description-long": "MealieはDVO Cook'n X3からレシピをインポートできます。 \n「cook'n」形式でクックブックまたはメニューをエクスポートし、エクスポート拡張子の名前を.zipに変更してから、.zipを下にアップロードします。", + "title": "dvo cook'n x3" } }, "new-recipe": { diff --git a/frontend/lang/messages/ko-KR.json b/frontend/lang/messages/ko-KR.json index 7254539f3..de5503675 100644 --- a/frontend/lang/messages/ko-KR.json +++ b/frontend/lang/messages/ko-KR.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "레시피 보관함", "description-long": "Mealie는 Recipe Keeper에서 레시피를 가져올 수 있습니다. 레시피를 zip 형식으로 내보낸 다음 아래의 .zip 파일을 업로드하세요." + }, + "cookn": { + "description-long": "Sealie는 DVO Cook'n X3에서 레시피를 수입 할 수 있습니다. \nCookbook 또는 메뉴를 \"Cook'n\"형식으로 내보내고 내보내기 확장자 이름을 .zip로 바꾸고 아래 .zip을 업로드하십시오.", + "title": "DVO Cook'n X3" } }, "new-recipe": { diff --git a/frontend/lang/messages/lt-LT.json b/frontend/lang/messages/lt-LT.json index bbe70e8c5..8f8563672 100644 --- a/frontend/lang/messages/lt-LT.json +++ b/frontend/lang/messages/lt-LT.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "Recipe Keeper", "description-long": "Mealie can import recipes from Recipe Keeper. Export your recipes in zip format, then upload the .zip file below." + }, + "cookn": { + "description-long": "Maitinimas gali importuoti receptus iš „DVO Cook'n X3“. \nEksportuokite kulinarijos knygą ar meniu „Cook'n“ formatu, pervardykite eksporto plėtinį į .zip, tada įkelkite .zip žemiau.", + "title": "DVO Cook'n x3" } }, "new-recipe": { diff --git a/frontend/lang/messages/lv-LV.json b/frontend/lang/messages/lv-LV.json index 450b8b8bc..f3c1a9e02 100644 --- a/frontend/lang/messages/lv-LV.json +++ b/frontend/lang/messages/lv-LV.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "Recepšu turētājs", "description-long": "Mealie var importēt receptes no Recipe Keeper. Eksportējiet savas receptes zip formātā, pēc tam augšupielādējiet zemāk esošo.zip failu." + }, + "cookn": { + "description-long": "Mealie var importēt receptes no DVO Cook'n X3. \nEksportējiet pavārgrāmatu vai izvēlni formātā \"Cook'n\", pārdēvējiet eksporta paplašinājumu uz .zip, pēc tam augšupielādējiet zemāk esošo .zip.", + "title": "DVO Cook'n X3" } }, "new-recipe": { diff --git a/frontend/lang/messages/nl-NL.json b/frontend/lang/messages/nl-NL.json index f1bf5c94f..00a10b23a 100644 --- a/frontend/lang/messages/nl-NL.json +++ b/frontend/lang/messages/nl-NL.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "Recipe Keeper", "description-long": "Mealie kan recepten importeren van Recipe Keeper. Exporteer de recepten als .zip. Dat bestand kan je hier dan uploaden." + }, + "cookn": { + "description-long": "Maaltijd kan recepten importeren van DVO Cook'n X3. \nExporteer een kookboek of menu in het formaat \"Cook'n\", hernoem de exportuitbreiding naar .zip en upload vervolgens de .zip hieronder.", + "title": "Dvo cook'n x3" } }, "new-recipe": { diff --git a/frontend/lang/messages/no-NO.json b/frontend/lang/messages/no-NO.json index 35cacd6ed..ad0633c7e 100644 --- a/frontend/lang/messages/no-NO.json +++ b/frontend/lang/messages/no-NO.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "Recipe Keeper", "description-long": "Meali kan importere oppskrifter fra Recipe Keeper. Eksporter oppskrifter i zip-format, og last deretter opp .zip-filen nedenfor." + }, + "cookn": { + "description-long": "Mealie kan importere oppskrifter fra DVO Cook'n X3. \nEksporter en kokebok eller meny i \"Cook'n\" -formatet, gi nytt navn til eksportforlengelsen til .zip, og last deretter opp .zip nedenfor.", + "title": "DVO Cook'n x3" } }, "new-recipe": { diff --git a/frontend/lang/messages/pl-PL.json b/frontend/lang/messages/pl-PL.json index c7a25ee22..47b6559b8 100644 --- a/frontend/lang/messages/pl-PL.json +++ b/frontend/lang/messages/pl-PL.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "Recipe Keeper", "description-long": "Mealie może importować przepisy z Recipe Keeper. Eksportuj przepisy w formacie zip, a następnie prześlij plik .zip poniżej." + }, + "cookn": { + "description-long": "Posiłek może importować przepisy z DVO Cook'N x3. \nWyeksportuj książkę kucharską lub menu w formacie „Cook'n”, zmień nazwę rozszerzenia eksportu na .zip, a następnie prześlij .zip poniżej.", + "title": "DVO Cook'n x3" } }, "new-recipe": { diff --git a/frontend/lang/messages/pt-BR.json b/frontend/lang/messages/pt-BR.json index 5560aaae7..8ed6d1ddb 100644 --- a/frontend/lang/messages/pt-BR.json +++ b/frontend/lang/messages/pt-BR.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "Recipe Keeper", "description-long": "Mealie pode importar receitas do Recipe Keeper. Exporte suas receitas no formato ZIP, então envie o arquivo abaixo." + }, + "cookn": { + "description-long": "O Mealie pode importar receitas do DVO Cook'n X3. \nExport um livro de receitas ou menu no formato \"Cook'n\", renomeie a extensão de exportação para .zip e envie o .zip abaixo.", + "title": "Dvo Cook'n X3" } }, "new-recipe": { diff --git a/frontend/lang/messages/pt-PT.json b/frontend/lang/messages/pt-PT.json index 684b6b797..ae0bd5417 100644 --- a/frontend/lang/messages/pt-PT.json +++ b/frontend/lang/messages/pt-PT.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "Recipe Keeper", "description-long": "O Mealie pode importar receitas do Recipe Keeper. Exporte as suas receitas em formato zip e, em seguida, carregue o ficheiro .zip abaixo." + }, + "cookn": { + "description-long": "O Mealie pode importar receitas do DVO Cook'n X3. \nExport um livro de receitas ou menu no formato \"Cook'n\", renomeie a extensão de exportação para .zip e envie o .zip abaixo.", + "title": "Dvo Cook'n X3" } }, "new-recipe": { diff --git a/frontend/lang/messages/ro-RO.json b/frontend/lang/messages/ro-RO.json index 1066d2402..3be476c9c 100644 --- a/frontend/lang/messages/ro-RO.json +++ b/frontend/lang/messages/ro-RO.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "Oraganizator de rețete", "description-long": "Mealie poate importa rețete din \"Recipe Keeper\". Exportă rețetele tale în format ZIP, apoi încarcă fișierul .zip mai jos." + }, + "cookn": { + "description-long": "Mealie poate importa rețete de la DVO Cook'n X3. \nExportați o carte de bucate sau un meniu în formatul „Cook'n”, redenumiți extensia de export în .zip, apoi încărcați .zip de mai jos.", + "title": "DVO Cook'n X3" } }, "new-recipe": { diff --git a/frontend/lang/messages/ru-RU.json b/frontend/lang/messages/ru-RU.json index 9671a5395..61dc07378 100644 --- a/frontend/lang/messages/ru-RU.json +++ b/frontend/lang/messages/ru-RU.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "Recipe Keeper", "description-long": "Mealie can import recipes from Recipe Keeper. Export your recipes in zip format, then upload the .zip file below." + }, + "cookn": { + "description-long": "Mealie может импортировать рецепты из DVO Cook'n x3. \nЭкспортируйте поваренную книгу или меню в формате «Cook'n», переименовать расширение экспорта в .zip, затем загрузите .zip ниже.", + "title": "Dvo Cook'n x3" } }, "new-recipe": { diff --git a/frontend/lang/messages/sk-SK.json b/frontend/lang/messages/sk-SK.json index 2deebd2cf..11833c012 100644 --- a/frontend/lang/messages/sk-SK.json +++ b/frontend/lang/messages/sk-SK.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "Strážca receptov", "description-long": "Mealie dokáže importovať recepty z Recipe Keeper. Exportujte recepty vo formáte Zip a nižšie nahrajte zip súbor." + }, + "cookn": { + "description-long": "SOMEIE môže importovať recepty z DVO Cook'n X3. \nExportujte kuchárku alebo ponuku vo formáte „Cook'n“, premenujte rozšírenie exportu na .zip a potom nahrajte nižšie.", + "title": "Dvo cook'n x3" } }, "new-recipe": { diff --git a/frontend/lang/messages/sl-SI.json b/frontend/lang/messages/sl-SI.json index c928ad3a7..6450fddc2 100644 --- a/frontend/lang/messages/sl-SI.json +++ b/frontend/lang/messages/sl-SI.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "Recipe Keeper", "description-long": "Mealie lahko uvozi recepte iz Recipe Keeper. Izvozi svoje recepte v .zip formatu, nato spodaj naloži .zip datoteko." + }, + "cookn": { + "description-long": "Meanie lahko uvozi recepte iz DVO Cook'n X3. \nIzvozite kuharsko knjigo ali meni v obliki \"Cook'n\", preimenujte razširitev izvoza v .zip in nato naložite spodaj .zip.", + "title": "Dvo cook'n x3" } }, "new-recipe": { diff --git a/frontend/lang/messages/sr-SP.json b/frontend/lang/messages/sr-SP.json index cc002e7f9..c17c86102 100644 --- a/frontend/lang/messages/sr-SP.json +++ b/frontend/lang/messages/sr-SP.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "Recipe Keeper", "description-long": "Mealie can import recipes from Recipe Keeper. Export your recipes in zip format, then upload the .zip file below." + }, + "cookn": { + "description-long": "Mealie can import recipes from DVO Cook'n X3. Export a cookbook or menu in the \"Cook'n\" format, rename the export extension to .zip, then upload the .zip below.", + "title": "DVO Cook'n X3" } }, "new-recipe": { diff --git a/frontend/lang/messages/sv-SE.json b/frontend/lang/messages/sv-SE.json index d63aff4f2..b739def15 100644 --- a/frontend/lang/messages/sv-SE.json +++ b/frontend/lang/messages/sv-SE.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "Recipe Keeper", "description-long": "Mealie kan importera recept från Recept Keeper. Exportera dina recept i zip-format, ladda sedan upp .zip-filen här under." + }, + "cookn": { + "description-long": "Mealie kan importera recept från DVO Cook'n X3. \nExportera en kokbok eller meny i formatet \"Cook'n\", byta namn på exportförlängningen till .zip och ladda sedan upp .zip nedan.", + "title": "Dvo Cook'n x3" } }, "new-recipe": { diff --git a/frontend/lang/messages/tr-TR.json b/frontend/lang/messages/tr-TR.json index f0aced05c..96c59f2f2 100644 --- a/frontend/lang/messages/tr-TR.json +++ b/frontend/lang/messages/tr-TR.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "Recipe Keeper", "description-long": "Mealie tarifleri Recipe Keeper'dan içe aktarabilir. Tariflerinizi zip formatında dışa aktarın, daha sonra .zip dosyasını aşağıdan yükleyin." + }, + "cookn": { + "description-long": "Mealie, DVO Cook'n X3'ten tarifleri içe aktarabilir. \n\"Cook'n\" biçiminde bir yemek kitabı veya menüsü dışa aktarın, dışa aktarma uzantısını .zip'e yeniden adlandırın, ardından aşağıdaki .zip'i yükleyin.", + "title": "Dvo Cook'n x3" } }, "new-recipe": { diff --git a/frontend/lang/messages/uk-UA.json b/frontend/lang/messages/uk-UA.json index 0209bc476..592195400 100644 --- a/frontend/lang/messages/uk-UA.json +++ b/frontend/lang/messages/uk-UA.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "Recipe Keeper", "description-long": "Mealie може імпортувати рецепти з Recipe Keeper. Експортувати ваші рецепти у форматі zip, а потім відвантажте файл .zip нижче." + }, + "cookn": { + "description-long": "Mearie може імпортувати рецепти з DVO Cook'n x3. \nЕкспортуйте кулінарну книгу або меню у форматі \"Cook'n\", перейменуйте розширення експорту на .zip, а потім завантажте .zip нижче.", + "title": "Dvo cook'n x3" } }, "new-recipe": { diff --git a/frontend/lang/messages/vi-VN.json b/frontend/lang/messages/vi-VN.json index fe4ea2999..d6a3fe5f2 100644 --- a/frontend/lang/messages/vi-VN.json +++ b/frontend/lang/messages/vi-VN.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "Recipe Keeper", "description-long": "Mealie can import recipes from Recipe Keeper. Export your recipes in zip format, then upload the .zip file below." + }, + "cookn": { + "description-long": "Mealie có thể nhập công thức nấu ăn từ DVO Cook'n X3. \nXuất một cuốn sách nấu ăn hoặc menu ở định dạng \"Cook'n\", đổi tên phần mở rộng xuất sang .zip, sau đó tải lên .zip bên dưới.", + "title": "DVO Cook'n X3" } }, "new-recipe": { diff --git a/frontend/lang/messages/zh-CN.json b/frontend/lang/messages/zh-CN.json index f5a069b04..ac9a2c4c1 100644 --- a/frontend/lang/messages/zh-CN.json +++ b/frontend/lang/messages/zh-CN.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "食谱保存者", "description-long": "Mealie can import recipes from Recipe Keeper. Export your recipes in zip format, then upload the .zip file below." + }, + "cookn": { + "description-long": "用餐可以从DVO Cook'n X3进口食谱。\n以“库克”格式导出食谱或菜单,将导出扩展名重命名为.zip,然后在下面上传.zip。", + "title": "DVO Cook'n X3" } }, "new-recipe": { diff --git a/frontend/lang/messages/zh-TW.json b/frontend/lang/messages/zh-TW.json index bceab2bac..fb07abe02 100644 --- a/frontend/lang/messages/zh-TW.json +++ b/frontend/lang/messages/zh-TW.json @@ -419,6 +419,10 @@ "recipekeeper": { "title": "Recipe Keeper", "description-long": "Mealie can import recipes from Recipe Keeper. Export your recipes in zip format, then upload the .zip file below." + }, + "cookn": { + "description-long": "用餐可以從DVO Cook'n X3進口食譜。\n以“庫克”格式導出食譜或菜單,將導出擴展名重命名為.zip,然後在下面上傳.zip。", + "title": "DVO Cook'n X3" } }, "new-recipe": { diff --git a/frontend/lib/api/types/group.ts b/frontend/lib/api/types/group.ts index bc2fbcf62..36e2b991d 100644 --- a/frontend/lib/api/types/group.ts +++ b/frontend/lib/api/types/group.ts @@ -14,7 +14,8 @@ export type SupportedMigrations = | "tandoor" | "plantoeat" | "myrecipebox" - | "recipekeeper"; + | "recipekeeper" + | "cookn"; export interface CreateGroupPreferences { privateGroup?: boolean; diff --git a/frontend/pages/group/migrations.vue b/frontend/pages/group/migrations.vue index 1f8cdd947..3d69a66f8 100644 --- a/frontend/pages/group/migrations.vue +++ b/frontend/pages/group/migrations.vue @@ -84,6 +84,7 @@ const MIGRATIONS = { plantoeat: "plantoeat", recipekeeper: "recipekeeper", tandoor: "tandoor", + cookn: "cookn" }; export default defineComponent({ @@ -140,6 +141,10 @@ export default defineComponent({ text: i18n.tc("migration.tandoor.title"), value: MIGRATIONS.tandoor, }, + { + text: i18n.tc("migration.cookn.title"), + value: MIGRATIONS.cookn, + }, ]; const _content = { [MIGRATIONS.mealie]: { @@ -372,6 +377,35 @@ export default defineComponent({ } ], }, + [MIGRATIONS.cookn]: { + text: i18n.tc("migration.cookn.description-long"), + acceptedFileType: ".zip", + tree: [ + { + id: 1, + icon: $globals.icons.zip, + name: "cookn.zip", + children: [ + { id: 2, name: "temp_brand.dsv", icon: $globals.icons.codeJson }, + { id: 3, name: "temp_chapter_desc.dsv", icon: $globals.icons.codeJson }, + { id: 4, name: "temp_chapter.dsv", icon: $globals.icons.codeJson }, + { id: 5, name: "temp_cookBook_desc.dsv", icon: $globals.icons.codeJson }, + { id: 6, name: "temp_cookBook.dsv", icon: $globals.icons.codeJson }, + { id: 7, name: "temp_food_brand.dsv", icon: $globals.icons.codeJson }, + { id: 8, name: "temp_food_group.dsv", icon: $globals.icons.codeJson }, + { id: 9, name: "temp_food.dsv", icon: $globals.icons.codeJson }, + { id: 10, name: "temp_ingrediant.dsv", icon: $globals.icons.codeJson }, + { id: 11, name: "temp_media.dsv", icon: $globals.icons.codeJson }, + { id: 12, name: "temp_nutrient.dsv", icon: $globals.icons.codeJson }, + { id: 13, name: "temp_recipe_desc.dsv", icon: $globals.icons.codeJson }, + { id: 13, name: "temp_recipe.dsv", icon: $globals.icons.codeJson }, + { id: 13, name: "temp_unit_equivalent.dsv", icon: $globals.icons.codeJson }, + { id: 13, name: "temp_unit.dsv", icon: $globals.icons.codeJson }, + { id: 13, name: "images", icon: $globals.icons.fileImage }, + ] + } + ], + }, }; function setFileObject(fileObject: File) { diff --git a/mealie/routes/groups/controller_migrations.py b/mealie/routes/groups/controller_migrations.py index 252a55ac4..16111b8b2 100644 --- a/mealie/routes/groups/controller_migrations.py +++ b/mealie/routes/groups/controller_migrations.py @@ -11,6 +11,7 @@ from mealie.schema.reports.reports import ReportSummary from mealie.services.migrations import ( BaseMigrator, ChowdownMigrator, + CooknMigrator, CopyMeThatMigrator, MealieAlphaMigrator, MyRecipeBoxMigrator, @@ -59,6 +60,7 @@ class GroupMigrationController(BaseUserController): SupportedMigrations.plantoeat: PlanToEatMigrator, SupportedMigrations.myrecipebox: MyRecipeBoxMigrator, SupportedMigrations.recipekeeper: RecipeKeeperMigrator, + SupportedMigrations.cookn: CooknMigrator, } constructor = table.get(migration_type, None) diff --git a/mealie/schema/group/group_migration.py b/mealie/schema/group/group_migration.py index 00dc2e4ab..4dcb2d435 100644 --- a/mealie/schema/group/group_migration.py +++ b/mealie/schema/group/group_migration.py @@ -13,6 +13,7 @@ class SupportedMigrations(str, enum.Enum): plantoeat = "plantoeat" myrecipebox = "myrecipebox" recipekeeper = "recipekeeper" + cookn = "cookn" class DataMigrationCreate(MealieModel): diff --git a/mealie/services/migrations/__init__.py b/mealie/services/migrations/__init__.py index e9db4ee17..825bcfa7b 100644 --- a/mealie/services/migrations/__init__.py +++ b/mealie/services/migrations/__init__.py @@ -1,4 +1,5 @@ from .chowdown import * +from .cookn import * from .copymethat import * from .mealie_alpha import * from .myrecipebox import * diff --git a/mealie/services/migrations/cookn.py b/mealie/services/migrations/cookn.py new file mode 100644 index 000000000..f88c8e8b7 --- /dev/null +++ b/mealie/services/migrations/cookn.py @@ -0,0 +1,239 @@ +import os +import tempfile +import zipfile +from fractions import Fraction +from pathlib import Path +from typing import Any + +from mealie.schema.recipe.recipe_ingredient import RecipeIngredientBase + +from ._migration_base import BaseMigrator +from .utils.migration_helpers import import_image + + +def _format_time(minutes: int) -> str: + """Formats time from minutes to a human-readable string.""" + hours, minutes = divmod(minutes, 60) + parts = [] + if hours: + parts.append(f"{hours} hour{'s' if hours > 1 else ''}") + if minutes: + parts.append(f"{minutes} minute{'s' if minutes > 1 else ''}") + return " ".join(parts) + + +def convert_to_float(value): + try: + value = value.strip() # Remove any surrounding spaces + if " " in value: # Check for mixed fractions like "1 1/2" + # Split into whole number and fraction + whole, fraction = value.split(" ", 1) + return float(whole) + float(Fraction(fraction)) + return float(Fraction(value)) # Convert fraction or whole number + except (ValueError, ZeroDivisionError): + return None # Return None for invalid values + + +def extract_instructions(instructions: str) -> list[str]: + """Splits the instruction text into steps.""" + return instructions.split("\n") if instructions else [] + + +# def extract_ingredients(ingredient_data: list[dict[str, Any]]) -> list[dict[str, Any]]: +# """Extracts ingredient details from parsed Cook'n ingredient data.""" +# ingredients = [] +# for ingredient in ingredient_data: +# base_ingredient = RecipeIngredientBase( +# quantity=ingredient.get("AMOUNT_QTY", "1"), +# unit=ingredient.get("AMOUNT_UNIT"), +# food=ingredient.get("INGREDIENT_FOOD_ID"), +# ) +# ingredients.append({"title": None, "note": base_ingredient.display}) +# return ingredients + + +class DSVParser: + def __init__(self, directory: Path): + self.directory = directory + self.tables: dict[str, list[dict[str, Any]]] = {} + self.load_files() + + def load_files(self): + """Loads all .dsv files from the directory into lists of dictionaries.""" + for file in self.directory.glob("*.dsv"): + with open(file, "rb") as f: + file_contents = f.read().decode("utf-8", errors="ignore") + + # Replace unique delimiters + file_contents = file_contents.replace("||||", "\x06") + file_contents = file_contents.replace("!@#%^&*()", "\x07") + + # Manually parse rows + rows = file_contents.strip().split("\x07") + if not rows: + continue # Skip empty files + + # Extract header + headers = rows[0].split("\x06") + data = [dict(zip(headers, row.split("\x06"), strict=False)) for row in rows[1:] if row] + + self.tables[file.stem] = data # Store parsed table + + def query_by_id(self, table_name: str, column_name: str, ids: list, return_first_only=False): + """Returns rows from a specified table where column_name matches any of the provided IDs.""" + if table_name not in self.tables: + raise ValueError(f"Table '{table_name}' not found.") + + results = [row for row in self.tables[table_name] if row.get(column_name) in ids] + + if len(results) == 0: + results.append({}) + + if return_first_only and results: + return results[0] + return results + + def get_table(self, table_name: str): + """Returns the entire table as a list of dictionaries.""" + if table_name not in self.tables: + raise ValueError(f"Table '{table_name}' not found.") + return self.tables[table_name] + + def list_tables(self): + """Returns a list of available tables.""" + return list(self.tables.keys()) + + +class CooknMigrator(BaseMigrator): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.name = "cookn" + self.key_aliases = [] + + def _process_recipe_document(self, _recipe_row, db) -> dict: + recipe_data = {} + + # Select db values + _recipe_id = _recipe_row["ID"] + _recipe_desc_row = db.query_by_id("temp_recipe_desc", "ID", [_recipe_id], return_first_only=True) + _chapter_id = _recipe_desc_row["PARENT"] + _chapter_row = db.query_by_id("temp_chapter_desc", "ID", [_chapter_id], return_first_only=True) + _cookbook_id = _chapter_row["PARENT"] + _cookbook_row = db.query_by_id("temp_cookBook_desc", "ID", [_cookbook_id], return_first_only=True) + _media_row = db.query_by_id("temp_media", "ENTITY_ID", [_recipe_id], return_first_only=True) + _media_id = _media_row.get("ID", "") + + # Parse general recipe info + cookbook = _cookbook_row.get("TITLE", "") + chapter = _chapter_row.get("TITLE", "") + name = _recipe_desc_row.get("TITLE", "") + description = _recipe_desc_row.get("DESCRIPTION", "") + serves = _recipe_row["SERVES"] + prep_time = int(_recipe_row["PREPTIME"]) + cook_time = int(_recipe_row["COOKTIME"]) + + recipe_data["recipeCategory"] = [cookbook] + recipe_data["tags"] = [chapter] + recipe_data["name"] = name + recipe_data["description"] = description + recipe_data["recipeYield"] = serves + recipe_data["prepTime"] = _format_time(prep_time) + recipe_data["performTime"] = _format_time(cook_time) + recipe_data["totalTime"] = _format_time(prep_time + cook_time) + + # Parse and rename image + + if _media_id != "": + _media_type = _media_row["MEDIA_CONTENT_TYPE"] + # Determine file extension based on media type + _extension = _media_type.split("/")[-1] + _old_image_path = os.path.join(db.directory, str(_media_id)) + new_image_path = f"{_old_image_path}.{_extension}" + # Rename the file if it exists and has no extension + if os.path.exists(_old_image_path) and not os.path.exists(new_image_path): + os.rename(_old_image_path, new_image_path) + if Path(new_image_path).exists(): + recipe_data["image"] = [new_image_path] + + # Parse ingrediants + ingredients = [] + _ingrediant_rows = db.query_by_id("temp_ingredient", "PARENT_ID", [_recipe_id]) + for _ingrediant_row in _ingrediant_rows: + _unit_id = _ingrediant_row.get("AMOUNT_UNIT", "") + _unit_row = db.query_by_id("temp_unit", "ID", [_unit_id], return_first_only=True) + _food_id = _ingrediant_row.get("INGREDIENT_FOOD_ID", "") + _food_row = db.query_by_id("temp_food", "ID", [_food_id], return_first_only=True) + _brand_id = _ingrediant_row.get("BRAND_ID", "") + _brand_row = db.query_by_id("temp_brand", "ID", [_brand_id], return_first_only=True) + + amount = convert_to_float(_ingrediant_row.get("AMOUNT_QTY_STRING", "1")) + unit = _unit_row.get("ABBREVIATION") + + food_name_singluar = _food_row.get("NAME", "") + food_name_plural = _food_row.get("PLURAL_NAME", "") + if food_name_singluar != "" and food_name_plural != "": + if unit is None: + if amount is not None and amount > 1: + food_name = _food_row.get("PLURAL_NAME", "") + else: + food_name = _food_row.get("NAME", "") + else: + food_name = _food_row.get("NAME", "") + else: + if food_name_singluar != "": + food_name = food_name_singluar + else: + food_name = food_name_plural + + if unit is None: + unit = "" + else: + if amount is not None and amount > 1: + unit = _unit_row.get("PLURAL_NAME") + else: + unit = _unit_row.get("NAME") + + pre_qualifier = _ingrediant_row.get("PRE_QUALIFIER", "") + if pre_qualifier == "[null]": + pre_qualifier = "" + post_qualifier = _ingrediant_row.get("POST_QUALIFIER", "") + if post_qualifier == "[null]": + post_qualifier = "" + brand = _brand_row.get("NAME", "") + if brand == "[null]": + brand = "" + + base_ingredient = RecipeIngredientBase( + quantity=amount, + unit=unit, + food=pre_qualifier + " " + food_name + " " + post_qualifier + " " + brand, + notes=None, + ) + _display_order = int(_ingrediant_row.get("DISPLAY_ORDER", "")) + ingredients.append({"title": None, "order": _display_order, "note": base_ingredient.display}) + ingredients = sorted(ingredients, key=lambda d: d["order"]) + recipe_data["recipeIngredient"] = ingredients + + # Parse instructions + recipe_data["recipeInstructions"] = extract_instructions(_recipe_row["INSTRUCTIONS"]) + + return recipe_data + + def _migrate(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + with zipfile.ZipFile(self.archive) as zip_file: + zip_file.extractall(tmpdir) + + source_dir = self.get_zip_base_path(Path(tmpdir)) + db = DSVParser(source_dir) + _recipe_table = db.get_table("temp_recipe") + recipes_as_dicts = [self._process_recipe_document(_recipe_row, db) for _recipe_row in _recipe_table] + + recipes = [self.clean_recipe_dictionary(x) for x in recipes_as_dicts] + results = self.import_recipes_to_database(recipes) + recipe_lookup = {r.slug: r for r in recipes} + for slug, recipe_id, status in results: + if status: + r = recipe_lookup.get(slug) + if r and r.image: + import_image(r.image, recipe_id) diff --git a/tests/data/__init__.py b/tests/data/__init__.py index 2972f9184..92c81ab68 100644 --- a/tests/data/__init__.py +++ b/tests/data/__init__.py @@ -46,6 +46,8 @@ migrations_myrecipebox = CWD / "migrations/myrecipebox.csv" migrations_recipekeeper = CWD / "migrations/recipekeeper.zip" +migrations_cookn = CWD / "migrations/cookn.zip" + images_test_image_1 = CWD / "images/test-image-1.jpg" images_test_image_2 = CWD / "images/test-image-2.png" diff --git a/tests/integration_tests/recipe_migration_tests/test_recipe_migrations.py b/tests/integration_tests/recipe_migration_tests/test_recipe_migrations.py index 8236f07bf..bd792231a 100644 --- a/tests/integration_tests/recipe_migration_tests/test_recipe_migrations.py +++ b/tests/integration_tests/recipe_migration_tests/test_recipe_migrations.py @@ -108,6 +108,12 @@ test_cases = [ search_slug="zucchini-bread", nutrition_entries=set(), ), + MigrationTestData( + typ=SupportedMigrations.cookn, + archive=test_data.migrations_cookn, + search_slug="zucchini-bread", + nutrition_entries=set(), + ), ] test_ids = [ @@ -120,6 +126,7 @@ test_ids = [ "plantoeat_archive", "myrecipebox_csv", "recipekeeper_archive", + "cookn_archive", ]