refactor(backend): ♻️ Refactor to make SessionLocal a globally accessible object

This commit is contained in:
hay-kot 2021-08-22 16:08:37 -08:00
parent a1aad078da
commit 234db39cc7
33 changed files with 360 additions and 397 deletions

View file

@ -0,0 +1 @@
from .db_access import DatabaseAccessLayer

View file

@ -1,7 +1,7 @@
from typing import Union
from typing import Callable, Union
from mealie.core.root_logger import get_logger
from mealie.db.models.model_base import SqlAlchemyBase
from mealie.db.models._model_base import SqlAlchemyBase
from pydantic import BaseModel
from sqlalchemy import func
from sqlalchemy.orm import load_only
@ -10,16 +10,25 @@ from sqlalchemy.orm.session import Session
logger = get_logger()
class BaseDocument:
def __init__(self) -> None:
self.primary_key: str
self.store: str
self.sql_model: SqlAlchemyBase
self.schema: BaseModel
self.observers: list = None
class BaseAccessModel:
def __init__(self, primary_key, sql_model, schema) -> None:
self.primary_key: str = primary_key
self.sql_model: SqlAlchemyBase = sql_model
self.schema: BaseModel = schema
self.observers: list = []
def subscribe(self, func: Callable) -> None:
self.observers.append(func)
# TODO: Run Observer in Async Background Task
def update_observers(self) -> None:
if self.observers:
for observer in self.observers:
observer()
def get_all(
self, session: Session, limit: int = None, order_by: str = None, start=0, end=9999, override_schema=None
self, session: Session, limit: int = None, order_by: str = None, start=0, override_schema=None
) -> list[dict]:
eff_schema = override_schema or self.schema
@ -130,7 +139,7 @@ class BaseDocument:
session.add(new_document)
session.commit()
if hasattr(self, "update_observers"):
if self.observers:
self.update_observers()
return self.schema.from_orm(new_document)
@ -150,7 +159,7 @@ class BaseDocument:
entry = self._query_one(session=session, match_value=match_value)
entry.update(session=session, **new_data)
if hasattr(self, "update_observers"):
if self.observers:
self.update_observers()
session.commit()
@ -176,7 +185,7 @@ class BaseDocument:
session.delete(result)
session.commit()
if hasattr(self, "update_observers"):
if self.observers:
self.update_observers()
return results_as_model
@ -185,7 +194,7 @@ class BaseDocument:
session.query(self.sql_model).delete()
session.commit()
if hasattr(self, "update_observers"):
if self.observers:
self.update_observers()
def count_all(self, session: Session, match_key=None, match_value=None) -> int:

View file

