mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-12-27 20:55:12 -05:00
feat(backend): ✨ add initial cookbook support
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Callable, Generic, TypeVar, Union
|
||||
|
||||
from sqlalchemy import func
|
||||
@@ -22,11 +24,8 @@ class BaseAccessModel(Generic[T, D]):
|
||||
|
||||
def __init__(self, primary_key: Union[str, int], sql_model: D, schema: T) -> None:
|
||||
self.primary_key = primary_key
|
||||
|
||||
self.sql_model = sql_model
|
||||
|
||||
self.schema = schema
|
||||
|
||||
self.observers: list = []
|
||||
|
||||
def subscribe(self, func: Callable) -> None:
|
||||
@@ -82,40 +81,52 @@ class BaseAccessModel(Generic[T, D]):
|
||||
return [x.get(self.primary_key) for x in results_as_dict]
|
||||
|
||||
def _query_one(self, session: Session, match_value: str, match_key: str = None) -> D:
|
||||
"""Query the sql database for one item an return the sql alchemy model
|
||||
"""
|
||||
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:
|
||||
session (Session): Database Session object
|
||||
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
|
||||
"""
|
||||
if match_key is None:
|
||||
match_key = self.primary_key
|
||||
|
||||
return session.query(self.sql_model).filter_by(**{match_key: match_value}).one()
|
||||
|
||||
def get_one(self, session: Session, value: str | int, key: str = None, any_case=False, override_schema=None) -> T:
|
||||
key = key or self.primary_key
|
||||
|
||||
if any_case:
|
||||
search_attr = getattr(self.sql_model, key)
|
||||
result = session.query(self.sql_model).filter(func.lower(search_attr) == key.lower()).one_or_none()
|
||||
|
||||
result = session.query(self.sql_model).filter_by(**{key: value}).one_or_none()
|
||||
|
||||
if not result:
|
||||
return
|
||||
|
||||
eff_schema = override_schema or self.schema
|
||||
return eff_schema.from_orm(result)
|
||||
|
||||
def get_many(
|
||||
self, session: Session, value: str, key: str = None, limit=1, any_case=False, override_schema=None
|
||||
) -> list[T]:
|
||||
pass
|
||||
|
||||
def get(
|
||||
self, session: Session, match_value: str, match_key: str = None, limit=1, any_case=False, override_schema=None
|
||||
) -> Union[T, list[T]]:
|
||||
) -> T | list[T]:
|
||||
"""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:
|
||||
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
|
||||
match_value (str): A value used to match against the key/value in the database
|
||||
match_key (str, optional): They key to match the value against. Defaults to None.
|
||||
limit (int, optional): A limit to returned responses. Defaults to 1.
|
||||
|
||||
Returns:
|
||||
dict or list[dict]:
|
||||
|
||||
"""
|
||||
if match_key is None:
|
||||
match_key = self.primary_key
|
||||
match_key = match_key or self.primary_key
|
||||
|
||||
if any_case:
|
||||
search_attr = getattr(self.sql_model, match_key)
|
||||
|
||||
@@ -3,6 +3,7 @@ from logging import getLogger
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from mealie.db.data_access_layer.group_access_model import GroupDataAccessModel
|
||||
from mealie.db.models.cookbook import CookBook
|
||||
from mealie.db.models.event import Event, EventNotification
|
||||
from mealie.db.models.group import Group
|
||||
from mealie.db.models.mealplan import MealPlan
|
||||
@@ -10,12 +11,12 @@ from mealie.db.models.recipe.category import Category
|
||||
from mealie.db.models.recipe.comment import RecipeComment
|
||||
from mealie.db.models.recipe.ingredient import IngredientFoodModel, IngredientUnitModel
|
||||
from mealie.db.models.recipe.recipe import RecipeModel, Tag
|
||||
from mealie.db.models.settings import CustomPage, SiteSettings
|
||||
from mealie.db.models.settings import SiteSettings
|
||||
from mealie.db.models.shopping_list import ShoppingList
|
||||
from mealie.db.models.sign_up import SignUp
|
||||
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.cookbook import ReadCookBook
|
||||
from mealie.schema.events import Event as EventSchema
|
||||
from mealie.schema.events import EventNotificationIn
|
||||
from mealie.schema.meal_plan import MealPlanOut, ShoppingListOut
|
||||
@@ -73,7 +74,6 @@ class DatabaseAccessLayer:
|
||||
# Site
|
||||
self.settings = BaseAccessModel(DEFAULT_PK, SiteSettings, SiteSettingsSchema)
|
||||
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)
|
||||
|
||||
@@ -83,3 +83,4 @@ class DatabaseAccessLayer:
|
||||
self.groups = GroupDataAccessModel(DEFAULT_PK, Group, GroupInDB)
|
||||
self.meals = BaseAccessModel(DEFAULT_PK, MealPlan, MealPlanOut)
|
||||
self.shopping_lists = BaseAccessModel(DEFAULT_PK, ShoppingList, ShoppingListOut)
|
||||
self.cookbooks = BaseAccessModel(DEFAULT_PK, CookBook, ReadCookBook)
|
||||
|
||||
@@ -4,7 +4,7 @@ from typing import Union
|
||||
from sqlalchemy.orm import MANYTOMANY, MANYTOONE, ONETOMANY
|
||||
|
||||
|
||||
def handle_one_to_many_list(relation_cls, all_elements: list[dict]):
|
||||
def handle_one_to_many_list(get_attr, relation_cls, all_elements: list[dict]):
|
||||
elems_to_create = []
|
||||
updated_elems = []
|
||||
|
||||
@@ -75,8 +75,17 @@ def auto_init(exclude: Union[set, list] = None): # sourcery no-metrics
|
||||
relation_cls = relationships[key].mapper.entity
|
||||
use_list = relationships[key].uselist
|
||||
|
||||
try:
|
||||
get_attr = relation_cls.Config.get_attr
|
||||
if get_attr is None:
|
||||
get_attr = "id"
|
||||
except Exception:
|
||||
get_attr = "id"
|
||||
|
||||
print(get_attr)
|
||||
|
||||
if relation_dir == ONETOMANY.name and use_list:
|
||||
instances = handle_one_to_many_list(relation_cls, val)
|
||||
instances = handle_one_to_many_list(get_attr, relation_cls, val)
|
||||
setattr(self, key, instances)
|
||||
|
||||
if relation_dir == ONETOMANY.name and not use_list:
|
||||
@@ -85,7 +94,7 @@ def auto_init(exclude: Union[set, list] = None): # sourcery no-metrics
|
||||
|
||||
elif relation_dir == MANYTOONE.name and not use_list:
|
||||
if isinstance(val, dict):
|
||||
val = val.get("id")
|
||||
val = val.get(get_attr)
|
||||
|
||||
if val is None:
|
||||
raise ValueError(f"Expected 'id' to be provided for {key}")
|
||||
@@ -100,7 +109,7 @@ def auto_init(exclude: Union[set, list] = None): # sourcery no-metrics
|
||||
raise ValueError(f"Expected many to many input to be of type list for {key}")
|
||||
|
||||
if len(val) > 0 and isinstance(val[0], dict):
|
||||
val = [elem.get("id") for elem in val]
|
||||
val = [elem.get(get_attr) for elem in val]
|
||||
|
||||
instances = [x for x in [relation_cls.get_ref(elem, session=session) for elem in val] if x]
|
||||
setattr(self, key, instances)
|
||||
|
||||
24
mealie/db/models/cookbook.py
Normal file
24
mealie/db/models/cookbook.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from sqlalchemy import Column, ForeignKey, Integer, String, orm
|
||||
|
||||
from ._model_base import BaseMixins, SqlAlchemyBase
|
||||
from ._model_utils import auto_init
|
||||
from .recipe.category import Category, cookbooks_to_categories
|
||||
|
||||
|
||||
class CookBook(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "cookbooks"
|
||||
id = Column(Integer, primary_key=True)
|
||||
position = Column(Integer, nullable=False)
|
||||
name = Column(String, nullable=False)
|
||||
slug = Column(String, nullable=False)
|
||||
categories = orm.relationship(Category, secondary=cookbooks_to_categories, single_parent=True)
|
||||
|
||||
group_id = Column(Integer, ForeignKey("groups.id"))
|
||||
group = orm.relationship("Group", back_populates="cookbooks")
|
||||
|
||||
@auto_init()
|
||||
def __init__(self, **_) -> None:
|
||||
pass
|
||||
|
||||
def update(self, *args, **kwarg):
|
||||
self.__init__(*args, **kwarg)
|
||||
@@ -4,6 +4,7 @@ from sqlalchemy.orm.session import Session
|
||||
|
||||
from mealie.core.config import settings
|
||||
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
|
||||
from mealie.db.models.cookbook import CookBook
|
||||
from mealie.db.models.recipe.category import Category, group2categories
|
||||
|
||||
|
||||
@@ -19,19 +20,9 @@ class Group(SqlAlchemyBase, BaseMixins):
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
name = sa.Column(sa.String, index=True, nullable=False, unique=True)
|
||||
users = orm.relationship("User", back_populates="group")
|
||||
mealplans = orm.relationship(
|
||||
"MealPlan",
|
||||
back_populates="group",
|
||||
single_parent=True,
|
||||
order_by="MealPlan.start_date",
|
||||
)
|
||||
|
||||
shopping_lists = orm.relationship(
|
||||
"ShoppingList",
|
||||
back_populates="group",
|
||||
single_parent=True,
|
||||
)
|
||||
|
||||
mealplans = orm.relationship("MealPlan", back_populates="group", single_parent=True, order_by="MealPlan.start_date")
|
||||
shopping_lists = orm.relationship("ShoppingList", back_populates="group", single_parent=True)
|
||||
cookbooks = orm.relationship(CookBook, back_populates="group", single_parent=True)
|
||||
categories = orm.relationship("Category", secondary=group2categories, single_parent=True)
|
||||
|
||||
# Webhook Settings
|
||||
|
||||
@@ -29,10 +29,10 @@ recipes2categories = sa.Table(
|
||||
sa.Column("category_id", sa.Integer, sa.ForeignKey("categories.id")),
|
||||
)
|
||||
|
||||
custom_pages2categories = sa.Table(
|
||||
"custom_pages2categories",
|
||||
cookbooks_to_categories = sa.Table(
|
||||
"cookbooks_to_categories",
|
||||
SqlAlchemyBase.metadata,
|
||||
sa.Column("custom_page_id", sa.Integer, sa.ForeignKey("custom_pages.id")),
|
||||
sa.Column("cookbook_id", sa.Integer, sa.ForeignKey("cookbooks.id")),
|
||||
sa.Column("category_id", sa.Integer, sa.ForeignKey("categories.id")),
|
||||
)
|
||||
|
||||
@@ -61,6 +61,7 @@ class Category(SqlAlchemyBase, BaseMixins):
|
||||
if not session or not match_value:
|
||||
return None
|
||||
|
||||
print(match_value)
|
||||
slug = slugify(match_value)
|
||||
|
||||
result = session.query(Category).filter(Category.slug == slug).one_or_none()
|
||||
|
||||
@@ -3,7 +3,7 @@ import sqlalchemy.orm as orm
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
|
||||
from mealie.db.models.recipe.category import Category, custom_pages2categories, site_settings2categories
|
||||
from mealie.db.models.recipe.category import Category, site_settings2categories
|
||||
|
||||
|
||||
class SiteSettings(SqlAlchemyBase, BaseMixins):
|
||||
@@ -33,21 +33,3 @@ class SiteSettings(SqlAlchemyBase, BaseMixins):
|
||||
|
||||
def update(self, *args, **kwarg):
|
||||
self.__init__(*args, **kwarg)
|
||||
|
||||
|
||||
class CustomPage(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "custom_pages"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
position = sa.Column(sa.Integer, nullable=False)
|
||||
name = sa.Column(sa.String, nullable=False)
|
||||
slug = sa.Column(sa.String, nullable=False)
|
||||
categories = orm.relationship("Category", secondary=custom_pages2categories, single_parent=True)
|
||||
|
||||
def __init__(self, session=None, name=None, slug=None, position=0, categories=[], **_) -> None:
|
||||
self.name = name
|
||||
self.slug = slug
|
||||
self.position = position
|
||||
self.categories = [Category.get_ref(session=session, slug=cat.get("slug")) for cat in categories]
|
||||
|
||||
def update(self, *args, **kwarg):
|
||||
self.__init__(*args, **kwarg)
|
||||
|
||||
Reference in New Issue
Block a user