diff --git a/.vscode/settings.json b/.vscode/settings.json index 0445b096a..b74ae169c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,13 +8,8 @@ "python.testing.unittestEnabled": false, "python.testing.nosetestsEnabled": false, - "python.discoverTest": true, + "python.testing.cwd": "./mealie/tests", "python.testing.pytestEnabled": true, - "cSpell.enableFiletypes": [ - "!javascript", - "!python" - ], - "python.testing.pytestArgs": [ - "mealie" - ] + "cSpell.enableFiletypes": ["!javascript", "!python"], + "python.testing.pytestArgs": ["mealie/test/"] } diff --git a/mealie/data/debug/last_recipe.json b/mealie/data/debug/last_recipe.json index a12692e32..e66b6bfde 100644 --- a/mealie/data/debug/last_recipe.json +++ b/mealie/data/debug/last_recipe.json @@ -1,33 +1,29 @@ { "@context": "http://schema.org", "@type": "Recipe", - "articleBody": "With plenty of protein, a double punch of acid from citrus and vinegar, and a garlicky chili oil, this salad is made for dinner. While your chicken is getting toasty in the oven, make your chile crisp-inspired dressing with garlic, paprika, cracked coriander, and red pepper flakes. If you have leftovers (or want to make extra for a bit of meal prep), keep all of your ingredients separate in the fridge so that they stay fresher for longer. \u00a0This recipe is part of the 2021\u00a0Feel Good Food Plan, our eight-day dinner plan for starting the year off right.", - "alternativeHeadline": "With plenty of protein, a double punch of acid from citrus and vinegar, and a garlicky chili oil, this salad is made for dinner.", - "dateModified": "2021-01-11 11:51:19.699000", - "datePublished": "2021-01-01 06:00:00", + "articleBody": "\u201cAfter a draining day juggling work, homeschooling, and urging children to stop using their masks as slingshots, the ideal food for me isn\u2019t perfectly prepared food that\u2019s been tweezered into position, but a meal that\u2019s simply comforting,\u201d writes the Smitten Kitchen\u2019s Deb Perelman. Right now, it\u2019s this deeply cozy pot of tender chicken thighs, jammy leeks, and broth-soaked rice.", + "alternativeHeadline": "This one-skillet dinner gets deep oniony flavor from lots of leeks cooked down to jammy tenderness.", + "dateModified": "2021-01-10 15:20:51.422000", + "datePublished": "2020-08-18 04:00:00", "keywords": [ "recipes", - "healthyish", - "salad", "chicken recipes", "kosher salt", - "pepper", - "olive oil", + "black pepper", + "butter", + "leek", + "lemon zest", + "rice", + "chicken broth", + "anchovy", "garlic", - "paprika", - "coriander", - "endive", - "radicchio", - "blood orange", - "orange", - "vinegar", - "red wine vinegar", - "sesame seed", - "feel good food plan", - "feel good food plan 2021", + "capers", + "herb", + "olive oil", + "healthyish", "web" ], - "thumbnailUrl": "https://assets.bonappetit.com/photos/5fdbe110607088d75a57bc54/1:1/w_3142,h_3142,c_limit/BA0221coverfinal01_rev.jpg", + "thumbnailUrl": "https://assets.bonappetit.com/photos/5f29796456f43685a49327fb/1:1/w_1125,h_1125,c_limit/Chicken-and-Rice-With-Leeks-Salsa-Verde-01.jpg", "publisher": { "@context": "https://schema.org", "@type": "Organization", @@ -51,57 +47,49 @@ "author": [ { "@type": "Person", - "name": "Devonn Francis", - "sameAs": "https://bon-appetit.com/contributor/devonn-francis/" + "name": "Deb Perelman", + "sameAs": "https://bon-appetit.com/contributor/deb-perelman/" } ], "aggregateRating": { "@type": "AggregateRating", - "ratingValue": 4.89, - "ratingCount": 12 + "ratingValue": 4.02, + "ratingCount": 48 }, - "description": "With plenty of protein, a double punch of acid from citrus and vinegar, and a garlicky chili oil, this salad is made for dinner.", - "image": "chicken-salad-with-citrus-and-chile-oil.jpg", - "headline": "Chicken Salad With Citrus and Chile Oil", - "name": "Chicken Salad With Citrus and Chile Oil", + "description": "This one-skillet dinner gets deep oniony flavor from lots of leeks cooked down to jammy tenderness.", + "image": "chicken-and-rice-with-leeks-and-salsa-verde.jpg", + "headline": "Chicken and Rice With Leeks and Salsa Verde", + "name": "Chicken and Rice With Leeks and Salsa Verde", "recipeIngredient": [ - "3 skin-on, bone-in chicken breasts\u00a0 (about 1\u00bd lb. total)", + "1\u00bd lb. skinless, boneless chicken thighs (4\u20138 depending on size)", "Kosher salt, freshly ground pepper", - "2 Tbsp. plus \u2153 cup extra-virgin olive oil", - "3 garlic cloves, thinly sliced", - "2 tsp. paprika", - "1 tsp. coriander seeds, coarsely\u00a0crushed", - "\u00bd tsp. crushed red pepper flakes", - "2 medium endive, leaves separated", - "1 large head of radicchio, leaves\u00a0 separated, torn if large", - "4 satsumas or blood oranges or 3 medium oranges, peeled, sliced into rounds, seeds removed", - "2 Tbsp. white wine vinegar or red wine vinegar", - "2 tsp. toasted sesame seeds, lightly crushed" + "3 Tbsp. unsalted butter, divided", + "2 large or 3 medium leeks, white and pale green parts only, halved lengthwise, thinly sliced", + "Zest and juice of 1 lemon, divided", + "1\u00bd cups long-grain white rice, rinsed until water runs clear", + "2\u00be cups low-sodium chicken broth", + "1 oil-packed anchovy fillet", + "2 garlic cloves", + "1 Tbsp. drained capers", + "Crushed red pepper flakes", + "1 cup tender herb leaves (such as parsley, cilantro, and/or mint)", + "4\u20135 Tbsp. extra-virgin olive oil" ], "recipeInstructions": [ { - "text": "Preheat oven to 450\u00b0. Pat chicken breasts dry with paper towels; season on all sides with salt and pepper, then rub with 2 Tbsp. oil." + "text": "Season chicken with salt and pepper. Melt 2 Tbsp. butter in a large high-sided skillet over medium-high heat. Add leeks and half of lemon zest, season with salt and pepper, and mix to coat leeks in butter. Reduce heat to medium-low, cover, and cook, stirring occasionally, until leeks are somewhat tender, about 5 minutes. Remove lid, increase heat to medium-high, and cook, stirring occasionally, until tender and just starting to take on color, about 3 minutes. Add rice and cook, stirring often, 3 minutes, then add broth, scraping up any browned bits. Tuck short sides of each chicken thigh underneath so they are touching and nestle seam side down into rice mixture. Bring to a simmer. Cover, reduce heat to medium-low, and cook until rice is tender and chicken is cooked through, about 20 minutes. Remove from heat. Cut remaining 1 Tbsp. butter into small pieces and scatter over mixture. Re-cover and let sit 10 minutes." }, { - "text": "Heat a large ovenproof skillet over medium-high. Arrange chicken, skin side down, in pan and cook, undisturbed, until skin is deep golden brown, about 3 minutes. Turn chicken over with tongs and transfer skillet to oven. Roast chicken until cooked all the way through, 15\u201317 minutes. Transfer to a cutting board and let cool 10 minutes." + "text": "Meanwhile, pulse anchovy, garlic, capers, a few pinches of red pepper flakes, and remaining lemon zest in a food processor until finely chopped. Add herbs; process until a paste forms. With motor running, gradually stream in oil until loosened to a thick sauce. Add half of lemon juice; season salsa verde with salt." }, { - "text": "Meanwhile, cook garlic and remaining \u2153 cup oil in a small skillet over medium heat, stirring occasionally, until garlic is fragrant and pale golden, about 4 minutes. Immediately pour garlic oil into a small bowl and stir in paprika, coriander seeds, and red pepper flakes; season with salt." - }, - { - "text": "Cut chicken off the bone, then slice\u00a0 \u00bd\" thick; discard bones." - }, - { - "text": "Toss endive, radicchio, and satsumas with vinegar and half of spiced garlic oil in a large bowl to combine; season salad with salt and pepper." - }, - { - "text": "Divide salad among plates or shallow bowls; top with chicken and drizzle with more spiced garlic oil. Sprinkle sesame seeds over." + "text": "Drizzle remaining lemon juice over chicken and rice. Serve with salsa verde." } ], - "recipeYield": "4 servings", - "url": "https://www.bonappetit.com/recipe/chicken-salad-with-citrus-and-chile-oil", - "slug": "chicken-salad-with-citrus-and-chile-oil", - "orgURL": "https://www.bonappetit.com/recipe/chicken-salad-with-citrus-and-chile-oil", + "recipeYield": "4 Servings", + "url": "https://www.bonappetit.com/recipe/chicken-and-rice-with-leeks-and-salsa-verde", + "slug": "chicken-and-rice-with-leeks-and-salsa-verde", + "orgURL": "https://www.bonappetit.com/recipe/chicken-and-rice-with-leeks-and-salsa-verde", "categories": [], "tags": [], "dateAdded": null, diff --git a/mealie/data/img/broccoli-beer-cheese-soup.jpg b/mealie/data/img/broccoli-beer-cheese-soup.jpg deleted file mode 100644 index 8395e1822..000000000 Binary files a/mealie/data/img/broccoli-beer-cheese-soup.jpg and /dev/null differ diff --git a/mealie/data/img/coffee-hazelnut-biscotti.jpg b/mealie/data/img/coffee-hazelnut-biscotti.jpg deleted file mode 100644 index c369c0983..000000000 Binary files a/mealie/data/img/coffee-hazelnut-biscotti.jpg and /dev/null differ diff --git a/mealie/data/img/corn-and-crab-beignets-with-yaji-aioli.jpg b/mealie/data/img/corn-and-crab-beignets-with-yaji-aioli.jpg deleted file mode 100644 index f126efb65..000000000 Binary files a/mealie/data/img/corn-and-crab-beignets-with-yaji-aioli.jpg and /dev/null differ diff --git a/mealie/data/img/crispy-carrots.jpg b/mealie/data/img/crispy-carrots.jpg deleted file mode 100644 index 35cfd083d..000000000 Binary files a/mealie/data/img/crispy-carrots.jpg and /dev/null differ diff --git a/mealie/data/img/crockpot-buffalo-chicken.jpg b/mealie/data/img/crockpot-buffalo-chicken.jpg deleted file mode 100644 index 258b3771d..000000000 Binary files a/mealie/data/img/crockpot-buffalo-chicken.jpg and /dev/null differ diff --git a/mealie/data/img/detroit-style-pepperoni-pizza.jpg b/mealie/data/img/detroit-style-pepperoni-pizza.jpg deleted file mode 100644 index a1da6c9d8..000000000 Binary files a/mealie/data/img/detroit-style-pepperoni-pizza.jpg and /dev/null differ diff --git a/mealie/data/img/downtown-marinade.jpg b/mealie/data/img/downtown-marinade.jpg deleted file mode 100644 index da579b2f6..000000000 Binary files a/mealie/data/img/downtown-marinade.jpg and /dev/null differ diff --git a/mealie/data/img/falafel-hummus-plate.jpg b/mealie/data/img/falafel-hummus-plate.jpg deleted file mode 100644 index cefc202f1..000000000 Binary files a/mealie/data/img/falafel-hummus-plate.jpg and /dev/null differ diff --git a/mealie/data/img/five-spice-popcorn-chicken.jpg b/mealie/data/img/five-spice-popcorn-chicken.jpg deleted file mode 100644 index 76f703c01..000000000 Binary files a/mealie/data/img/five-spice-popcorn-chicken.jpg and /dev/null differ diff --git a/mealie/data/img/ginger-citrus-cookies.jpg b/mealie/data/img/ginger-citrus-cookies.jpg deleted file mode 100644 index 8fccaa807..000000000 Binary files a/mealie/data/img/ginger-citrus-cookies.jpg and /dev/null differ diff --git a/mealie/data/img/green-chile-stew.jpg b/mealie/data/img/green-chile-stew.jpg deleted file mode 100644 index 74ba5362f..000000000 Binary files a/mealie/data/img/green-chile-stew.jpg and /dev/null differ diff --git a/mealie/data/img/green-spaghetti.jpg b/mealie/data/img/green-spaghetti.jpg deleted file mode 100644 index 7821394b6..000000000 Binary files a/mealie/data/img/green-spaghetti.jpg and /dev/null differ diff --git a/mealie/data/img/jalapeno-cornbread.jpg b/mealie/data/img/jalapeno-cornbread.jpg deleted file mode 100644 index b416a63e5..000000000 Binary files a/mealie/data/img/jalapeno-cornbread.jpg and /dev/null differ diff --git a/mealie/data/img/marranitos-enfiestados.jpg b/mealie/data/img/marranitos-enfiestados.jpg deleted file mode 100644 index 5fcb17d04..000000000 Binary files a/mealie/data/img/marranitos-enfiestados.jpg and /dev/null differ diff --git a/mealie/data/img/mushroom-risotto.jpg b/mealie/data/img/mushroom-risotto.jpg deleted file mode 100644 index 5c79f02e2..000000000 Binary files a/mealie/data/img/mushroom-risotto.jpg and /dev/null differ diff --git a/mealie/data/img/nilla-wafer-french-toast.jpg b/mealie/data/img/nilla-wafer-french-toast.jpg deleted file mode 100644 index a3fb14cbb..000000000 Binary files a/mealie/data/img/nilla-wafer-french-toast.jpg and /dev/null differ diff --git a/mealie/data/img/one-pot-chicken-and-rice.jpg b/mealie/data/img/one-pot-chicken-and-rice.jpg deleted file mode 100644 index b7141d3fd..000000000 Binary files a/mealie/data/img/one-pot-chicken-and-rice.jpg and /dev/null differ diff --git a/mealie/data/img/pace-pork.jpg b/mealie/data/img/pace-pork.jpg deleted file mode 100644 index d05748c23..000000000 Binary files a/mealie/data/img/pace-pork.jpg and /dev/null differ diff --git a/mealie/data/img/pizzettes.jpg b/mealie/data/img/pizzettes.jpg deleted file mode 100644 index 73f2c476d..000000000 Binary files a/mealie/data/img/pizzettes.jpg and /dev/null differ diff --git a/mealie/data/img/pork-steaks.jpg b/mealie/data/img/pork-steaks.jpg deleted file mode 100644 index 3932f83fa..000000000 Binary files a/mealie/data/img/pork-steaks.jpg and /dev/null differ diff --git a/mealie/data/img/roasted-brussels-sprouts.jpg b/mealie/data/img/roasted-brussels-sprouts.jpg deleted file mode 100644 index c3797eced..000000000 Binary files a/mealie/data/img/roasted-brussels-sprouts.jpg and /dev/null differ diff --git a/mealie/data/img/roasted-okra.jpg b/mealie/data/img/roasted-okra.jpg deleted file mode 100644 index 8e01ebcb1..000000000 Binary files a/mealie/data/img/roasted-okra.jpg and /dev/null differ diff --git a/mealie/data/img/salt-vinegar-potatoes.jpg b/mealie/data/img/salt-vinegar-potatoes.jpg deleted file mode 100644 index cad60070b..000000000 Binary files a/mealie/data/img/salt-vinegar-potatoes.jpg and /dev/null differ diff --git a/mealie/data/img/smashed-carrots.jpg b/mealie/data/img/smashed-carrots.jpg deleted file mode 100644 index 02b59ba13..000000000 Binary files a/mealie/data/img/smashed-carrots.jpg and /dev/null differ diff --git a/mealie/data/img/tequila-beer-and-citrus-cocktail.jpg b/mealie/data/img/tequila-beer-and-citrus-cocktail.jpg deleted file mode 100644 index fa9eb7076..000000000 Binary files a/mealie/data/img/tequila-beer-and-citrus-cocktail.jpg and /dev/null differ diff --git a/mealie/db/database.py b/mealie/db/database.py new file mode 100644 index 000000000..ecb756004 --- /dev/null +++ b/mealie/db/database.py @@ -0,0 +1,36 @@ +from db.db_mealplan import _Meals +from db.db_recipes import _Recipes +from db.db_settings import _Settings +from db.db_themes import _Themes + +""" +Common Actions: + - [ ] Create + - [ ] Read + - [ ] Update + - [ ] Delete + - [ ] Unpack Document Mongo Mostly. + - [ ] Query by Primary Key + +Recipe Actions + - [ ] Query by Category + - [ ] Query by dateAdded + +Progress: + - [x] Recipes + - [ ] MealPlans + - [ ] Site Settings + - [ ] Themes + +""" + + +class Database: + def __init__(self) -> None: + self.recipes = _Recipes() + self.meals = _Meals() + self.settings = _Settings() + self.themes = _Themes() + + +db = Database() diff --git a/mealie/db/db_base.py b/mealie/db/db_base.py new file mode 100644 index 000000000..165ae8623 --- /dev/null +++ b/mealie/db/db_base.py @@ -0,0 +1,109 @@ +import json + +import mongoengine +from settings import USE_MONGO, USE_TINYDB + +from db.tinydb.baseclass import StoreBase + + +class BaseDocument: + def __init__(self) -> None: + self.primary_key: str + self.store: StoreBase + self.document: mongoengine.Document + + @staticmethod + def _unpack_mongo(document) -> dict: # TODO: Probably Put a version in each class to speed up reads? + document = json.loads(document.to_json()) + del document["_id"] + + # Recipe Cleanup + try: + document["dateAdded"] = document["dateAdded"]["$date"] + except: + pass + + try: + document["uid"] = document["uid"]["$uuid"] + except: + pass + + # Meal Plan + try: + document["startDate"] = document["startDate"]["$date"] + document["endDate"] = document["endDate"]["$date"] + + meals = [] + for meal in document["meals"]: + meal["date"] = meal["date"]["$date"] + meals.append(meal) + document["meals"] = meals + except: + pass + + + return document + + def get_all(self, limit: int = None, order_by: str = "dateAdded"): + if USE_MONGO: + documents = self.document.objects.order_by(str(order_by)).limit(limit) + docs = [] + for item in documents: + doc = BaseDocument._unpack_mongo(item) + docs.append(doc) + if limit == 1: + return docs[0] + return docs + + elif USE_TINYDB: + return self.store.get_all() + + def get( + self, match_value: str, match_key: str = None, limit=1 + ) -> dict or list[dict]: + """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. + + + Args: \n + match_value (str): A value used to match against the key/value in the database \n + match_key (str, optional): They key to match the value against. Defaults to None. \n + limit (int, optional): A limit to returned responses. Defaults to 1. \n + + Returns: + dict or list[dict]: + """ + if match_key == None: + match_key = self.primary_key + + if USE_MONGO: + document = self.document.objects.get(**{str(match_key): match_value}) + db_entry = BaseDocument._unpack_mongo(document) + + elif USE_TINYDB: + db_entry = self.store.get(match_value, match_key, limit=limit) + + else: + raise Exception("No database type established") + + if limit == 1 and type(db_entry) == list: + return db_entry[0] + else: + return db_entry + + def save_new(self, document: dict) -> str: + if USE_MONGO: + new_document = self.document(**document) + new_document.save() + return new_document + elif USE_TINYDB: + return self.store.save(document) + + def delete(self, primary_key) -> dict: + if USE_MONGO: + document = self.document.objects.get(**{str(self.primary_key): primary_key}) + + if document: + document.delete() + elif USE_TINYDB: + self.store.delete(primary_key) diff --git a/mealie/db/db_mealplan.py b/mealie/db/db_mealplan.py new file mode 100644 index 000000000..9c8ec2bba --- /dev/null +++ b/mealie/db/db_mealplan.py @@ -0,0 +1,59 @@ +from typing import List + +from settings import USE_MONGO, USE_TINYDB + +from db.db_base import BaseDocument +from db.db_setup import USE_MONGO, USE_TINYDB, tiny_db +from db.mongo.meal_models import MealDocument, MealPlanDocument + + +class _Meals(BaseDocument): + def __init__(self) -> None: + self.primary_key = "uid" + if USE_TINYDB: + self.store = tiny_db.meals + self.document = MealPlanDocument + + @staticmethod + def _process_meals(meals: List[dict]) -> List[MealDocument]: + """Turns a list of Meals in dictionary form into a list of + MealDocuments that can be attached to a MealPlanDocument + + + Args: \n + meals (List[dict]): From a Pydantic Class in meal_services.py \n + + Returns: + a List of MealDocuments + """ + meal_docs = [] + for meal in meals: + meal_doc = MealDocument(**meal) + meal_docs.append(meal_doc) + + return meal_docs + + def save_new(self, plan_data: dict) -> None: + """Saves a new meal plan into the database + + Args: \n + plan_data (dict): From a Pydantic Class in meal_services.py \n + """ + + if USE_MONGO: + plan_data["meals"] = _Meals._process_meals(plan_data["meals"]) + document = self.document(**plan_data) + + document.save() + elif USE_TINYDB: + pass + + def update(self, uid: str, plan_data: dict) -> dict: + if USE_MONGO: + document = self.document.objects.get(uid=uid) + if document: + new_meals = _Meals._process_meals(plan_data["meals"]) + document.update(set__meals=new_meals) + document.save() + elif USE_TINYDB: + pass diff --git a/mealie/db/db_recipes.py b/mealie/db/db_recipes.py new file mode 100644 index 000000000..993571d00 --- /dev/null +++ b/mealie/db/db_recipes.py @@ -0,0 +1,51 @@ +from settings import USE_MONGO, USE_TINYDB + +from db.db_base import BaseDocument +from db.db_setup import tiny_db +from db.mongo.recipe_models import RecipeDocument + + +class _Recipes(BaseDocument): + def __init__(self) -> None: + self.primary_key = "slug" + if USE_TINYDB: + self.store = tiny_db.recipes + self.document = RecipeDocument + + def update(self, slug: str, new_data: dict) -> None: + if USE_MONGO: + document = self.document.objects.get(slug=slug) + + if document: + document.update(set__name=new_data.get("name")) + document.update(set__description=new_data.get("description")) + document.update(set__image=new_data.get("image")) + document.update(set__recipeYield=new_data.get("recipeYield")) + document.update(set__recipeIngredient=new_data.get("recipeIngredient")) + document.update( + set__recipeInstructions=new_data.get("recipeInstructions") + ) + document.update(set__totalTime=new_data.get("totalTime")) + + document.update(set__slug=new_data.get("slug")) + document.update(set__categories=new_data.get("categories")) + document.update(set__tags=new_data.get("tags")) + document.update(set__notes=new_data.get("notes")) + document.update(set__orgURL=new_data.get("orgURL")) + document.update(set__rating=new_data.get("rating")) + document.update(set__extras=new_data.get("extras")) + document.save() + + return new_data.get("slug") + elif USE_TINYDB: + self.store.update_doc(slug, new_data) + return new_data.get("slug") + + def update_image(self, slug: str, extension: str) -> None: + if USE_MONGO: + document = self.document.objects.get(slug=slug) + + if document: + document.update(set__image=f"{slug}.{extension}") + elif USE_TINYDB: + self.store.update_doc(slug, {"image": f"{slug}.{extension}"}) diff --git a/mealie/db/db_settings.py b/mealie/db/db_settings.py new file mode 100644 index 000000000..6eed3d03c --- /dev/null +++ b/mealie/db/db_settings.py @@ -0,0 +1,36 @@ +from settings import USE_MONGO, USE_TINYDB + +from db.db_base import BaseDocument +from db.db_setup import USE_MONGO, USE_TINYDB, tiny_db +from db.mongo.settings_models import SiteSettingsDocument, WebhooksDocument + + +class _Settings(BaseDocument): + def __init__(self) -> None: + + self.primary_key = "name" + + if USE_TINYDB: + self.store = tiny_db.settings + + self.document = SiteSettingsDocument + + def save_new(self, main: dict, webhooks: dict) -> str: + + if USE_MONGO: + main["webhooks"] = WebhooksDocument(**webhooks) + new_doc = self.document(**main) + return new_doc.save() + + elif USE_TINYDB: + main["webhooks"] = webhooks + return self.store.save(main) + + def update(self, name: str, new_data: dict) -> dict: + if USE_MONGO: + document = self.document.objects.get(name=name) + if document: + document.update(set__webhooks=WebhooksDocument(**new_data["webhooks"])) + document.save() + elif USE_TINYDB: + pass diff --git a/mealie/db/db_setup.py b/mealie/db/db_setup.py index 269b716f9..8f6c08d95 100644 --- a/mealie/db/db_setup.py +++ b/mealie/db/db_setup.py @@ -1,37 +1,9 @@ -import json -from typing import List - -import mongoengine from settings import USE_MONGO, USE_TINYDB -from db.mongo.meal_models import MealDocument, MealPlanDocument -from db.mongo.recipe_models import RecipeDocument -from db.mongo.settings_models import SiteSettingsDocument, SiteThemeDocument -from db.tinydb.baseclass import StoreBase - -""" -Common Actions: - - [ ] Create - - [ ] Read - - [ ] Update - - [ ] Delete - - [ ] Unpack Document Mongo Mostly. - - [ ] Query by Primary Key - -Recipe Actions - - [ ] Query by Category - - [ ] Query by dateAdded - -Progress: - - [x] Recipes - - [ ] MealPlans - - [ ] Site Settings - - [ ] Themes - -""" +from db.tinydb.tinydb_setup import TinyDatabase +tiny_db: TinyDatabase = None if USE_TINYDB: - from db.tinydb.tinydb_setup import TinyDatabase tiny_db = TinyDatabase() @@ -39,194 +11,3 @@ elif USE_MONGO: from db.mongo.mongo_setup import global_init as mongo_global_init mongo_global_init() - - -class BaseDocument: - def __init__(self) -> None: - self.primary_key: str - self.store: StoreBase - self.document: mongoengine.Document - - @staticmethod - def _unpack_mongo(document) -> dict: - document = json.loads(document.to_json()) - del document["_id"] - - # Recipe Cleanup - try: - document["dateAdded"] = document["dateAdded"]["$date"] - except: - pass - - try: - document["uid"] = document["uid"]["$uuid"] - except: - pass - - # Meal Plan - try: - document["startDate"] = document["startDate"]["$date"] - document["endDate"] = document["endDate"]["$date"] - - meals = [] - for meal in document["meals"]: - meal["date"] = meal["date"]["$date"] - meals.append(meal) - document["meals"] = meals - except: - pass - - return document - - def get_all(self, limit: int = None, order_by: str = "dateAdded") -> list[dict]: - if USE_MONGO: - documents = self.document.objects.order_by(str(order_by)).limit(limit) - docs = [] - for item in documents: - doc = BaseDocument._unpack_mongo(item) - docs.append(doc) - if limit == 1: - return docs[0] - return docs - - elif USE_TINYDB: - return self.store.get_all() - - def get(self, match_value: str, match_key: str = None, limit=1) -> dict: - if USE_MONGO: - document = self.document.objects.get(**{str(match_key): match_value}) - return BaseDocument._unpack_mongo(document) - - elif USE_TINYDB: - return self.store.get(match_value, match_key, limit=limit) - - def save_new(self, document: dict) -> str: - if USE_MONGO: - new_document = self.document(**document) - new_document.save() - return new_document - elif USE_TINYDB: - return self.store.save(document) - - def delete(self, primary_key) -> dict: - if USE_MONGO: - document = self.document.objects.get(**{str(self.primary_key): primary_key}) - - if document: - document.delete() - return "Document Deleted" - elif USE_TINYDB: - self.store.delete(primary_key) - - -class Database: - def __init__(self) -> None: - self.recipes = self._Recipes() - self.meals = self._Meals() - self.settings = self._Settings() - self.themes = self._Themes() - - class _Recipes(BaseDocument): - def __init__(self) -> None: - self.primary_key = "slug" - if USE_TINYDB: - self.store = tiny_db.recipes - self.document = RecipeDocument - - def update(self, slug: str, new_data: dict) -> None: - if USE_MONGO: - document = self.document.objects.get(slug=slug) - - if document: - document.update(set__name=new_data.get("name")) - document.update(set__description=new_data.get("description")) - document.update(set__image=new_data.get("image")) - document.update(set__recipeYield=new_data.get("recipeYield")) - document.update( - set__recipeIngredient=new_data.get("recipeIngredient") - ) - document.update( - set__recipeInstructions=new_data.get("recipeInstructions") - ) - document.update(set__totalTime=new_data.get("totalTime")) - - document.update(set__slug=new_data.get("slug")) - document.update(set__categories=new_data.get("categories")) - document.update(set__tags=new_data.get("tags")) - document.update(set__notes=new_data.get("notes")) - document.update(set__orgURL=new_data.get("orgURL")) - document.update(set__rating=new_data.get("rating")) - document.update(set__extras=new_data.get("extras")) - document.save() - - return new_data.get("slug") - elif USE_TINYDB: - self.store.update_doc(slug, new_data) - return new_data.get("slug") - - def update_image(self, slug: str, extension: str) -> None: - if USE_MONGO: - document = self.document.objects.get(slug=slug) - - if document: - document.update(set__image=f"{slug}.{extension}") - elif USE_TINYDB: - self.store.update_doc(slug, {"image": f"{slug}.{extension}"}) - - class _Meals(BaseDocument): - def __init__(self) -> None: - self.primary_key = "uid" - if USE_TINYDB: - self.store = tiny_db.meals - self.document = MealPlanDocument - - def update(self, uid: str, new_meals: List[MealDocument]) -> dict: - if USE_MONGO: - document = self.document.objects.get(uid=uid) - if document: - document.update(set__meals=new_meals) - document.save() - elif USE_TINYDB: - pass - - class _Settings(BaseDocument): - def __init__(self) -> None: - - self.primary_key = "name" - - if USE_TINYDB: - self.store = tiny_db.settings - - self.document = SiteSettingsDocument - - def save_new(self, main: dict, webhooks: dict) -> str: - - if USE_MONGO: - new_doc = self.document(**main) - return new_doc.save() - - elif USE_TINYDB: - main["webhooks"] = webhooks - return self.store.save(main) - - def update(self, key: str, new_data: dict) -> dict: - if USE_MONGO: - pass - elif USE_TINYDB: - pass - - class _Themes(BaseDocument): - def __init__(self) -> None: - self.primary_key = "name" - if USE_TINYDB: - self.store = tiny_db.themes - self.document = SiteThemeDocument - - def update(self, key: str, new_data: dict) -> dict: - if USE_MONGO: - pass - elif USE_TINYDB: - pass - - -db = Database() diff --git a/mealie/db/db_themes.py b/mealie/db/db_themes.py new file mode 100644 index 000000000..1f009787f --- /dev/null +++ b/mealie/db/db_themes.py @@ -0,0 +1,19 @@ +from settings import USE_MONGO, USE_TINYDB + +from db.db_base import BaseDocument +from db.db_setup import USE_MONGO, USE_TINYDB, tiny_db +from db.mongo.settings_models import SiteThemeDocument + + +class _Themes(BaseDocument): + def __init__(self) -> None: + self.primary_key = "name" + if USE_TINYDB: + self.store = tiny_db.themes + self.document = SiteThemeDocument + + def update(self, key: str, new_data: dict) -> dict: + if USE_MONGO: + pass + elif USE_TINYDB: + pass diff --git a/mealie/services/meal_services.py b/mealie/services/meal_services.py index 6d1cb6eda..f4ec5cb58 100644 --- a/mealie/services/meal_services.py +++ b/mealie/services/meal_services.py @@ -3,8 +3,7 @@ from datetime import date, timedelta from pathlib import Path from typing import List, Optional -from db.db_setup import db -from db.mongo.meal_models import MealDocument +from db.database import db from pydantic import BaseModel from services.recipe_services import Recipe @@ -81,14 +80,6 @@ class MealPlan(BaseModel): self.meals = meals def save_to_db(self): - - meal_docs = [] - for meal in self.meals: - meal = meal.dict() - meal_doc = MealDocument(**meal) - meal_docs.append(meal_doc) - - self.meals = meal_docs db.meals.save_new(self.dict()) @staticmethod @@ -99,14 +90,7 @@ class MealPlan(BaseModel): return all_meals def update(self, uid): - - meal_docs = [] - for meal in self.meals: - meal = meal.dict() - meal_doc = MealDocument(**meal) - meal_docs.append(meal_doc) - - db.meals.update(uid, meal_docs) + db.meals.update(uid, self.dict()) @staticmethod def delete(uid): @@ -117,11 +101,7 @@ class MealPlan(BaseModel): """ Returns the meal slug for Today """ meal_plan = db.meals.get_all(limit=1, order_by="startDate") - meal_docs = [] - for meal in meal_plan["meals"]: - print(meal) - meal_doc = Meal(**meal) - meal_docs.append(meal_doc) + meal_docs = [Meal(**meal) for meal in meal_plan["meals"]] for meal in meal_docs: if meal.date == date.today(): diff --git a/mealie/services/recipe_services.py b/mealie/services/recipe_services.py index 73c2797f5..eeae4e26c 100644 --- a/mealie/services/recipe_services.py +++ b/mealie/services/recipe_services.py @@ -3,7 +3,7 @@ import json from pathlib import Path from typing import Any, List, Optional -from db.db_setup import db +from db.database import db from pydantic import BaseModel, validator from slugify import slugify diff --git a/mealie/services/settings_services.py b/mealie/services/settings_services.py index 7729ab2e7..e483a86ac 100644 --- a/mealie/services/settings_services.py +++ b/mealie/services/settings_services.py @@ -1,21 +1,22 @@ import json from typing import List, Optional -from db.mongo.settings_models import (SiteSettingsDocument, SiteThemeDocument, - ThemeColorsDocument, WebhooksDocument) +from db.database import db +from db.mongo.settings_models import ( + SiteSettingsDocument, + SiteThemeDocument, + ThemeColorsDocument, + WebhooksDocument, +) from pydantic import BaseModel from startup import USE_TINYDB from utils.logger import logger class Webhooks(BaseModel): - webhookTime: str - webhookURLs: Optional[List[str]] - enabled: bool - - @staticmethod - def run(): - pass + webhookTime: str = "00:00" + webhookURLs: Optional[List[str]] = [] + enabled: bool = "false" class SiteSettings(BaseModel): @@ -43,25 +44,18 @@ class SiteSettings(BaseModel): @staticmethod def get_site_settings(): - if USE_TINYDB: - document = tinydb.settings.get("main") - else: - try: - document = SiteSettingsDocument.objects.get(name="main") - except: - webhooks = WebhooksDocument() - document = SiteSettingsDocument(name="main", webhooks=webhooks) - document.save() + try: + document = db.settings.get("main") + except: + webhooks = Webhooks() + default_entry = SiteSettings(name="main", webhooks=webhooks) + document = db.settings.save_new(default_entry.dict(), webhooks.dict()) - return SiteSettings._unpack_doc(document) + return SiteSettings(**document) def update(self): - document = SiteSettingsDocument.objects.get(name="main") - new_webhooks = WebhooksDocument(**self.webhooks.dict()) - document.update(set__webhooks=new_webhooks) - - document.save() + db.settings.update(name="main", new_data=self.dict()) class Colors(BaseModel): diff --git a/mealie/test/conftest.py b/mealie/test/conftest.py new file mode 100644 index 000000000..299af4d01 --- /dev/null +++ b/mealie/test/conftest.py @@ -0,0 +1,2 @@ +from pytest import fixture + diff --git a/mealie/test/pytest.ini b/mealie/test/pytest.ini new file mode 100644 index 000000000..321d6b964 --- /dev/null +++ b/mealie/test/pytest.ini @@ -0,0 +1,4 @@ +[pytest] +python_files = test_* +python_classes = *Tests +python_functions = test_* \ No newline at end of file diff --git a/mealie/test/test_migrations/__init__.py b/mealie/test/test_migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/mealie/test/test_nextcloud.py b/mealie/test/test_migrations/test_nextcloud.py similarity index 84% rename from mealie/test/test_nextcloud.py rename to mealie/test/test_migrations/test_nextcloud.py index c6e53f9bf..8eaa84f5a 100644 --- a/mealie/test/test_nextcloud.py +++ b/mealie/test/test_migrations/test_nextcloud.py @@ -11,8 +11,8 @@ from services.migrations.nextcloud import ( from services.recipe_services import Recipe CWD = Path(__file__).parent -NEXTCLOUD_DIR = CWD.joinpath("data", "nextcloud_recipes") -TEMP_NEXTCLOUD = CWD.parent.joinpath("data", "temp", "nextcloud") +NEXTCLOUD_DIR = CWD.parent.joinpath("data", "nextcloud_recipes") +TEMP_NEXTCLOUD = CWD.parent.parent.joinpath("data", "temp", "nextcloud") @pytest.mark.parametrize( @@ -39,5 +39,5 @@ def test_zip_extraction(file_name: str, final_path: Path): ) def test_nextcloud_migration(recipe_dir: Path): recipe = import_recipes(recipe_dir) - assert type(recipe) == Recipe + assert isinstance(recipe, Recipe) IMG_DIR.joinpath(recipe.image).unlink(missing_ok=True) diff --git a/mealie/test/test_recipes/__init__.py b/mealie/test/test_recipes/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/mealie/test/test_scraper.py b/mealie/test/test_recipes/test_scraper.py similarity index 96% rename from mealie/test/test_scraper.py rename to mealie/test/test_recipes/test_scraper.py index ab635dc4d..2201007d8 100644 --- a/mealie/test/test_scraper.py +++ b/mealie/test/test_recipes/test_scraper.py @@ -10,8 +10,8 @@ from services.scrape_services import ( ) CWD = Path(__file__).parent -RAW_RECIPE_DIR = CWD.joinpath("data", "recipes-raw") -RAW_HTML_DIR = CWD.joinpath("data", "html-raw") +RAW_RECIPE_DIR = CWD.parent.joinpath("data", "recipes-raw") +RAW_HTML_DIR = CWD.parent.joinpath("data", "html-raw") # https://github.com/django/django/blob/stable/1.3.x/django/core/validators.py#L45 url_validation_regex = re.compile(