@ -0,0 +1,86 @@
from logging import getLogger
from mealie.db.data_access_layer.group_access_model import GroupDataAccessModel
from mealie.db.models.event import Event, EventNotification
from mealie.db.models.group import Group
from mealie.db.models.mealplan import MealPlan
from mealie.db.models.recipe.comment import RecipeComment
from mealie.db.models.recipe.ingredient import IngredientFoodModel, IngredientUnitModel
from mealie.db.models.recipe.recipe import Category, RecipeModel, Tag
from mealie.db.models.settings import CustomPage, SiteSettings
from mealie.db.models.shopping_list import ShoppingList
from mealie.db.models.sign_up import SignUp
from mealie.db.models.theme import SiteThemeModel
from mealie.db.models.users import LongLiveToken, User
from mealie.schema.admin import CustomPageOut
from mealie.schema.admin import SiteSettings as SiteSettingsSchema
from mealie.schema.admin import SiteTheme
from mealie.schema.events import Event as EventSchema
from mealie.schema.events import EventNotificationIn
from mealie.schema.meal_plan import MealPlanOut, ShoppingListOut
from mealie.schema.recipe import (
CommentOut,
IngredientFood,
IngredientUnit,
Recipe,
RecipeCategoryResponse,
RecipeTagResponse,
)
from mealie.schema.user import GroupInDB, LongLiveTokenInDB, SignUpOut, UserInDB
from sqlalchemy.orm.session import Session
from ._base_access_model import BaseAccessModel
from .recipe_access_model import RecipeDataAccessModel
from .user_access_model import UserDataAccessModel
logger = getLogger()
DEFAULT_PK = "id"
class CategoryDataAccessModel(BaseAccessModel):
def get_empty(self, session: Session):
self.schema
return session.query(Category).filter(~Category.recipes.any()).all()
class TagsDataAccessModel(BaseAccessModel):
def get_empty(self, session: Session):
return session.query(Tag).filter(~Tag.recipes.any()).all()
class DatabaseAccessLayer:
"""
`DatabaseAccessLayer` class is the data access layer for all database actions within
Mealie. Database uses composition from classes derived from BaseAccessModel. These
can be substantiated from the BaseAccessModel class or through inheritance when
additional methods are required.
"""
def __init__(self) -> None:
# Recipes
self.recipes = RecipeDataAccessModel("slug", RecipeModel, Recipe)
self.ingredient_foods = BaseAccessModel(DEFAULT_PK, IngredientFoodModel, IngredientFood)
self.ingredient_units = BaseAccessModel(DEFAULT_PK, IngredientUnitModel, IngredientUnit)
self.comments = BaseAccessModel(DEFAULT_PK, RecipeComment, CommentOut)
# Tags and Categories
self.categories = CategoryDataAccessModel("slug", Category, RecipeCategoryResponse)
self.tags = TagsDataAccessModel("slug", Tag, RecipeTagResponse)
# Site
self.settings = BaseAccessModel(DEFAULT_PK, SiteSettings, SiteSettingsSchema)
self.themes = BaseAccessModel(DEFAULT_PK, SiteThemeModel, SiteTheme)
self.sign_ups = BaseAccessModel("token", SignUp, SignUpOut)
self.custom_pages = BaseAccessModel(DEFAULT_PK, CustomPage, CustomPageOut)
self.event_notifications = BaseAccessModel(DEFAULT_PK, EventNotification, EventNotificationIn)
self.events = BaseAccessModel(DEFAULT_PK, Event, EventSchema)
# Users / Groups
self.users = UserDataAccessModel(DEFAULT_PK, User, UserInDB)
self.api_tokens = BaseAccessModel(DEFAULT_PK, LongLiveToken, LongLiveTokenInDB)
self.groups = GroupDataAccessModel(DEFAULT_PK, Group, GroupInDB)
self.meals = BaseAccessModel("uid", MealPlan, MealPlanOut)
self.shopping_lists = BaseAccessModel(DEFAULT_PK, ShoppingList, ShoppingListOut)

View file

@ -0,0 +1,22 @@
from mealie.schema.meal_plan.meal import MealPlanOut
from mealie.schema.user.user import GroupInDB
from sqlalchemy.orm.session import Session
from ._base_access_model import BaseAccessModel
class GroupDataAccessModel(BaseAccessModel):
def get_meals(self, session: Session, match_value: str, match_key: str = "name") -> list[MealPlanOut]:
"""A Helper function to get the group from the database and return a sorted list of
Args:
session (Session): SqlAlchemy Session
match_value (str): Match Value
match_key (str, optional): Match Key. Defaults to "name".
Returns:
list[MealPlanOut]: [description]
"""
group: GroupInDB = session.query(self.sql_model).filter_by(**{match_key: match_value}).one_or_none()
return group.mealplans

View file

