From 17537bf43af1e8572c0459df69cd87a1fcf805e5 Mon Sep 17 00:00:00 2001 From: Hayden Date: Sun, 10 Jan 2021 20:16:05 -0900 Subject: [PATCH] port recipes over to new schema --- mealie/data/db/recipes.json | 46 +---------- mealie/data/debug/last_recipe.json | 102 ++++++++++++++----------- mealie/db/db_setup.py | 118 ++++++++++++++++++++++++----- mealie/db/tinydb/baseclass.py | 16 ++-- mealie/services/recipe_services.py | 37 ++------- 5 files changed, 174 insertions(+), 145 deletions(-) diff --git a/mealie/data/db/recipes.json b/mealie/data/db/recipes.json index 6580c66fb..e4ed5e6d4 100644 --- a/mealie/data/db/recipes.json +++ b/mealie/data/db/recipes.json @@ -1,45 +1 @@ -{ - "_default": { - "1": { - "name": "Chicken and Rice With Leeks and Salsa Verde", - "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", - "recipeYield": "4 Servings", - "recipeIngredient": [ - "1\u00bd lb. skinless, boneless chicken thighs (4\u20138 depending on size)", - "Kosher salt, freshly ground pepper", - "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": "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": "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": "Drizzle remaining lemon juice over chicken and rice. Serve with salsa verde." - } - ], - "totalTime": "None", - "slug": "chicken-and-rice-with-leeks-and-salsa-verde", - "categories": [], - "tags": [], - "dateAdded": null, - "notes": [], - "rating": null, - "orgURL": "https://www.bonappetit.com/recipe/chicken-and-rice-with-leeks-and-salsa-verde", - "extras": {} - } - } -} \ No newline at end of file +{"_default": {"3": {"name": "Chicken and Rice With Leeks and Salsa Verde", "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", "recipeYield": "4 Servings", "recipeIngredient": ["1\u00bd lb. skinless, boneless chicken thighs (4\u20138 depending on size)", "Kosher salt, freshly ground pepper", "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": "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": "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": "Drizzle remaining lemon juice over chicken and rice. Serve with salsa verde."}], "totalTime": "None", "slug": "chicken-and-rice-with-leeks-and-salsa-verde", "categories": [], "tags": [], "dateAdded": null, "notes": [], "rating": null, "orgURL": "https://www.bonappetit.com/recipe/chicken-and-rice-with-leeks-and-salsa-verde", "extras": {}}}} \ No newline at end of file diff --git a/mealie/data/debug/last_recipe.json b/mealie/data/debug/last_recipe.json index e66b6bfde..460d25cc4 100644 --- a/mealie/data/debug/last_recipe.json +++ b/mealie/data/debug/last_recipe.json @@ -1,29 +1,30 @@ { "@context": "http://schema.org", "@type": "Recipe", - "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", + "articleBody": "In this brothy beans recipe, caramelizing fennel, shallots, and lemon builds a base layer that is sweet, tangy, and bright. Tinned sardines add briney flavor (and protein!)\u2014leave them whole or break them up and fold them into the soup. \u00a0And chopped celery or mushrooms work just as well as fennel if you want to swap. This recipe is part of the 2021\u00a0Feel Good Food Plan, our eight-day dinner plan for starting the year off right.", + "alternativeHeadline": "Tinned sardines add briney flavor (and protein!)\u2014leave them whole or break them up and fold them into the soup.", + "dateModified": "2021-01-10 13:22:02.933000", + "datePublished": "2021-01-01 06:00:00", "keywords": [ "recipes", - "chicken recipes", - "kosher salt", - "black pepper", - "butter", - "leek", - "lemon zest", - "rice", - "chicken broth", - "anchovy", - "garlic", - "capers", - "herb", - "olive oil", "healthyish", + "fennel", + "lemon", + "olive oil", + "shallot", + "garlic", + "herb", + "white wine", + "chicken broth", + "cannellini beans", + "sardine", + "parsley", + "kosher salt", + "feel good food plan", + "feel good food plan 2021", "web" ], - "thumbnailUrl": "https://assets.bonappetit.com/photos/5f29796456f43685a49327fb/1:1/w_1125,h_1125,c_limit/Chicken-and-Rice-With-Leeks-Salsa-Verde-01.jpg", + "thumbnailUrl": "https://assets.bonappetit.com/photos/5fdbe3ac045c6b67ead1938c/1:1/w_2880,h_2880,c_limit/BA0221feelgood06_web.jpg", "publisher": { "@context": "https://schema.org", "@type": "Organization", @@ -47,49 +48,58 @@ "author": [ { "@type": "Person", - "name": "Deb Perelman", - "sameAs": "https://bon-appetit.com/contributor/deb-perelman/" + "name": "Devonn Francis", + "sameAs": "https://bon-appetit.com/contributor/devonn-francis/" } ], "aggregateRating": { "@type": "AggregateRating", - "ratingValue": 4.02, - "ratingCount": 48 + "ratingValue": 4.1, + "ratingCount": 12 }, - "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", + "description": "Tinned sardines add briney flavor (and protein!)\u2014leave them whole or break them up and fold them into the soup.", + "image": "braised-beans-and-sardines-with-fennel.jpg", + "headline": "Braised Beans and Sardines With Fennel", + "name": "Braised Beans and Sardines With Fennel", "recipeIngredient": [ - "1\u00bd lb. skinless, boneless chicken thighs (4\u20138 depending on size)", - "Kosher salt, freshly ground pepper", - "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" + "1 fennel bulb with fronds, stalks and fronds removed", + "1 lemon, halved", + "\u00bc cup extra-virgin olive oil", + "2 medium shallots, thinly sliced", + "6 garlic cloves, thinly sliced", + "Small handful of mixed hardy herb sprigs (such as bay leaves, thyme, and/or rosemary)", + "\u00bd tsp. crushed red pepper flakes", + "\u00bc cup dry white wine", + "6 cups low-sodium chicken broth", + "2 15-oz. cans cannellini (white kidney) or cranberry beans, rinsed1 4.4-oz. tin oil-packed sardines, drained", + "1 (loosely packed) cup very coarsely chopped parsley", + "Kosher salt", + "Toasted seeded bread (for serving)" ], "recipeInstructions": [ { - "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": "Slice fennel bulb in half lengthwise and cut each half lengthwise into 3 wedges. Thinly slice 1 lemon half into rounds and wriggle out and discard any seeds; leave remaining half intact and set aside." }, { - "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": "Heat oil in a medium pot over medium-high. Add fennel, shallots, garlic, hardy herbs, lemon rounds, and red pepper flakes and cook, stirring occasionally, until fennel and lemon are softened slightly and golden brown in spots, 5\u20137 minutes." }, { - "text": "Drizzle remaining lemon juice over chicken and rice. Serve with salsa verde." + "text": "Using tongs, transfer lemon rounds to a small bowl; set aside. Add wine to pot and cook until reduced by half, about\u00a0 2 minutes. Pour in broth and bring to a boil. Reduce heat to medium-low and simmer, stirring occasionally, until fennel is tender, about 5 minutes. Add beans and simmer until beans soak up some of the broth and are warmed through, 8\u201310 minutes." + }, + { + "text": "Meanwhile, working one at a time, slice open each sardine with the tip of a paring knife and remove any visible bones; discard. Separate fillets from one another and place in a small bowl. Squeeze juice from remaining reserved lemon half over fillets." + }, + { + "text": "Fish out and discard any hardy herbs and stems you can from braise. Stir in parsley; taste and season broth with more salt if needed." + }, + { + "text": "Divide braise among bowls; top with reserved lemon slices and sardines. Serve with bread alongside." } ], - "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", + "recipeYield": "4 servings", + "url": "https://www.bonappetit.com/recipe/braised-beans-and-sardines-with-fennel", + "slug": "braised-beans-and-sardines-with-fennel", + "orgURL": "https://www.bonappetit.com/recipe/braised-beans-and-sardines-with-fennel", "categories": [], "tags": [], "dateAdded": null, diff --git a/mealie/db/db_setup.py b/mealie/db/db_setup.py index 469f50e57..203015a31 100644 --- a/mealie/db/db_setup.py +++ b/mealie/db/db_setup.py @@ -1,7 +1,9 @@ +import json + import mongoengine from settings import USE_MONGO, USE_TINYDB -from db.mongo.meal_models import MealDocument, MealPlanDocument +from db.mongo.meal_models import MealPlanDocument from db.mongo.recipe_models import RecipeDocument from db.mongo.settings_models import SiteSettingsDocument, SiteThemeDocument from db.tinydb.baseclass import StoreBase @@ -19,6 +21,12 @@ Recipe Actions - [ ] Query by Category - [ ] Query by dateAdded +Progress: + - [x] Recipes + - [ ] MealPlans + - [ ] Site Settings + - [ ] Themes + """ if USE_TINYDB: @@ -38,31 +46,55 @@ class BaseDocument: self.store: StoreBase self._document: mongoengine.Document + @staticmethod + def _unpack_mongo(document) -> dict: + document = json.loads(document.to_json()) + del document["_id"] + + document["dateAdded"] = document["dateAdded"]["$date"] + + 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) + + 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: - return self._document.objects.get(match_key=match_value).limit(limit) + 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) + return self.store.get(match_value, match_key, limit=limit) def save_new(self, document: dict) -> str: if USE_MONGO: - self._document = self._document(**document) - self._document.save() - return self._document.slug + new_document = self._document(**document) + new_document.save() + return new_document.slug elif USE_TINYDB: return self.store.save(document) - def update(self) -> dict: + def delete(self, primary_key) -> dict: if USE_MONGO: - pass - elif USE_TINYDB: - pass + document = self._document.objects.get( + **{str(self.primary_key): primary_key} + ) - def delete(self) -> dict: - if USE_MONGO: - pass + if document: + document.delete() + return "Document Deleted" elif USE_TINYDB: - pass + self.store.delete(primary_key) class Database: @@ -78,7 +110,42 @@ class Database: if USE_TINYDB: self.store = tiny_db.recipes self._document = RecipeDocument - pass + + 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__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() + elif USE_TINYDB: + self.store.update_doc(slug, new_data) + + 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: @@ -86,7 +153,12 @@ class Database: if USE_TINYDB: self.store = tiny_db.meals self.document = MealPlanDocument - pass + + def update(self, key: str, new_data: dict) -> dict: + if USE_MONGO: + pass + elif USE_TINYDB: + pass class _Settings(BaseDocument): def __init__(self) -> None: @@ -94,7 +166,12 @@ class Database: if USE_TINYDB: self.store = tiny_db.settings self.document = SiteSettingsDocument - pass + + def update(self, key: str, new_data: dict) -> dict: + if USE_MONGO: + pass + elif USE_TINYDB: + pass class _Themes(BaseDocument): def __init__(self) -> None: @@ -102,7 +179,12 @@ class Database: if USE_TINYDB: self.store = tiny_db.themes self.document = SiteThemeDocument - pass + + def update(self, key: str, new_data: dict) -> dict: + if USE_MONGO: + pass + elif USE_TINYDB: + pass db = Database() diff --git a/mealie/db/tinydb/baseclass.py b/mealie/db/tinydb/baseclass.py index 60579e7ab..698953c26 100644 --- a/mealie/db/tinydb/baseclass.py +++ b/mealie/db/tinydb/baseclass.py @@ -13,16 +13,18 @@ class StoreBase: if data != []: raise Exception( - f"Cannot save document. Primary Key: {self.primary_key} already exists" + f"Cannot Save, Primary Key: {self.primary_key} already exists" ) else: - return self.store.upsert( - document, Query()[self.primary_key] == document[self.primary_key] - ) + self.store.insert(document) + return document["slug"] def delete(self, document_primary_key: str): self.store.remove(where(self.primary_key) == document_primary_key) + def get_all(self) -> list: + return self.store.all() + def get(self, value: str, key: str = None, limit: int = None) -> list or dict: """Retrieves an entry from the database matching the key/value provided. If no key is provided the classes primary_key will be used instead @@ -49,14 +51,16 @@ class StoreBase: return data def update_doc(self, id, document): - data: dict = self.get(self.primary_key, id, limit=1) + data: dict = self.get(id, self.primary_key, limit=1) if data: if data[self.primary_key] == document[self.primary_key]: data.update(document) + return document["slug"] else: self.delete(id) - return self.save(document) + self.save(document) + return document["slug"] elif not data: raise Exception( diff --git a/mealie/services/recipe_services.py b/mealie/services/recipe_services.py index 881e0633b..3e8fcf40c 100644 --- a/mealie/services/recipe_services.py +++ b/mealie/services/recipe_services.py @@ -96,9 +96,9 @@ class Recipe(BaseModel): def get_by_slug(_cls, slug: str): """ Returns a recipe dictionary from the slug """ - document = RecipeDocument.objects.get(slug=slug) + document = db.recipes.get(slug, "slug") - return Recipe._unpack_doc(document) + return document def save_to_db(self) -> str: recipe_dict = self.dict() @@ -123,39 +123,16 @@ class Recipe(BaseModel): def delete(recipe_slug: str) -> str: """ Removes the recipe from the database by slug """ delete_image(recipe_slug) - document = RecipeDocument.objects.get(slug=recipe_slug) - - if document: - document.delete() - return "Document Deleted" + db.recipes.delete(recipe_slug) + return "Document Deleted" def update(self, recipe_slug: str): """ Updates the recipe from the database by slug""" - document = RecipeDocument.objects.get(slug=recipe_slug) - - if document: - document.update(set__name=self.name) - document.update(set__description=self.description) - document.update(set__image=self.image) - document.update(set__recipeYield=self.recipeYield) - document.update(set__recipeIngredient=self.recipeIngredient) - document.update(set__recipeInstructions=self.recipeInstructions) - document.update(set__totalTime=self.totalTime) - - document.update(set__categories=self.categories) - document.update(set__tags=self.tags) - document.update(set__notes=self.notes) - document.update(set__orgURL=self.orgURL) - document.update(set__rating=self.rating) - document.update(set__extras=self.extras) - document.save() + db.recipes.update(recipe_slug, self.dict()) @staticmethod def update_image(slug: str, extension: str): - document = RecipeDocument.objects.get(slug=slug) - - if document: - document.update(set__image=f"{slug}.{extension}") + db.recipes.update_image(slug, extension) def read_requested_values(keys: list, max_results: int = 0) -> List[dict]: @@ -171,7 +148,7 @@ def read_requested_values(keys: list, max_results: int = 0) -> List[dict]: """ recipe_list = [] - for recipe in RecipeDocument.objects.order_by("dateAdded").limit(max_results): + for recipe in db.recipes.get_all(limit=max_results, order_by="dateAdded"): recipe_details = {} for key in keys: try: