diff --git a/.dockerignore b/.dockerignore index cc5da2130..b0f176b1d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,2 @@ */node_modules */dist -## \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index c98f474de..ac409863d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,6 @@ { "python.formatting.provider": "black", - "python.pythonPath": ".venv/bin/python", + "python.pythonPath": ".venv/bin/python3.8", "python.linting.pylintEnabled": true, "python.linting.enabled": true, "python.autoComplete.extraPaths": ["mealie", "mealie/mealie"], @@ -10,7 +10,5 @@ "python.testing.nosetestsEnabled": false, "python.testing.pytestEnabled": true, "cSpell.enableFiletypes": ["!javascript", "!python"], - "python.testing.pytestArgs": [ - "mealie" - ] + "python.testing.pytestArgs": ["mealie"] } diff --git a/frontend/package-lock.json b/frontend/package-lock.json index d4d7573e2..bfe873610 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1738,16 +1738,6 @@ "integrity": "sha1-/q7SVZc9LndVW4PbwIhRpsY1IPo=", "dev": true }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "optional": true, - "requires": { - "color-convert": "^2.0.1" - } - }, "cacache": { "version": "13.0.1", "resolved": "https://registry.npm.taobao.org/cacache/download/cacache-13.0.1.tgz?cache=0&sync_timestamp=1594428402513&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcacache%2Fdownload%2Fcacache-13.0.1.tgz", @@ -1774,34 +1764,6 @@ "unique-filename": "^1.1.1" } }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "optional": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "optional": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "optional": true - }, "find-cache-dir": { "version": "3.3.1", "resolved": "https://registry.npm.taobao.org/find-cache-dir/download/find-cache-dir-3.3.1.tgz?cache=0&sync_timestamp=1583735626956&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffind-cache-dir%2Fdownload%2Ffind-cache-dir-3.3.1.tgz", @@ -1823,25 +1785,6 @@ "path-exists": "^4.0.0" } }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "optional": true - }, - "loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "dev": true, - "optional": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npm.taobao.org/locate-path/download/locate-path-5.0.0.tgz?cache=0&sync_timestamp=1597081764621&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Flocate-path%2Fdownload%2Flocate-path-5.0.0.tgz", @@ -1906,16 +1849,6 @@ "minipass": "^3.1.1" } }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "optional": true, - "requires": { - "has-flag": "^4.0.0" - } - }, "terser-webpack-plugin": { "version": "2.3.8", "resolved": "https://registry.npm.taobao.org/terser-webpack-plugin/download/terser-webpack-plugin-2.3.8.tgz?cache=0&sync_timestamp=1603882075288&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fterser-webpack-plugin%2Fdownload%2Fterser-webpack-plugin-2.3.8.tgz", @@ -1932,18 +1865,6 @@ "terser": "^4.6.12", "webpack-sources": "^1.4.3" } - }, - "vue-loader-v16": { - "version": "npm:vue-loader@16.1.2", - "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.1.2.tgz", - "integrity": "sha512-8QTxh+Fd+HB6fiL52iEVLKqE9N1JSlMXLR92Ijm6g8PZrwIxckgpqjPDWRP5TWxdiPaHR+alUWsnu1ShQOwt+Q==", - "dev": true, - "optional": true, - "requires": { - "chalk": "^4.1.0", - "hash-sum": "^2.0.0", - "loader-utils": "^2.0.0" - } } } }, @@ -11207,6 +11128,87 @@ } } }, + "vue-loader-v16": { + "version": "npm:vue-loader@16.1.2", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.1.2.tgz", + "integrity": "sha512-8QTxh+Fd+HB6fiL52iEVLKqE9N1JSlMXLR92Ijm6g8PZrwIxckgpqjPDWRP5TWxdiPaHR+alUWsnu1ShQOwt+Q==", + "dev": true, + "optional": true, + "requires": { + "chalk": "^4.1.0", + "hash-sum": "^2.0.0", + "loader-utils": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "optional": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "optional": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "optional": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "optional": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "optional": true + }, + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "dev": true, + "optional": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "optional": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "vue-router": { "version": "3.4.9", "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.4.9.tgz", diff --git a/mealie/data/db/mealie.sqlite b/mealie/data/db/mealie.sqlite index 77a38a303..9cc937f47 100644 Binary files a/mealie/data/db/mealie.sqlite and b/mealie/data/db/mealie.sqlite differ diff --git a/mealie/db/db_base.py b/mealie/db/db_base.py index 3bf60c7cc..c9ccb8601 100644 --- a/mealie/db/db_base.py +++ b/mealie/db/db_base.py @@ -71,12 +71,21 @@ class BaseDocument: if limit == 1: return list[0] - session.close() return list def _query_one( self, match_value: str, match_key: str = None ) -> Union[Session, SqlAlchemyBase]: + """Query the sql database for one item an return the sql alchemy model + object. If no match key is provided the primary_key attribute will be used. + + Args: + match_value (str): The value to use in the query + match_key (str, optional): the key/property to match against. Defaults to None. + + Returns: + Union[Session, SqlAlchemyBase]: Will return both the session and found model + """ session = self.create_session() if match_key == None: @@ -136,7 +145,13 @@ class BaseDocument: new_document.save() return BaseDocument._unpack_mongo(new_document) elif USE_SQL: - return self.save_new_sql(document) + session = self.create_session() + new_document = self.sql_model(**document) + session.add(new_document) + return_data = new_document.dict() + session.commit() + + return return_data def delete(self, primary_key_value) -> dict: if USE_MONGO: diff --git a/mealie/db/db_recipes.py b/mealie/db/db_recipes.py index 801134756..a2a35b2d2 100644 --- a/mealie/db/db_recipes.py +++ b/mealie/db/db_recipes.py @@ -49,14 +49,16 @@ class _Recipes(BaseDocument): return new_data.get("slug") elif USE_SQL: - session, recipe = self._query_one(match_value=slug) - recipe.update(**new_data) - recipe_dict = recipe.dict() - session.commit() + # data = self.save_new(new_data) + # session, recipe = self._query_one(match_value=slug) + session = self.create_session() + session.merge((self.sql_model(**new_data))) + # recipe_dict = recipe.dict() + # session.commit() session.close() - return recipe_dict + return "string" def update_image(self, slug: str, extension: str) -> None: if USE_MONGO: diff --git a/mealie/db/sql/meal_models.py b/mealie/db/sql/meal_models.py index d696c37a9..e08bc57c0 100644 --- a/mealie/db/sql/meal_models.py +++ b/mealie/db/sql/meal_models.py @@ -39,6 +39,14 @@ class MealPlanModel(SqlAlchemyBase): endDate = sa.Column(sa.Date) meals: List[Meal] = orm.relation(Meal) + def __init__(self, startDate, endDate, meals) -> None: + self.startDate = startDate + self.endDate = endDate + self.meals = [Meal(meal) for meal in meals] + + def update(self, startDate, endDate, meals) -> None: + self.__init__(startDate, endDate, meals) + def dict(self) -> dict: data = { "uid": self.uid, diff --git a/mealie/db/sql/recipe_models.py b/mealie/db/sql/recipe_models.py index 47edd6da0..f14959198 100644 --- a/mealie/db/sql/recipe_models.py +++ b/mealie/db/sql/recipe_models.py @@ -10,18 +10,22 @@ from db.sql.model_base import SqlAlchemyBase class ApiExtras(SqlAlchemyBase): __tablename__ = "api_extras" id = sa.Column(sa.Integer, primary_key=True) - parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.slug")) - key: sa.Column(sa.String, primary_key=True) - value: sa.Column(sa.String) + parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id")) + key_name = sa.Column(sa.String, unique=True) + value = sa.Column(sa.String) + + def __init__(self, key, value) -> None: + self.key_name = key + self.value = value def dict(self): - return {self.key: self.value} + return {self.key_name: self.value} class Category(SqlAlchemyBase): __tablename__ = "categories" id = sa.Column(sa.Integer, primary_key=True) - parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.slug")) + parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id")) name = sa.Column(sa.String, index=True) def to_str(self): @@ -31,7 +35,7 @@ class Category(SqlAlchemyBase): class Tag(SqlAlchemyBase): __tablename__ = "tags" id = sa.Column(sa.Integer, primary_key=True) - parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.slug")) + parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id")) name = sa.Column(sa.String, index=True) def to_str(self): @@ -41,7 +45,7 @@ class Tag(SqlAlchemyBase): class Note(SqlAlchemyBase): __tablename__ = "notes" id = sa.Column(sa.Integer, primary_key=True) - parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.slug")) + parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id")) title = sa.Column(sa.String) text = sa.Column(sa.String) @@ -52,9 +56,12 @@ class Note(SqlAlchemyBase): class RecipeIngredient(SqlAlchemyBase): __tablename__ = "recipes_ingredients" id = sa.Column(sa.Integer, primary_key=True) - parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.slug")) + parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id")) ingredient = sa.Column(sa.String) + def update(self, ingredient): + self.ingredient = ingredient + def to_str(self): return self.ingredient @@ -62,7 +69,7 @@ class RecipeIngredient(SqlAlchemyBase): class RecipeInstruction(SqlAlchemyBase): __tablename__ = "recipe_instructions" id = sa.Column(sa.Integer, primary_key=True) - parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.slug")) + parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id")) type = sa.Column(sa.String, default="") text = sa.Column(sa.String) @@ -74,24 +81,33 @@ class RecipeInstruction(SqlAlchemyBase): class RecipeModel(SqlAlchemyBase): __tablename__ = "recipes" + id = sa.Column(sa.Integer, primary_key=True) name = sa.Column(sa.String) description = sa.Column(sa.String) image = sa.Column(sa.String) recipeYield = sa.Column(sa.String) recipeIngredient: List[RecipeIngredient] = orm.relationship( - "RecipeIngredient", cascade="all, delete" + "RecipeIngredient", cascade="all, delete", order_by="RecipeIngredient.position" ) recipeInstructions: List[RecipeInstruction] = orm.relationship( - "RecipeInstruction", cascade="all, delete" + "RecipeInstruction", + cascade="all, delete", + order_by="RecipeInstruction.position", ) totalTime = sa.Column(sa.String) # Mealie Specific - slug = sa.Column(sa.String, primary_key=True, index=True, unique=True) - categories: List[Category] = orm.relationship("Category", cascade="all, delete") - tags: List[Tag] = orm.relationship("Tag", cascade="all, delete") + slug = sa.Column(sa.String, index=True, unique=True) + categories: List[Category] = orm.relationship( + "Category", cascade="all, delete", order_by="Category.position" + ) + tags: List[Tag] = orm.relationship( + "Tag", cascade="all, delete", order_by="Tag.position" + ) dateAdded = sa.Column(sa.Date, default=date.today) - notes: List[Note] = orm.relationship("Note", cascade="all, delete") + notes: List[Note] = orm.relationship( + "Note", cascade="all, delete", order_by="Note.position" + ) rating = sa.Column(sa.Integer) orgURL = sa.Column(sa.String) extras: List[ApiExtras] = orm.relationship("ApiExtras", cascade="all, delete") @@ -118,7 +134,6 @@ class RecipeModel(SqlAlchemyBase): self.description = description self.image = image self.recipeYield = recipeYield - print(recipeIngredient) self.recipeIngredient = [ RecipeIngredient(ingredient=ingr) for ingr in recipeIngredient ] @@ -130,13 +145,28 @@ class RecipeModel(SqlAlchemyBase): # Mealie Specific self.slug = slug - self.categories = [Category(cat) for cat in categories] + self.categories = [Category(name=cat) for cat in categories] self.tags = [Tag(name=tag) for tag in tags] self.dateAdded = dateAdded self.notes = [Note(note) for note in notes] self.rating = rating self.orgURL = orgURL - self.extras = [ApiExtras(extra) for extra in extras] + self.extras = [ApiExtras(key=key, value=value) for key, value in extras.items()] + + @staticmethod + def _update_list_str(new_list, existing, cls_model): + current_index = 0 + + new_list = [] + for item in new_list: + try: + existing.update(new_list[current_index]) + current_index += 1 + except: + existing.append(cls_model(item)) + + for item in new_list[current_index:]: + existing.append(cls_model(item)) def update( self, @@ -156,29 +186,41 @@ class RecipeModel(SqlAlchemyBase): orgURL: str = None, extras: dict = None, ): - self.__init__( - name, - description, - image, - recipeYield, - recipeIngredient, - recipeInstructions, - totalTime, - slug, - categories, - tags, - dateAdded, - notes, - rating, - orgURL, - extras, + self.name = name + self.description = description + self.image = image + self.recipeYield = recipeYield + + for ingr in recipeIngredient: + self.recipeIngredient + self.recipeIngredient = RecipeModel._update_list_str( + recipeIngredient, self.recipeIngredient, RecipeIngredient ) + # self.recipeInstructions = recipeInstructions + + # self.totalTime = totalTime + # self.slug = slug + # self.categories = categories + + # for category in categories: + # if category in self.categories: + # pass + # else: + # self.categories.append(Category(name=category)) + + # self.tags = tags + # self.dateAdded = dateAdded + # self.notes = notes + # self.rating = rating + # self.orgURL = orgURL + # self.extras = extras @staticmethod def _flatten_dict(list_of_dict: List[dict]): finalMap = {} for d in list_of_dict: - finalMap.update(d) + + finalMap.update(d.dict()) return finalMap diff --git a/mealie/services/migrations/chowdown.py b/mealie/services/migrations/chowdown.py index 01b23be2f..061a2ac8c 100644 --- a/mealie/services/migrations/chowdown.py +++ b/mealie/services/migrations/chowdown.py @@ -3,8 +3,8 @@ from pathlib import Path import git import yaml -from services.image_services import IMG_DIR from services.recipe_services import Recipe +from settings import IMG_DIR try: from yaml import CLoader as Loader diff --git a/mealie/services/migrations/nextcloud.py b/mealie/services/migrations/nextcloud.py index a8698cf06..3cac03ef7 100644 --- a/mealie/services/migrations/nextcloud.py +++ b/mealie/services/migrations/nextcloud.py @@ -4,9 +4,10 @@ import shutil import zipfile from pathlib import Path -from services.recipe_services import IMG_DIR, Recipe +from services.recipe_services import Recipe from services.scrape_services import normalize_data, process_recipe_data -from settings import TEMP_DIR +from settings import IMG_DIR, TEMP_DIR + CWD = Path(__file__).parent MIGRTAION_DIR = CWD.parent.parent.joinpath("data", "migration")