@ -0,0 +1,57 @@
from random import randint
from mealie.db.models.recipe.recipe import RecipeModel
from mealie.db.models.recipe.settings import RecipeSettings
from sqlalchemy.orm.session import Session
from ._base_access_model import BaseAccessModel
class RecipeDataAccessModel(BaseAccessModel):
def get_all_public(self, session: Session, limit: int = None, order_by: str = None, start=0, override_schema=None):
eff_schema = override_schema or self.schema
if order_by:
order_attr = getattr(self.sql_model, str(order_by))
return [
eff_schema.from_orm(x)
for x in session.query(self.sql_model)
.join(RecipeSettings)
.filter(RecipeSettings.public == True) # noqa: 711
.order_by(order_attr.desc())
.offset(start)
.limit(limit)
.all()
]
return [
eff_schema.from_orm(x)
for x in session.query(self.sql_model)
.join(RecipeSettings)
.filter(RecipeSettings.public == True) # noqa: 711
.offset(start)
.limit(limit)
.all()
]
def update_image(self, session: Session, slug: str, _: str = None) -> str:
entry: RecipeModel = self._query_one(session, match_value=slug)
entry.image = randint(0, 255)
session.commit()
return entry.image
def count_uncategorized(self, session: Session, count=True, override_schema=None) -> int:
return self._count_attribute(
session,
attribute_name=RecipeModel.recipe_category,
attr_match=None,
count=count,
override_schema=override_schema,
)
def count_untagged(self, session: Session, count=True, override_schema=None) -> int:
return self._count_attribute(
session, attribute_name=RecipeModel.tags, attr_match=None, count=count, override_schema=override_schema
)

View file

@ -0,0 +1,10 @@
from ._base_access_model import BaseAccessModel
class UserDataAccessModel(BaseAccessModel):
def update_password(self, session, id, password: str):
entry = self._query_one(session=session, match_value=id)
entry.update_password(password)
session.commit()
return self.schema.from_orm(entry)

View file

@ -1,4 +1,4 @@
from mealie.schema.recipe.recipe import IngredientUnit
from mealie.schema.recipe.units_and_foods import CreateIngredientUnit
from sqlalchemy.orm.session import Session
from ..data_access_layer import DatabaseAccessLayer
@ -7,21 +7,21 @@ from ..data_access_layer import DatabaseAccessLayer
def get_default_units():
return [
# Volume
IngredientUnit(name="teaspoon", abbreviation="tsp"),
IngredientUnit(name="tablespoon", abbreviation="tbsp"),
IngredientUnit(name="fluid ounce", abbreviation="fl oz"),
IngredientUnit(name="cup", abbreviation="cup"),
IngredientUnit(name="pint", abbreviation="pt"),
IngredientUnit(name="quart", abbreviation="qt"),
IngredientUnit(name="gallon", abbreviation="gal"),
IngredientUnit(name="milliliter", abbreviation="ml"),
IngredientUnit(name="liter", abbreviation="l"),
CreateIngredientUnit(name="teaspoon", abbreviation="tsp"),
CreateIngredientUnit(name="tablespoon", abbreviation="tbsp"),
CreateIngredientUnit(name="fluid ounce", abbreviation="fl oz"),
CreateIngredientUnit(name="cup", abbreviation="cup"),
CreateIngredientUnit(name="pint", abbreviation="pt"),
CreateIngredientUnit(name="quart", abbreviation="qt"),
CreateIngredientUnit(name="gallon", abbreviation="gal"),
CreateIngredientUnit(name="milliliter", abbreviation="ml"),
CreateIngredientUnit(name="liter", abbreviation="l"),
# Mass Weight
IngredientUnit(name="pound", abbreviation="lb"),
IngredientUnit(name="ounce", abbreviation="oz"),
IngredientUnit(name="gram", abbreviation="g"),
IngredientUnit(name="kilogram", abbreviation="kg"),
IngredientUnit(name="milligram", abbreviation="mg"),
CreateIngredientUnit(name="pound", abbreviation="lb"),
CreateIngredientUnit(name="ounce", abbreviation="oz"),
CreateIngredientUnit(name="gram", abbreviation="g"),
CreateIngredientUnit(name="kilogram", abbreviation="kg"),
CreateIngredientUnit(name="milligram", abbreviation="mg"),
]

View file

@ -1,268 +1,3 @@
from logging import getLogger
from random import randint
from typing import Callable
from .data_access_layer import DatabaseAccessLayer
from mealie.db.db_base import BaseDocument
from mealie.db.models.event import Event, EventNotification
from mealie.db.models.group import Group
from mealie.db.models.mealplan import MealPlan
from mealie.db.models.recipe.comment import RecipeComment
from mealie.db.models.recipe.ingredient import IngredientFood, IngredientUnit
from mealie.db.models.recipe.recipe import Category, RecipeModel, Tag
from mealie.db.models.recipe.settings import RecipeSettings
from mealie.db.models.settings import CustomPage, SiteSettings
from mealie.db.models.shopping_list import ShoppingList
from mealie.db.models.sign_up import SignUp
from mealie.db.models.theme import SiteThemeModel
from mealie.db.models.users import LongLiveToken, User
from mealie.schema.admin import CustomPageOut
from mealie.schema.admin import SiteSettings as SiteSettingsSchema
from mealie.schema.admin import SiteTheme
from mealie.schema.events import Event as EventSchema
from mealie.schema.events import EventNotificationIn
from mealie.schema.meal_plan import MealPlanOut, ShoppingListOut
from mealie.schema.recipe import (
CommentOut,
Recipe,
RecipeCategoryResponse,
RecipeIngredientFood,
RecipeIngredientUnit,
RecipeTagResponse,
)
from mealie.schema.user import GroupInDB, LongLiveTokenInDB, SignUpOut, UserInDB
from sqlalchemy.orm.session import Session
logger = getLogger()
class _Recipes(BaseDocument):
def __init__(self) -> None:
self.primary_key = "slug"
self.sql_model: RecipeModel = RecipeModel
self.schema: Recipe = Recipe
self.observers = []
def get_all_public(self, session: Session, limit: int = None, order_by: str = None, start=0, override_schema=None):
eff_schema = override_schema or self.schema
if order_by:
order_attr = getattr(self.sql_model, str(order_by))
return [
eff_schema.from_orm(x)
for x in session.query(self.sql_model)
.join(RecipeSettings)
.filter(RecipeSettings.public == True) # noqa: 711
.order_by(order_attr.desc())
.offset(start)
.limit(limit)
.all()
]
return [
eff_schema.from_orm(x)
for x in session.query(self.sql_model)
.join(RecipeSettings)
.filter(RecipeSettings.public == True) # noqa: 711
.offset(start)
.limit(limit)
.all()
]
def update_image(self, session: Session, slug: str, _: str = None) -> str:
entry: RecipeModel = self._query_one(session, match_value=slug)
entry.image = randint(0, 255)
session.commit()
return entry.image
def count_uncategorized(self, session: Session, count=True, override_schema=None) -> int:
return self._count_attribute(
session,
attribute_name=RecipeModel.recipe_category,
attr_match=None,
count=count,
override_schema=override_schema,
)
def count_untagged(self, session: Session, count=True, override_schema=None) -> int:
return self._count_attribute(
session, attribute_name=RecipeModel.tags, attr_match=None, count=count, override_schema=override_schema
)
def subscribe(self, func: Callable) -> None:
self.observers.append(func)
def update_observers(self) -> None:
for observer in self.observers:
observer()
class _IngredientFoods(BaseDocument):
def __init__(self) -> None:
self.primary_key = "id"
self.sql_model = IngredientFood
self.schema = RecipeIngredientFood
class _IngredientUnits(BaseDocument):
def __init__(self) -> None:
self.primary_key = "id"
self.sql_model = IngredientUnit
self.schema = RecipeIngredientUnit
class _Categories(BaseDocument):
def __init__(self) -> None:
self.primary_key = "slug"
self.sql_model = Category
self.schema = RecipeCategoryResponse
def get_empty(self, session: Session):
return session.query(Category).filter(~Category.recipes.any()).all()
class _Tags(BaseDocument):
def __init__(self) -> None:
self.primary_key = "slug"
self.sql_model = Tag
self.schema = RecipeTagResponse
def get_empty(self, session: Session):
return session.query(Tag).filter(~Tag.recipes.any()).all()
class _Meals(BaseDocument):
def __init__(self) -> None:
self.primary_key = "uid"
self.sql_model = MealPlan
self.schema = MealPlanOut
class _Settings(BaseDocument):
def __init__(self) -> None:
self.primary_key = "id"
self.sql_model = SiteSettings
self.schema = SiteSettingsSchema
class _Themes(BaseDocument):
def __init__(self) -> None:
self.primary_key = "id"
self.sql_model = SiteThemeModel
self.schema = SiteTheme
class _Users(BaseDocument):
def __init__(self) -> None:
self.primary_key = "id"
self.sql_model = User
self.schema = UserInDB
def update_password(self, session, id, password: str):
entry = self._query_one(session=session, match_value=id)
entry.update_password(password)
session.commit()
return self.schema.from_orm(entry)
class _Comments(BaseDocument):
def __init__(self) -> None:
self.primary_key = "id"
self.sql_model = RecipeComment
self.schema = CommentOut
class _LongLiveToken(BaseDocument):
def __init__(self) -> None:
self.primary_key = "id"
self.sql_model = LongLiveToken
self.schema = LongLiveTokenInDB
class _Groups(BaseDocument):
def __init__(self) -> None:
self.primary_key = "id"
self.sql_model = Group
self.schema = GroupInDB
def get_meals(self, session: Session, match_value: str, match_key: str = "name") -> list[MealPlanOut]:
"""A Helper function to get the group from the database and return a sorted list of
Args:
session (Session): SqlAlchemy Session
match_value (str): Match Value
match_key (str, optional): Match Key. Defaults to "name".
Returns:
list[MealPlanOut]: [description]
"""
group: GroupInDB = session.query(self.sql_model).filter_by(**{match_key: match_value}).one_or_none()
return group.mealplans
class _ShoppingList(BaseDocument):
def __init__(self) -> None:
self.primary_key = "id"
self.sql_model = ShoppingList
self.schema = ShoppingListOut
class _SignUps(BaseDocument):
def __init__(self) -> None:
self.primary_key = "token"
self.sql_model = SignUp
self.schema = SignUpOut
class _CustomPages(BaseDocument):
def __init__(self) -> None:
self.primary_key = "id"
self.sql_model = CustomPage
self.schema = CustomPageOut
class _Events(BaseDocument):
def __init__(self) -> None:
self.primary_key = "id"
self.sql_model = Event
self.schema = EventSchema
class _EventNotification(BaseDocument):
def __init__(self) -> None:
self.primary_key = "id"
self.sql_model = EventNotification
self.schema = EventNotificationIn
class Database:
def __init__(self) -> None:
# Recipes
self.recipes = _Recipes()
self.ingredient_foods = _IngredientUnits()
self.ingredient_units = _IngredientFoods()
self.categories = _Categories()
self.tags = _Tags()
self.comments = _Comments()
# Site
self.settings = _Settings()
self.themes = _Themes()
self.sign_ups = _SignUps()
self.custom_pages = _CustomPages()
self.event_notifications = _EventNotification()
self.events = _Events()
# Users / Groups
self.users = _Users()
self.api_tokens = _LongLiveToken()
self.groups = _Groups()
self.meals = _Meals()
self.shopping_lists = _ShoppingList()
db = Database()
db = DatabaseAccessLayer()

