From 802633a0602a8735302782ba3105cab5729e769f Mon Sep 17 00:00:00 2001 From: hay-kot Date: Sun, 21 Mar 2021 12:19:03 -0800 Subject: [PATCH] potentially fix #217 --- .../Backup/ImportSummaryDialog/index.vue | 61 ++++++------------ makefile | 11 ++-- mealie/db/db_base.py | 6 +- mealie/services/backups/imports.py | 62 ++++++++++++------- tests/test_migrations/__init__.py | 0 tests/test_migrations/test_nextcloud.py | 45 -------------- 6 files changed, 68 insertions(+), 117 deletions(-) delete mode 100644 tests/test_migrations/__init__.py delete mode 100644 tests/test_migrations/test_nextcloud.py diff --git a/frontend/src/components/Admin/Backup/ImportSummaryDialog/index.vue b/frontend/src/components/Admin/Backup/ImportSummaryDialog/index.vue index 4cbf61a76..02c77c181 100644 --- a/frontend/src/components/Admin/Backup/ImportSummaryDialog/index.vue +++ b/frontend/src/components/Admin/Backup/ImportSummaryDialog/index.vue @@ -13,43 +13,13 @@ -
+
-

Recipes

-
-
- Success: {{ recipeNumbers.success }} -
-
- Failed: {{ recipeNumbers.failure }} -
-
-
-
- -
-

Themes

-
-
- Success: {{ themeNumbers.success }} -
-
- Failed: {{ themeNumbers.failure }} -
-
-
-
- -
-

Settings

-
-
- Success: {{ settingsNumbers.success }} -
-
- Failed: {{ settingsNumbers.failure }} +

{{ values.title }}

+
Success: {{ values.success }}
+
Failed: {{ values.failure }}
@@ -131,26 +101,35 @@ export default { computed: { recipeNumbers() { - return this.calculateNumbers(this.recipeData); + return this.calculateNumbers("Recipes", this.recipeData); }, settingsNumbers() { - return this.calculateNumbers(this.settingsData); + return this.calculateNumbers("Settings", this.settingsData); }, themeNumbers() { - return this.calculateNumbers(this.themeData); + return this.calculateNumbers("Theme", this.themeData); }, userNumbers() { - return this.calculateNumbers(this.userData); + return this.calculateNumbers("Users", this.userData); }, groupNumbers() { - return this.calculateNumbers(this.groupData); + return this.calculateNumbers("Groups", this.groupData); + }, + allNumbers() { + return [ + this.recipeNumbers, + this.settingsNumbers, + this.themeNumbers, + this.userNumbers, + this.groupNumbers, + ]; }, }, methods: { - calculateNumbers(list_array) { + calculateNumbers(title, list_array) { if (!list_array) return; - let numbers = { success: 0, failure: 0 }; + let numbers = { title: title, success: 0, failure: 0 }; list_array.forEach(element => { if (element.status) { numbers.success++; diff --git a/makefile b/makefile index 6fbdc0d45..57c18530d 100644 --- a/makefile +++ b/makefile @@ -4,10 +4,9 @@ setup: npm install && \ cd .. -backend: - source ./.venv/bin/activate && \ - python mealie/db/init_db.py && \ - python mealie/app.py +backend: + poetry run python mealie/db/init_db.py && \ + poetry run python mealie/app.py .PHONY: frontend frontend: @@ -15,9 +14,7 @@ frontend: .PHONY: docs docs: - source ./.venv/bin/activate && \ - cd docs && \ - mkdocs serve + cd docs && poetry run python -m mkdocs serve docker-dev: docker-compose -f docker-compose.dev.yml -p dev-mealie up --build diff --git a/mealie/db/db_base.py b/mealie/db/db_base.py index db4a5d8c0..68c06dbeb 100644 --- a/mealie/db/db_base.py +++ b/mealie/db/db_base.py @@ -76,7 +76,7 @@ class BaseDocument: return result - def get(self, session: Session, match_value: str, match_key: str = None, limit=1) -> dict or List[dict]: + def get(self, session: Session, match_value: str, match_key: str = None, limit=1) -> BaseModel or List[BaseModel]: """Retrieves an entry from the database by matching a key/value pair. If no key is provided the class objects primary key will be used to match against. @@ -101,7 +101,7 @@ class BaseDocument: return None return [self.schema.from_orm(x) for x in result] - def create(self, session: Session, document: dict) -> dict: + def create(self, session: Session, document: dict) -> BaseModel: """Creates a new database entry for the given SQL Alchemy Model. Args: \n @@ -121,7 +121,7 @@ class BaseDocument: return_data = new_document.dict() return return_data - def update(self, session: Session, match_value: str, new_data: str) -> dict: + def update(self, session: Session, match_value: str, new_data: str) -> BaseModel: """Update a database entry. Args: \n diff --git a/mealie/services/backups/imports.py b/mealie/services/backups/imports.py index 0a9b44139..4878d7808 100644 --- a/mealie/services/backups/imports.py +++ b/mealie/services/backups/imports.py @@ -7,7 +7,6 @@ from typing import List from fastapi.logger import logger from mealie.core.config import BACKUP_DIR, IMG_DIR, TEMP_DIR from mealie.db.database import db -from mealie.db.db_setup import create_session from mealie.schema.recipe import Recipe from mealie.schema.restore import GroupImport, RecipeImport, SettingsImport, ThemeImport, UserImport from mealie.schema.theme import SiteTheme @@ -47,38 +46,41 @@ class ImportDatabase: raise Exception("Import file does not exist") def import_recipes(self): - session = create_session() recipe_dir: Path = self.import_dir.joinpath("recipes") - imports = [] successful_imports = [] - for recipe in recipe_dir.glob("*.json"): - with open(recipe, "r") as f: - recipe_dict = json.loads(f.read()) - recipe_dict = ImportDatabase._recipe_migration(recipe_dict) - try: - if recipe_dict.get("categories", False): - recipe_dict["recipeCategory"] = recipe_dict.get("categories") - del recipe_dict["categories"] + def read_recipe_file(file_path: Path): + with open(file_path, "r") as f: + try: + recipe_dict = json.loads(f.read()) + recipe_dict = ImportDatabase._recipe_migration(recipe_dict) + return Recipe(**recipe_dict) + except: + import_status = RecipeImport(name=file_path.stem, slug=file_path.stem, status=False) + imports.append(import_status) - recipe_obj = Recipe(**recipe_dict) - db.recipes.create(session, recipe_obj.dict()) - import_status = RecipeImport(name=recipe_obj.name, slug=recipe_obj.slug, status=True) - imports.append(import_status) - successful_imports.append(recipe.stem) - logger.info(f"Imported: {recipe.stem}") + recipes = [read_recipe_file(r) for r in recipe_dir.glob("*.json")] + + for recipe in recipes: + try: + db.recipes.create(self.session, recipe.dict()) + import_status = RecipeImport(name=recipe.name, slug=recipe.slug, status=True) + successful_imports.append(recipe.slug) + logger.info(f"Imported: {recipe.slug}") except Exception as inst: + self.session.rollback() logger.error(inst) - logger.info(f"Failed Import: {recipe.stem}") + logger.info(f"Failed Import: {recipe.slug}") import_status = RecipeImport( - name=recipe.stem, - slug=recipe.stem, + name=recipe.name, + slug=recipe.slug, status=False, exception=str(inst), ) - imports.append(import_status) + + imports.append(import_status) self._import_images(successful_imports) @@ -86,6 +88,9 @@ class ImportDatabase: @staticmethod def _recipe_migration(recipe_dict: dict) -> dict: + if recipe_dict.get("categories", False): + recipe_dict["recipeCategory"] = recipe_dict.get("categories") + del recipe_dict["categories"] try: del recipe_dict["_id"] del recipe_dict["dateAdded"] @@ -117,6 +122,9 @@ class ImportDatabase: def import_themes(self): themes_file = self.import_dir.joinpath("themes", "themes.json") + if not themes_file.exists(): + return [] + theme_imports = [] with open(themes_file, "r") as f: themes: list[dict] = json.loads(f.read()) @@ -141,6 +149,9 @@ class ImportDatabase: def import_settings(self): settings_file = self.import_dir.joinpath("settings", "settings.json") + if not settings_file.exists(): + return [] + settings_imports = [] with open(settings_file, "r") as f: @@ -153,6 +164,7 @@ class ImportDatabase: import_status = SettingsImport(name=name, status=True) except Exception as inst: + self.session.rollback() import_status = SettingsImport(name=name, status=False, exception=str(inst)) settings_imports.append(import_status) @@ -160,6 +172,9 @@ class ImportDatabase: def import_groups(self): groups_file = self.import_dir.joinpath("groups", "groups.json") + if not groups_file.exists(): + return [] + group_imports = [] with open(groups_file, "r") as f: @@ -176,6 +191,7 @@ class ImportDatabase: import_status = GroupImport(name=group.name, status=True) except Exception as inst: + self.session.rollback() import_status = GroupImport(name=group.name, status=False, exception=str(inst)) group_imports.append(import_status) @@ -184,6 +200,9 @@ class ImportDatabase: def import_users(self): users_file = self.import_dir.joinpath("users", "users.json") + if not users_file.exists(): + return [] + user_imports = [] with open(users_file, "r") as f: @@ -207,6 +226,7 @@ class ImportDatabase: import_status = UserImport(name=user.full_name, status=True) except Exception as inst: + self.session.rollback() import_status = UserImport(name=user.full_name, status=False, exception=str(inst)) user_imports.append(import_status) diff --git a/tests/test_migrations/__init__.py b/tests/test_migrations/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/test_migrations/test_nextcloud.py b/tests/test_migrations/test_nextcloud.py deleted file mode 100644 index 0f35b5d78..000000000 --- a/tests/test_migrations/test_nextcloud.py +++ /dev/null @@ -1,45 +0,0 @@ -from pathlib import Path - -import pytest -from mealie.core.config import TEMP_DIR -from mealie.schema.recipe import Recipe -from mealie.services.image_services import IMG_DIR -from mealie.services.migrations.nextcloud import ( - cleanup, - import_recipes, - prep, - process_selection, -) -from tests.test_config import TEST_NEXTCLOUD_DIR - -CWD = Path(__file__).parent -TEST_NEXTCLOUD_DIR -TEMP_NEXTCLOUD = TEMP_DIR.joinpath("nextcloud") - - -@pytest.mark.parametrize( - "file_name,final_path", - [("nextcloud.zip", TEMP_NEXTCLOUD)], -) -def test_zip_extraction(file_name: str, final_path: Path): - prep() - zip = TEST_NEXTCLOUD_DIR.joinpath(file_name) - dir = process_selection(zip) - - assert dir == final_path - cleanup() - assert dir.exists() == False - - -@pytest.mark.parametrize( - "recipe_dir", - [ - TEST_NEXTCLOUD_DIR.joinpath("Air Fryer Shrimp"), - TEST_NEXTCLOUD_DIR.joinpath("Chicken Parmigiana"), - TEST_NEXTCLOUD_DIR.joinpath("Skillet Shepherd's Pie"), - ], -) -def test_nextcloud_migration(recipe_dir: Path): - recipe = import_recipes(recipe_dir) - assert isinstance(recipe, Recipe) - IMG_DIR.joinpath(recipe.image).unlink(missing_ok=True)