start settings migration
11
.vscode/settings.json
vendored
|
@ -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/"]
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
Before Width: | Height: | Size: 519 KiB |
Before Width: | Height: | Size: 371 KiB |
Before Width: | Height: | Size: 259 KiB |
Before Width: | Height: | Size: 794 KiB |
Before Width: | Height: | Size: 572 KiB |
Before Width: | Height: | Size: 1.5 MiB |
Before Width: | Height: | Size: 528 KiB |
Before Width: | Height: | Size: 452 KiB |
Before Width: | Height: | Size: 212 KiB |
Before Width: | Height: | Size: 393 KiB |
Before Width: | Height: | Size: 556 KiB |
Before Width: | Height: | Size: 317 KiB |
Before Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 309 KiB |
Before Width: | Height: | Size: 650 KiB |
Before Width: | Height: | Size: 889 KiB |
Before Width: | Height: | Size: 294 KiB |
Before Width: | Height: | Size: 602 KiB |
Before Width: | Height: | Size: 184 KiB |
Before Width: | Height: | Size: 788 KiB |
Before Width: | Height: | Size: 664 KiB |
Before Width: | Height: | Size: 985 KiB |
Before Width: | Height: | Size: 154 KiB |
Before Width: | Height: | Size: 413 KiB |
Before Width: | Height: | Size: 184 KiB |
36
mealie/db/database.py
Normal file
|
@ -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()
|
109
mealie/db/db_base.py
Normal file
|
@ -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)
|
59
mealie/db/db_mealplan.py
Normal file
|
@ -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
|
51
mealie/db/db_recipes.py
Normal file
|
@ -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}"})
|
36
mealie/db/db_settings.py
Normal file
|
@ -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
|
|
@ -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()
|
||||
|
|
19
mealie/db/db_themes.py
Normal file
|
@ -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
|
|
@ -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():
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
2
mealie/test/conftest.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
from pytest import fixture
|
||||
|
4
mealie/test/pytest.ini
Normal file
|
@ -0,0 +1,4 @@
|
|||
[pytest]
|
||||
python_files = test_*
|
||||
python_classes = *Tests
|
||||
python_functions = test_*
|
0
mealie/test/test_migrations/__init__.py
Normal file
|
@ -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)
|
0
mealie/test/test_recipes/__init__.py
Normal file
|
@ -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(
|