View file

@ -1,8 +1,22 @@
import sqlalchemy as sa
from mealie.core.config import settings
from mealie.db.models.db_session import sql_global_init
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm.session import Session
SessionLocal = sql_global_init(settings.DB_URL)
def sql_global_init(db_url: str):
connect_args = {}
if "sqlite" in db_url:
connect_args["check_same_thread"] = False
engine = sa.create_engine(db_url, echo=False, connect_args=connect_args)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
return SessionLocal, engine
SessionLocal, engine = sql_global_init(settings.DB_URL)
def create_session() -> Session:

View file

@ -1,8 +1,10 @@
from mealie.core import root_logger
from mealie.core.config import settings
from mealie.core.security import get_password_hash
from mealie.db.data_initialization.init_units_foods import default_recipe_unit_init
from mealie.db.database import db
from mealie.db.db_setup import create_session
from mealie.db.db_setup import create_session, engine
from mealie.db.models._model_base import SqlAlchemyBase
from mealie.schema.admin import SiteSettings, SiteTheme
from mealie.services.events import create_general_event
from sqlalchemy.orm import Session
@ -10,16 +12,26 @@ from sqlalchemy.orm import Session
logger = root_logger.get_logger("init_db")
def init_db(db: Session = None) -> None:
if not db:
db = create_session()
def create_all_models():
import mealie.db.models._all_models # noqa: F401
default_group_init(db)
default_settings_init(db)
default_theme_init(db)
default_user_init(db)
SqlAlchemyBase.metadata.create_all(engine)
db.close()
def init_db(session: Session = None) -> None:
create_all_models()
if not session:
session = create_session()
default_group_init(session)
default_settings_init(session)
default_theme_init(session)
default_user_init(session)
default_recipe_unit_init(db, session)
session.close()
def default_theme_init(session: Session):
@ -67,8 +79,12 @@ def default_user_init(session: Session):
def main():
session = create_session()
init_user = db.users.get(session, "1", "id")
try:
session = create_session()
init_user = db.users.get(session, "1", "id")
except Exception:
init_db()
return
if init_user:
logger.info("Database Exists")
else:

