feat(backend): add initial cookbook support

This commit is contained in:
hay-kot
2021-08-31 14:39:29 -08:00
parent 83ab858e46
commit d24e95c091
27 changed files with 284 additions and 490 deletions

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View 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)

View File

@@ -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

View File

@@ -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()

View File

@@ -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)