mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-01-08 01:51:23 -05:00
Release v0.1.0 Candidate (#85)
* Changed uvicorn port to 80 * Changed port in docker-compose to match dockerfile * Readded environment variables in docker-compose * production image rework * Use opengraph metadata to make basic recipe cards when full recipe metadata is not available * fixed instrucitons on parse * add last_recipe * automated testing * roadmap update * Sqlite (#75) * file structure * auto-test * take 2 * refactor ap scheduler and startup process * fixed scraper error * database abstraction * database abstraction * port recipes over to new schema * meal migration * start settings migration * finale mongo port * backup improvements * migration imports to new DB structure * unused import cleanup * docs strings * settings and theme import logic * cleanup * fixed tinydb error * requirements * fuzzy search * remove scratch file * sqlalchemy models * improved search ui * recipe models almost done * sql modal population * del scratch * rewrite database model mixins * mostly grabage * recipe updates * working sqllite * remove old files and reorganize * final cleanup Co-authored-by: Hayden <hay-kot@pm.me> * Backup card (#78) * backup / import dialog * upgrade to new tag method * New import card * rename settings.py to app_config.py * migrate to poetry for development * fix failing test Co-authored-by: Hayden <hay-kot@pm.me> * added mkdocs to docker-compose * Translations (#72) * Translations + danish * changed back proxy target to use ENV * Resolved more merge conflicts * Removed test in translation * Documentation of translations * Updated translations * removed old packages Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com> * fail to start bug fixes * feature: prep/cook/total time slots (#80) Co-authored-by: Hayden <hay-kot@pm.me> * missing bind attributes * Bug fixes (#81) * fix: url remains after succesful import * docs: changelog + update todos * arm image * arm compose * compose updates * update poetry * arm support Co-authored-by: Hayden <hay-kot@pm.me> * dockerfile hotfix * dockerfile hotfix * Version Release Final Touches (#84) * Remove slim * bug: opacity issues * bug: startup failure with no database * ci/cd on dev branch * formatting * v0.1.0 documentation Co-authored-by: Hayden <hay-kot@pm.me> * db init hotfix * bug: fix crash in mongo * fix mongo bug * fixed version notifier * finale changelog Co-authored-by: kentora <=> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk>
This commit is contained in:
15
mealie/db/database.py
Normal file
15
mealie/db/database.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from db.db_mealplan import _Meals
|
||||
from db.db_recipes import _Recipes
|
||||
from db.db_settings import _Settings
|
||||
from db.db_themes import _Themes
|
||||
|
||||
|
||||
class Database:
|
||||
def __init__(self) -> None:
|
||||
self.recipes = _Recipes()
|
||||
self.meals = _Meals()
|
||||
self.settings = _Settings()
|
||||
self.themes = _Themes()
|
||||
|
||||
|
||||
db = Database()
|
||||
191
mealie/db/db_base.py
Normal file
191
mealie/db/db_base.py
Normal file
@@ -0,0 +1,191 @@
|
||||
import json
|
||||
from typing import Union
|
||||
|
||||
import mongoengine
|
||||
from app_config import USE_MONGO, USE_SQL
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from db.sql.db_session import create_session
|
||||
from db.sql.model_base import SqlAlchemyBase
|
||||
|
||||
|
||||
class BaseDocument:
|
||||
def __init__(self) -> None:
|
||||
self.primary_key: str
|
||||
self.store: str
|
||||
self.document: mongoengine.Document
|
||||
self.sql_model: SqlAlchemyBase
|
||||
self.create_session = create_session
|
||||
|
||||
@staticmethod # TODO: Probably Put a version in each class to speed up reads?
|
||||
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 = None):
|
||||
if USE_MONGO:
|
||||
if order_by:
|
||||
documents = self.document.objects.order_by(str(order_by)).limit(limit)
|
||||
elif limit == None:
|
||||
documents = self.document.objects()
|
||||
else:
|
||||
documents = self.document.objects().limit(limit)
|
||||
|
||||
docs = [BaseDocument._unpack_mongo(item) for item in documents]
|
||||
|
||||
if limit == 1:
|
||||
return docs[0]
|
||||
return docs
|
||||
|
||||
elif USE_SQL:
|
||||
session = create_session()
|
||||
list = [x.dict() for x in session.query(self.sql_model).all()]
|
||||
session.close()
|
||||
|
||||
if limit == 1:
|
||||
return list[0]
|
||||
|
||||
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:
|
||||
match_key = self.primary_key
|
||||
|
||||
result = (
|
||||
session.query(self.sql_model).filter_by(**{match_key: match_value}).one()
|
||||
)
|
||||
|
||||
return session, result
|
||||
|
||||
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_SQL:
|
||||
session = self.create_session()
|
||||
result = (
|
||||
session.query(self.sql_model)
|
||||
.filter_by(**{match_key: match_value})
|
||||
.one()
|
||||
)
|
||||
db_entry = result.dict()
|
||||
session.close()
|
||||
|
||||
return db_entry
|
||||
|
||||
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) -> dict:
|
||||
if USE_MONGO:
|
||||
new_document = self.document(**document)
|
||||
new_document.save()
|
||||
return BaseDocument._unpack_mongo(new_document)
|
||||
elif USE_SQL:
|
||||
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 update(self, match_value, new_data) -> dict:
|
||||
if USE_MONGO:
|
||||
return_data = self.update_mongo(match_value, new_data)
|
||||
elif USE_SQL:
|
||||
session, entry = self._query_one(match_value=match_value)
|
||||
entry.update(session=session, **new_data)
|
||||
return_data = entry.dict()
|
||||
session.commit()
|
||||
|
||||
session.close()
|
||||
else:
|
||||
raise Exception("No Database Configured")
|
||||
|
||||
return return_data
|
||||
|
||||
def delete(self, primary_key_value) -> dict:
|
||||
if USE_MONGO:
|
||||
document = self.document.objects.get(
|
||||
**{str(self.primary_key): primary_key_value}
|
||||
)
|
||||
|
||||
if document:
|
||||
document.delete()
|
||||
elif USE_SQL:
|
||||
session = create_session()
|
||||
|
||||
result = (
|
||||
session.query(self.sql_model)
|
||||
.filter_by(**{self.primary_key: primary_key_value})
|
||||
.one()
|
||||
)
|
||||
|
||||
session.delete(result)
|
||||
|
||||
session.commit()
|
||||
session.close()
|
||||
63
mealie/db/db_mealplan.py
Normal file
63
mealie/db/db_mealplan.py
Normal file
@@ -0,0 +1,63 @@
|
||||
from typing import List
|
||||
|
||||
from app_config import USE_MONGO, USE_SQL
|
||||
|
||||
from db.db_base import BaseDocument
|
||||
from db.db_setup import USE_MONGO, USE_SQL
|
||||
from db.mongo.meal_models import MealDocument, MealPlanDocument
|
||||
from db.sql.db_session import create_session
|
||||
from db.sql.meal_models import MealPlanModel
|
||||
|
||||
|
||||
class _Meals(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "uid"
|
||||
if USE_SQL:
|
||||
self.sql_model = MealPlanModel
|
||||
self.create_session = create_session
|
||||
|
||||
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_mongo(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_SQL:
|
||||
pass
|
||||
|
||||
def update_mongo(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_SQL:
|
||||
pass
|
||||
68
mealie/db/db_recipes.py
Normal file
68
mealie/db/db_recipes.py
Normal file
@@ -0,0 +1,68 @@
|
||||
from app_config import USE_MONGO, USE_SQL
|
||||
|
||||
from db.db_base import BaseDocument
|
||||
from db.mongo.recipe_models import RecipeDocument
|
||||
from db.sql.db_session import create_session
|
||||
from db.sql.recipe_models import RecipeModel
|
||||
|
||||
|
||||
class _Recipes(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "slug"
|
||||
if USE_SQL:
|
||||
self.sql_model = RecipeModel
|
||||
self.create_session = create_session
|
||||
else:
|
||||
self.document = RecipeDocument
|
||||
|
||||
def save_new_sql(self, recipe_data: dict):
|
||||
session = self.create_session()
|
||||
new_recipe = self.sql_model(**recipe_data)
|
||||
session.add(new_recipe)
|
||||
session.commit()
|
||||
|
||||
return recipe_data
|
||||
|
||||
def update_mongo(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
|
||||
# elif USE_SQL:
|
||||
# session, recipe = self._query_one(match_value=slug)
|
||||
# recipe.update(session=session, **new_data)
|
||||
# recipe_dict = recipe.dict()
|
||||
# session.commit()
|
||||
|
||||
# session.close()
|
||||
|
||||
# return recipe_dict
|
||||
|
||||
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_SQL:
|
||||
pass
|
||||
44
mealie/db/db_settings.py
Normal file
44
mealie/db/db_settings.py
Normal file
@@ -0,0 +1,44 @@
|
||||
from app_config import USE_MONGO, USE_SQL
|
||||
|
||||
from db.db_base import BaseDocument
|
||||
from db.db_setup import USE_MONGO, USE_SQL
|
||||
from db.mongo.settings_models import SiteSettingsDocument, WebhooksDocument
|
||||
from db.sql.db_session import create_session
|
||||
from db.sql.settings_models import SiteSettingsModel
|
||||
|
||||
|
||||
class _Settings(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
|
||||
self.primary_key = "name"
|
||||
|
||||
if USE_SQL:
|
||||
self.sql_model = SiteSettingsModel
|
||||
self.create_session = create_session
|
||||
|
||||
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_SQL:
|
||||
session = create_session()
|
||||
new_settings = self.sql_model(main.get("name"), webhooks)
|
||||
|
||||
session.add(new_settings)
|
||||
session.commit()
|
||||
|
||||
return new_settings.dict()
|
||||
|
||||
def update_mongo(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_SQL:
|
||||
return
|
||||
16
mealie/db/db_setup.py
Normal file
16
mealie/db/db_setup.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from app_config import SQLITE_FILE, USE_MONGO, USE_SQL
|
||||
|
||||
from db.sql.db_session import globa_init as sql_global_init
|
||||
|
||||
sql_exists = True
|
||||
|
||||
if USE_SQL:
|
||||
sql_exists = SQLITE_FILE.is_file()
|
||||
sql_global_init(SQLITE_FILE)
|
||||
|
||||
pass
|
||||
|
||||
elif USE_MONGO:
|
||||
from db.mongo.mongo_setup import global_init as mongo_global_init
|
||||
|
||||
mongo_global_init()
|
||||
56
mealie/db/db_themes.py
Normal file
56
mealie/db/db_themes.py
Normal file
@@ -0,0 +1,56 @@
|
||||
from app_config import USE_MONGO, USE_SQL
|
||||
|
||||
from db.db_base import BaseDocument
|
||||
from db.db_setup import USE_MONGO, USE_SQL
|
||||
from db.mongo.settings_models import SiteThemeDocument, ThemeColorsDocument
|
||||
from db.sql.db_session import create_session
|
||||
from db.sql.theme_models import SiteThemeModel
|
||||
|
||||
|
||||
class _Themes(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "name"
|
||||
if USE_SQL:
|
||||
self.sql_model = SiteThemeModel
|
||||
self.create_session = create_session
|
||||
else:
|
||||
self.document = SiteThemeDocument
|
||||
|
||||
def save_new(self, theme_data: dict) -> None:
|
||||
if USE_MONGO:
|
||||
theme_data["colors"] = ThemeColorsDocument(**theme_data["colors"])
|
||||
|
||||
document = self.document(**theme_data)
|
||||
|
||||
document.save()
|
||||
elif USE_SQL:
|
||||
session = self.create_session()
|
||||
new_theme = self.sql_model(**theme_data)
|
||||
|
||||
session.add(new_theme)
|
||||
session.commit()
|
||||
|
||||
return_data = new_theme.dict()
|
||||
|
||||
session.close()
|
||||
return return_data
|
||||
|
||||
def update(self, data: dict) -> dict:
|
||||
if USE_MONGO:
|
||||
colors = ThemeColorsDocument(**data["colors"])
|
||||
theme_document = self.document.objects.get(name=data.get("name"))
|
||||
|
||||
if theme_document:
|
||||
theme_document.update(set__colors=colors)
|
||||
theme_document.save()
|
||||
else:
|
||||
raise Exception("No database entry was found to update")
|
||||
|
||||
elif USE_SQL:
|
||||
session, theme_model = self._query_one(
|
||||
match_value=data["name"], match_key="name"
|
||||
)
|
||||
|
||||
theme_model.update(**data)
|
||||
session.commit()
|
||||
session.close()
|
||||
@@ -1,5 +1,6 @@
|
||||
import mongoengine
|
||||
from settings import DB_HOST, DB_PASSWORD, DB_PORT, DB_USERNAME, MEALIE_DB_NAME
|
||||
from app_config import DB_HOST, DB_PASSWORD, DB_PORT, DB_USERNAME, MEALIE_DB_NAME
|
||||
from utils.logger import logger
|
||||
|
||||
|
||||
def global_init():
|
||||
@@ -12,3 +13,5 @@ def global_init():
|
||||
password=DB_PASSWORD,
|
||||
authentication_source="admin",
|
||||
)
|
||||
|
||||
logger.info("Mongo Data Initialized")
|
||||
@@ -1,5 +1,4 @@
|
||||
import datetime
|
||||
import uuid
|
||||
|
||||
import mongoengine
|
||||
|
||||
@@ -19,7 +18,7 @@ class RecipeDocument(mongoengine.Document):
|
||||
slug = mongoengine.StringField(required=True, unique=True)
|
||||
categories = mongoengine.ListField(default=[])
|
||||
tags = mongoengine.ListField(default=[])
|
||||
dateAdded = mongoengine.DateTimeField(binary=True, default=datetime.date.today())
|
||||
dateAdded = mongoengine.DateTimeField(binary=True, default=datetime.date.today)
|
||||
notes = mongoengine.ListField(default=[])
|
||||
rating = mongoengine.IntField(required=True, default=0)
|
||||
orgURL = mongoengine.URLField(required=False)
|
||||
6
mealie/db/mongo/user_models.py
Normal file
6
mealie/db/mongo/user_models.py
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
# import mongoengine
|
||||
|
||||
# class User(mongoengine.Document):
|
||||
# username: mongoengine.EmailField()
|
||||
# password: mongoengine.ReferenceField
|
||||
4
mealie/db/sql/_all_models.py
Normal file
4
mealie/db/sql/_all_models.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from db.sql.meal_models import *
|
||||
from db.sql.recipe_models import *
|
||||
from db.sql.settings_models import *
|
||||
from db.sql.theme_models import *
|
||||
29
mealie/db/sql/db_session.py
Normal file
29
mealie/db/sql/db_session.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from pathlib import Path
|
||||
|
||||
import sqlalchemy as sa
|
||||
import sqlalchemy.orm as orm
|
||||
from db.sql.model_base import SqlAlchemyBase
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
__factory = None
|
||||
|
||||
|
||||
def globa_init(db_file: Path):
|
||||
global __factory
|
||||
|
||||
if __factory:
|
||||
return
|
||||
conn_str = "sqlite:///" + str(db_file.absolute())
|
||||
|
||||
engine = sa.create_engine(conn_str, echo=False)
|
||||
|
||||
__factory = orm.sessionmaker(bind=engine)
|
||||
|
||||
import db.sql._all_models
|
||||
|
||||
SqlAlchemyBase.metadata.create_all(engine)
|
||||
|
||||
|
||||
def create_session() -> Session:
|
||||
global __factory
|
||||
return __factory()
|
||||
66
mealie/db/sql/meal_models.py
Normal file
66
mealie/db/sql/meal_models.py
Normal file
@@ -0,0 +1,66 @@
|
||||
import uuid
|
||||
from typing import List
|
||||
|
||||
import sqlalchemy as sa
|
||||
import sqlalchemy.orm as orm
|
||||
from db.sql.model_base import BaseMixins, SqlAlchemyBase
|
||||
|
||||
|
||||
class Meal(SqlAlchemyBase):
|
||||
__tablename__ = "meal"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
parent_id = sa.Column(sa.String, sa.ForeignKey("mealplan.uid"))
|
||||
slug = sa.Column(sa.String)
|
||||
name = sa.Column(sa.String)
|
||||
date = sa.Column(sa.Date)
|
||||
dateText = sa.Column(sa.String)
|
||||
image = sa.Column(sa.String)
|
||||
description = sa.Column(sa.String)
|
||||
|
||||
def __init__(self, slug, name, date, dateText, image, description) -> None:
|
||||
self.slug = slug
|
||||
self.name = name
|
||||
self.date = date
|
||||
self.dateText = dateText
|
||||
self.image = image
|
||||
self.description = description
|
||||
|
||||
def dict(self) -> dict:
|
||||
data = {
|
||||
"slug": self.slug,
|
||||
"name": self.name,
|
||||
"date": self.date,
|
||||
"dateText": self.dateText,
|
||||
"image": self.image,
|
||||
"description": self.description,
|
||||
}
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class MealPlanModel(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "mealplan"
|
||||
uid = sa.Column(sa.Integer, primary_key=True, unique=True) #! Probably Bad?
|
||||
startDate = sa.Column(sa.Date)
|
||||
endDate = sa.Column(sa.Date)
|
||||
meals: List[Meal] = orm.relation(Meal)
|
||||
|
||||
def __init__(self, startDate, endDate, meals, uid=None) -> None:
|
||||
self.startDate = startDate
|
||||
self.endDate = endDate
|
||||
self.meals = [Meal(**meal) for meal in meals]
|
||||
|
||||
def update(self, session, startDate, endDate, meals, uid) -> None:
|
||||
MealPlanModel._sql_remove_list(session, [Meal], uid)
|
||||
|
||||
self.__init__(startDate, endDate, meals)
|
||||
|
||||
def dict(self) -> dict:
|
||||
data = {
|
||||
"uid": self.uid,
|
||||
"startDate": self.startDate,
|
||||
"endDate": self.endDate,
|
||||
"meals": [meal.dict() for meal in self.meals],
|
||||
}
|
||||
|
||||
return data
|
||||
22
mealie/db/sql/model_base.py
Normal file
22
mealie/db/sql/model_base.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from typing import List
|
||||
|
||||
import sqlalchemy.ext.declarative as dec
|
||||
|
||||
SqlAlchemyBase = dec.declarative_base()
|
||||
|
||||
|
||||
class BaseMixins:
|
||||
@staticmethod
|
||||
def _sql_remove_list(session, list_of_tables: list, parent_id):
|
||||
|
||||
for table in list_of_tables:
|
||||
session.query(table).filter_by(parent_id=parent_id).delete()
|
||||
|
||||
@staticmethod
|
||||
def _flatten_dict(list_of_dict: List[dict]):
|
||||
finalMap = {}
|
||||
for d in list_of_dict:
|
||||
|
||||
finalMap.update(d.dict())
|
||||
|
||||
return finalMap
|
||||
244
mealie/db/sql/recipe_models.py
Normal file
244
mealie/db/sql/recipe_models.py
Normal file
@@ -0,0 +1,244 @@
|
||||
import datetime
|
||||
from datetime import date
|
||||
from typing import List
|
||||
|
||||
import sqlalchemy as sa
|
||||
import sqlalchemy.orm as orm
|
||||
from db.sql.model_base import BaseMixins, SqlAlchemyBase
|
||||
from sqlalchemy.ext.orderinglist import ordering_list
|
||||
|
||||
|
||||
class ApiExtras(SqlAlchemyBase):
|
||||
__tablename__ = "api_extras"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
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_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.id"))
|
||||
name = sa.Column(sa.String, index=True)
|
||||
|
||||
def to_str(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class Tag(SqlAlchemyBase):
|
||||
__tablename__ = "tags"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id"))
|
||||
name = sa.Column(sa.String, index=True)
|
||||
|
||||
def to_str(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class Note(SqlAlchemyBase):
|
||||
__tablename__ = "notes"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id"))
|
||||
title = sa.Column(sa.String)
|
||||
text = sa.Column(sa.String)
|
||||
|
||||
def dict(self):
|
||||
return {"title": self.title, "text": self.text}
|
||||
|
||||
|
||||
class RecipeIngredient(SqlAlchemyBase):
|
||||
__tablename__ = "recipes_ingredients"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
position = sa.Column(sa.Integer)
|
||||
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
|
||||
|
||||
|
||||
class RecipeInstruction(SqlAlchemyBase):
|
||||
__tablename__ = "recipe_instructions"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id"))
|
||||
position = sa.Column(sa.Integer)
|
||||
type = sa.Column(sa.String, default="")
|
||||
text = sa.Column(sa.String)
|
||||
|
||||
def dict(self):
|
||||
data = {"@type": self.type, "text": self.text}
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class RecipeModel(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "recipes"
|
||||
# Database Specific
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
|
||||
# General Recipe Properties
|
||||
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",
|
||||
order_by="RecipeIngredient.position",
|
||||
collection_class=ordering_list("position"),
|
||||
)
|
||||
recipeInstructions: List[RecipeInstruction] = orm.relationship(
|
||||
"RecipeInstruction",
|
||||
cascade="all, delete",
|
||||
order_by="RecipeInstruction.position",
|
||||
collection_class=ordering_list("position"),
|
||||
)
|
||||
|
||||
# How to Properties
|
||||
totalTime = sa.Column(sa.String)
|
||||
prepTime = sa.Column(sa.String)
|
||||
performTime = sa.Column(sa.String)
|
||||
|
||||
# Mealie Specific
|
||||
slug = sa.Column(sa.String, index=True, unique=True)
|
||||
categories: List[Category] = orm.relationship(
|
||||
"Category",
|
||||
cascade="all, delete",
|
||||
)
|
||||
tags: List[Tag] = orm.relationship(
|
||||
"Tag",
|
||||
cascade="all, delete",
|
||||
)
|
||||
dateAdded = sa.Column(sa.Date, default=date.today)
|
||||
notes: List[Note] = orm.relationship(
|
||||
"Note",
|
||||
cascade="all, delete",
|
||||
)
|
||||
rating = sa.Column(sa.Integer)
|
||||
orgURL = sa.Column(sa.String)
|
||||
extras: List[ApiExtras] = orm.relationship("ApiExtras", cascade="all, delete")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str = None,
|
||||
description: str = None,
|
||||
image: str = None,
|
||||
recipeYield: str = None,
|
||||
recipeIngredient: List[str] = None,
|
||||
recipeInstructions: List[dict] = None,
|
||||
totalTime: str = None,
|
||||
prepTime: str = None,
|
||||
performTime: str = None,
|
||||
slug: str = None,
|
||||
categories: List[str] = None,
|
||||
tags: List[str] = None,
|
||||
dateAdded: datetime.date = None,
|
||||
notes: List[dict] = None,
|
||||
rating: int = None,
|
||||
orgURL: str = None,
|
||||
extras: dict = None,
|
||||
) -> None:
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.image = image
|
||||
self.recipeYield = recipeYield
|
||||
self.recipeIngredient = [
|
||||
RecipeIngredient(ingredient=ingr) for ingr in recipeIngredient
|
||||
]
|
||||
self.recipeInstructions = [
|
||||
RecipeInstruction(text=instruc.get("text"), type=instruc.get("text"))
|
||||
for instruc in recipeInstructions
|
||||
]
|
||||
self.totalTime = totalTime
|
||||
self.prepTime = prepTime
|
||||
self.performTime = performTime
|
||||
|
||||
# Mealie Specific
|
||||
self.slug = slug
|
||||
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(key=key, value=value) for key, value in extras.items()]
|
||||
|
||||
def update(
|
||||
self,
|
||||
session,
|
||||
name: str = None,
|
||||
description: str = None,
|
||||
image: str = None,
|
||||
recipeYield: str = None,
|
||||
recipeIngredient: List[str] = None,
|
||||
recipeInstructions: List[dict] = None,
|
||||
totalTime: str = None,
|
||||
prepTime: str = None,
|
||||
performTime: str = None,
|
||||
slug: str = None,
|
||||
categories: List[str] = None,
|
||||
tags: List[str] = None,
|
||||
dateAdded: datetime.date = None,
|
||||
notes: List[dict] = None,
|
||||
rating: int = None,
|
||||
orgURL: str = None,
|
||||
extras: dict = None,
|
||||
):
|
||||
"""Updated a database entry by removing nested rows and rebuilds the row through the __init__ functions"""
|
||||
list_of_tables = [RecipeIngredient, RecipeInstruction, Category, Tag, ApiExtras]
|
||||
RecipeModel._sql_remove_list(session, list_of_tables, self.id)
|
||||
|
||||
self.__init__(
|
||||
name=name,
|
||||
description=description,
|
||||
image=image,
|
||||
recipeYield=recipeYield,
|
||||
recipeIngredient=recipeIngredient,
|
||||
recipeInstructions=recipeInstructions,
|
||||
totalTime=totalTime,
|
||||
prepTime=prepTime,
|
||||
performTime=performTime,
|
||||
slug=slug,
|
||||
categories=categories,
|
||||
tags=tags,
|
||||
dateAdded=dateAdded,
|
||||
notes=notes,
|
||||
rating=rating,
|
||||
orgURL=orgURL,
|
||||
extras=extras,
|
||||
)
|
||||
|
||||
def dict(self):
|
||||
data = {
|
||||
"name": self.name,
|
||||
"description": self.description,
|
||||
"image": self.image,
|
||||
"recipeYield": self.recipeYield,
|
||||
"recipeIngredient": [x.to_str() for x in self.recipeIngredient],
|
||||
"recipeInstructions": [x.dict() for x in self.recipeInstructions],
|
||||
"totalTime": self.totalTime,
|
||||
"prepTime": self.prepTime,
|
||||
"performTime": self.performTime,
|
||||
# Mealie
|
||||
"slug": self.slug,
|
||||
"categories": [x.to_str() for x in self.categories],
|
||||
"tags": [x.to_str() for x in self.tags],
|
||||
"dateAdded": self.dateAdded,
|
||||
"notes": [x.dict() for x in self.notes],
|
||||
"rating": self.rating,
|
||||
"orgURL": self.orgURL,
|
||||
"extras": RecipeModel._flatten_dict(self.extras),
|
||||
}
|
||||
|
||||
return data
|
||||
67
mealie/db/sql/settings_models.py
Normal file
67
mealie/db/sql/settings_models.py
Normal file
@@ -0,0 +1,67 @@
|
||||
import sqlalchemy as sa
|
||||
import sqlalchemy.orm as orm
|
||||
from db.sql.model_base import BaseMixins, SqlAlchemyBase
|
||||
|
||||
|
||||
class SiteSettingsModel(SqlAlchemyBase):
|
||||
__tablename__ = "site_settings"
|
||||
name = sa.Column(sa.String, primary_key=True)
|
||||
webhooks = orm.relationship("WebHookModel", uselist=False, cascade="all, delete")
|
||||
|
||||
def __init__(self, name: str = None, webhooks: dict = None) -> None:
|
||||
self.name = name
|
||||
self.webhooks = WebHookModel(**webhooks)
|
||||
|
||||
def update(self, session, name, webhooks: dict) -> dict:
|
||||
self.name = name
|
||||
self.webhooks.update(session=session, **webhooks)
|
||||
return
|
||||
|
||||
def dict(self):
|
||||
data = {"name": self.name, "webhooks": self.webhooks.dict()}
|
||||
return data
|
||||
|
||||
|
||||
class WebHookModel(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "webhook_settings"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
parent_id = sa.Column(sa.String, sa.ForeignKey("site_settings.name"))
|
||||
webhookURLs = orm.relationship(
|
||||
"WebhookURLModel", uselist=True, cascade="all, delete"
|
||||
)
|
||||
webhookTime = sa.Column(sa.String, default="00:00")
|
||||
enabled = sa.Column(sa.Boolean, default=False)
|
||||
|
||||
def __init__(
|
||||
self, webhookURLs: list, webhookTime: str, enabled: bool = False
|
||||
) -> None:
|
||||
|
||||
self.webhookURLs = [WebhookURLModel(url=x) for x in webhookURLs]
|
||||
self.webhookTime = webhookTime
|
||||
self.enabled = enabled
|
||||
|
||||
def update(
|
||||
self, session, webhookURLs: list, webhookTime: str, enabled: bool
|
||||
) -> None:
|
||||
|
||||
self._sql_remove_list(session, [WebhookURLModel], self.id)
|
||||
|
||||
self.__init__(webhookURLs, webhookTime, enabled)
|
||||
|
||||
def dict(self):
|
||||
data = {
|
||||
"webhookURLs": [url.to_str() for url in self.webhookURLs],
|
||||
"webhookTime": self.webhookTime,
|
||||
"enabled": self.enabled,
|
||||
}
|
||||
return data
|
||||
|
||||
|
||||
class WebhookURLModel(SqlAlchemyBase):
|
||||
__tablename__ = "webhook_urls"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
url = sa.Column(sa.String)
|
||||
parent_id = sa.Column(sa.Integer, sa.ForeignKey("webhook_settings.id"))
|
||||
|
||||
def to_str(self):
|
||||
return self.url
|
||||
64
mealie/db/sql/theme_models.py
Normal file
64
mealie/db/sql/theme_models.py
Normal file
@@ -0,0 +1,64 @@
|
||||
import sqlalchemy as sa
|
||||
import sqlalchemy.orm as orm
|
||||
from db.sql.model_base import BaseMixins, SqlAlchemyBase
|
||||
|
||||
|
||||
class SiteThemeModel(SqlAlchemyBase):
|
||||
__tablename__ = "site_theme"
|
||||
name = sa.Column(sa.String, primary_key=True)
|
||||
colors = orm.relationship("ThemeColorsModel", uselist=False, cascade="all, delete")
|
||||
|
||||
def __init__(self, name: str, colors: dict) -> None:
|
||||
self.name = name
|
||||
self.colors = ThemeColorsModel(**colors)
|
||||
|
||||
def update(self, name, colors: dict) -> dict:
|
||||
self.colors.update(**colors)
|
||||
return self.dict()
|
||||
|
||||
def dict(self):
|
||||
data = {"name": self.name, "colors": self.colors.dict()}
|
||||
return data
|
||||
|
||||
|
||||
class ThemeColorsModel(SqlAlchemyBase):
|
||||
__tablename__ = "theme_colors"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
parent_id = sa.Column(sa.String, sa.ForeignKey("site_theme.name"))
|
||||
primary = sa.Column(sa.String)
|
||||
accent = sa.Column(sa.String)
|
||||
secondary = sa.Column(sa.String)
|
||||
success = sa.Column(sa.String)
|
||||
info = sa.Column(sa.String)
|
||||
warning = sa.Column(sa.String)
|
||||
error = sa.Column(sa.String)
|
||||
|
||||
def update(
|
||||
self,
|
||||
primary: str = None,
|
||||
accent: str = None,
|
||||
secondary: str = None,
|
||||
success: str = None,
|
||||
info: str = None,
|
||||
warning: str = None,
|
||||
error: str = None,
|
||||
) -> None:
|
||||
self.primary = primary
|
||||
self.accent = accent
|
||||
self.secondary = secondary
|
||||
self.success = success
|
||||
self.info = info
|
||||
self.warning = warning
|
||||
self.error = error
|
||||
|
||||
def dict(self):
|
||||
data = {
|
||||
"primary": self.primary,
|
||||
"accent": self.accent,
|
||||
"secondary": self.secondary,
|
||||
"success": self.success,
|
||||
"info": self.info,
|
||||
"warning": self.warning,
|
||||
"error": self.error,
|
||||
}
|
||||
return data
|
||||
@@ -1,6 +0,0 @@
|
||||
|
||||
import mongoengine
|
||||
|
||||
class User(mongoengine.Document):
|
||||
username: mongoengine.EmailField()
|
||||
# password: mongoengine.ReferenceField
|
||||
Reference in New Issue
Block a user