View file

@ -0,0 +1,52 @@
import uuid
from datetime import datetime
from mealie.db.db_setup import SessionLocal
from sqlalchemy import Column, DateTime, Integer
from sqlalchemy.ext.declarative import as_declarative
from sqlalchemy.orm import declarative_base
def get_uuid_as_hex() -> str:
"""
Generate a UUID as a hex string.
:return: UUID as a hex string.
"""
return uuid.uuid4().hex
@as_declarative()
class Base:
id = Column(Integer, primary_key=True)
created_at = Column(DateTime, default=datetime.now())
# @declared_attr
# def __tablename__(cls):
# return cls.__name__.lower()
class BaseMixins:
"""
`self.update` method which directly passing arugments to the `__init__`
`cls.get_ref` method which will return the object from the database or none. Useful for many-to-many relationships.
"""
class Config:
get_attr = "id"
def update(self, *args, **kwarg):
self.__init__(*args, **kwarg)
@classmethod
def get_ref(cls, match_value: str, match_attr: str = None):
match_attr = match_attr = cls.Config.get_attr
if match_value is None:
return None
with SessionLocal() as session:
eff_ref = getattr(cls, match_attr)
return session.query(cls).filter(eff_ref == match_value).one_or_none()
SqlAlchemyBase = declarative_base(cls=Base, constructor=None)

View file

@ -1,19 +0,0 @@
import sqlalchemy as sa
from mealie.db.models.model_base import SqlAlchemyBase
from sqlalchemy.orm import sessionmaker
def sql_global_init(db_url: str):
connect_args = {}
if "sqlite" in db_url:
connect_args["check_same_thread"] = False
engine = sa.create_engine(db_url, echo=False, connect_args=connect_args)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
import mealie.db.models._all_models # noqa: F401
SqlAlchemyBase.metadata.create_all(engine)
return SessionLocal

View file

@ -1,4 +1,4 @@
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
from sqlalchemy import Boolean, Column, DateTime, Integer, String

View file

@ -1,7 +1,7 @@
import sqlalchemy as sa
import sqlalchemy.orm as orm
from mealie.core.config import settings
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
from mealie.db.models.recipe.category import Category, group2categories
from sqlalchemy.orm.session import Session

View file

@ -1,6 +1,6 @@
import sqlalchemy.orm as orm
from mealie.db.models.group import Group
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
from mealie.db.models.recipe.recipe import RecipeModel
from mealie.db.models.shopping_list import ShoppingList
from sqlalchemy import Column, Date, ForeignKey, Integer, String

View file

@ -1,14 +0,0 @@
import sqlalchemy.ext.declarative as dec
from requests import Session
SqlAlchemyBase = dec.declarative_base()
class BaseMixins:
def update(self, *args, **kwarg):
self.__init__(*args, **kwarg)
@classmethod
def get_ref(cls_type, session: Session, match_value: str, match_attr: str = "id"):
eff_ref = getattr(cls_type, match_attr)
return session.query(cls_type).filter(eff_ref == match_value).one_or_none()

View file

@ -1,5 +1,5 @@
import sqlalchemy as sa
from mealie.db.models.model_base import SqlAlchemyBase
from mealie.db.models._model_base import SqlAlchemyBase
class ApiExtras(SqlAlchemyBase):

View file

@ -1,5 +1,5 @@
import sqlalchemy as sa
from mealie.db.models.model_base import SqlAlchemyBase
from mealie.db.models._model_base import SqlAlchemyBase
class RecipeAsset(SqlAlchemyBase):

View file

@ -1,7 +1,7 @@
import sqlalchemy as sa
import sqlalchemy.orm as orm
from mealie.core import root_logger
from mealie.db.models.model_base import SqlAlchemyBase
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
from slugify import slugify
from sqlalchemy.orm import validates
@ -36,29 +36,25 @@ custom_pages2categories = sa.Table(
)
class Category(SqlAlchemyBase):
class Category(SqlAlchemyBase, BaseMixins):
__tablename__ = "categories"
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.String, index=True, nullable=False)
slug = sa.Column(sa.String, index=True, unique=True, nullable=False)
recipes = orm.relationship("RecipeModel", secondary=recipes2categories, back_populates="recipe_category")
class Config:
get_attr = "slug"
@validates("name")
def validate_name(self, key, name):
assert name != ""
return name
def __init__(self, name, session=None) -> None:
def __init__(self, name, **_) -> None:
self.name = name.strip()
self.slug = slugify(name)
def update(self, name, session=None) -> None:
self.__init__(name, session)
@staticmethod
def get_ref(session, slug: str):
return session.query(Category).filter(Category.slug == slug).one()
@staticmethod
def create_if_not_exist(session, name: str = None):
test_slug = slugify(name)

View file

@ -1,7 +1,7 @@
from datetime import datetime
from uuid import uuid4
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
from mealie.db.models.recipe.recipe import RecipeModel
from mealie.db.models.users import User
from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, orm

View file

@ -1,4 +1,4 @@
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
from requests import Session
from sqlalchemy import Column, ForeignKey, Integer, String, Table, orm
@ -44,7 +44,7 @@ class IngredientFoodModel(SqlAlchemyBase, BaseMixins):
pass
class RecipeIngredient(SqlAlchemyBase):
class RecipeIngredient(SqlAlchemyBase, BaseMixins):
__tablename__ = "recipes_ingredients"
id = Column(Integer, primary_key=True)
position = Column(Integer)
@ -63,6 +63,10 @@ class RecipeIngredient(SqlAlchemyBase):
def __init__(self, title: str, note: str, unit: dict, food: dict, quantity: int, session: Session, **_) -> None:
self.title = title
self.note = note
self.unit = IngredientUnitModel.get_ref_or_create(session, unit)
self.food = IngredientFoodModel.get_ref_or_create(session, food)
self.quantity = quantity
if unit:
self.unit = IngredientUnitModel.get_ref(unit.get("id"))
if food:
self.food = IngredientFoodModel.get_ref(unit.get("id"))

View file

@ -1,4 +1,4 @@
from mealie.db.models.model_base import SqlAlchemyBase
from mealie.db.models._model_base import SqlAlchemyBase
from sqlalchemy import Column, ForeignKey, Integer, String

View file

@ -1,5 +1,5 @@
import sqlalchemy as sa
from mealie.db.models.model_base import SqlAlchemyBase
from mealie.db.models._model_base import SqlAlchemyBase
class Note(SqlAlchemyBase):

View file

@ -1,5 +1,5 @@
import sqlalchemy as sa
from mealie.db.models.model_base import SqlAlchemyBase
from mealie.db.models._model_base import SqlAlchemyBase
class Nutrition(SqlAlchemyBase):

View file

@ -3,26 +3,24 @@ from datetime import date
import sqlalchemy as sa
import sqlalchemy.orm as orm
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
from mealie.db.models.recipe.api_extras import ApiExtras
from mealie.db.models.recipe.assets import RecipeAsset
from mealie.db.models.recipe.category import Category, recipes2categories
from mealie.db.models.recipe.ingredient import RecipeIngredient
from mealie.db.models.recipe.instruction import RecipeInstruction
from mealie.db.models.recipe.note import Note
from mealie.db.models.recipe.nutrition import Nutrition
from mealie.db.models.recipe.settings import RecipeSettings
from mealie.db.models.recipe.tag import Tag, recipes2tags
from mealie.db.models.recipe.tool import Tool
from sqlalchemy.ext.orderinglist import ordering_list
from sqlalchemy.orm import validates
from .._model_base import BaseMixins, SqlAlchemyBase
from .api_extras import ApiExtras
from .assets import RecipeAsset
from .category import Category, recipes2categories
from .ingredient import RecipeIngredient
from .instruction import RecipeInstruction
from .note import Note
from .nutrition import Nutrition
from .settings import RecipeSettings
from .tag import Tag, recipes2tags
from .tool import Tool
class RecipeModel(SqlAlchemyBase, BaseMixins):
__tablename__ = "recipes"
# Database Specific
id = sa.Column(sa.Integer, primary_key=True)
# General Recipe Properties
name = sa.Column(sa.String, nullable=False)
description = sa.Column(sa.String)
@ -128,11 +126,11 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
self.perform_time = perform_time
self.cook_time = cook_time
self.recipe_category = [Category.create_if_not_exist(session=session, name=cat) for cat in recipe_category]
self.recipe_category = [x for x in [Category.get_ref(cat) for cat in recipe_category] if x]
# Mealie Specific
self.settings = RecipeSettings(**settings) if settings else RecipeSettings()
self.tags = [Tag.create_if_not_exist(session=session, name=tag) for tag in tags]
self.tags = [x for x in [Tag.get_ref(tag) for tag in tags] if x]
self.slug = slug
self.notes = [Note(**note) for note in notes]
self.rating = rating
@ -142,7 +140,3 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
# Time Stampes
self.date_added = date_added
self.date_updated = datetime.datetime.now()
def update(self, **_):
"""Updated a database entry by removing nested rows and rebuilds the row through the __init__ functions"""
self.__init__(**_)

View file

@ -1,5 +1,5 @@
import sqlalchemy as sa
from mealie.db.models.model_base import SqlAlchemyBase
from mealie.db.models._model_base import SqlAlchemyBase
class RecipeSettings(SqlAlchemyBase):

View file

@ -1,7 +1,7 @@
import sqlalchemy as sa
import sqlalchemy.orm as orm
from mealie.core import root_logger
from mealie.db.models.model_base import SqlAlchemyBase
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
from slugify import slugify
from sqlalchemy.orm import validates
@ -15,13 +15,16 @@ recipes2tags = sa.Table(
)
class Tag(SqlAlchemyBase):
class Tag(SqlAlchemyBase, BaseMixins):
__tablename__ = "tags"
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.String, index=True, nullable=False)
slug = sa.Column(sa.String, index=True, unique=True, nullable=False)
recipes = orm.relationship("RecipeModel", secondary=recipes2tags, back_populates="tags")
class Config:
get_attr = "slug"
@validates("name")
def validate_name(self, key, name):
assert name != ""
@ -31,9 +34,6 @@ class Tag(SqlAlchemyBase):
self.name = name.strip()
self.slug = slugify(self.name)
def update(self, name, session=None) -> None:
self.__init__(name, session)
@staticmethod
def create_if_not_exist(session, name: str = None):
test_slug = slugify(name)

View file

@ -1,5 +1,5 @@
import sqlalchemy as sa
from mealie.db.models.model_base import SqlAlchemyBase
from mealie.db.models._model_base import SqlAlchemyBase
class Tool(SqlAlchemyBase):

View file

@ -1,6 +1,6 @@
import sqlalchemy as sa
import sqlalchemy.orm as orm
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
from mealie.db.models.recipe.category import Category, custom_pages2categories, site_settings2categories
from sqlalchemy.orm import Session

View file

@ -1,6 +1,6 @@
import sqlalchemy.orm as orm
from mealie.db.models.group import Group
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
from requests import Session
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
from sqlalchemy.ext.orderinglist import ordering_list

View file

@ -1,4 +1,4 @@
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
from sqlalchemy import Boolean, Column, Integer, String

View file

@ -1,5 +1,5 @@
import sqlalchemy.orm as orm
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
from sqlalchemy import Column, ForeignKey, Integer, String

View file

@ -1,6 +1,6 @@
from mealie.core.config import settings
from mealie.db.models.group import Group
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
from mealie.db.models.recipe.recipe import RecipeModel
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, orm