mirror of
				https://github.com/mealie-recipes/mealie.git
				synced 2025-11-03 19:18:22 -05:00 
			
		
		
		
	feat(backend): ✨ add initial cookbook support
This commit is contained in:
		
							parent
							
								
									83ab858e46
								
							
						
					
					
						commit
						d24e95c091
					
				@ -24,7 +24,7 @@ class ReadDeps:
 | 
				
			|||||||
        user=Depends(is_logged_in),
 | 
					        user=Depends(is_logged_in),
 | 
				
			||||||
    ):
 | 
					    ):
 | 
				
			||||||
        self.session: Session = session
 | 
					        self.session: Session = session
 | 
				
			||||||
        self.bg_tasks: BackgroundTasks = background_tasks
 | 
					        self.bg_task: BackgroundTasks = background_tasks
 | 
				
			||||||
        self.user: bool = user
 | 
					        self.user: bool = user
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,5 @@
 | 
				
			|||||||
 | 
					from __future__ import annotations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from typing import Callable, Generic, TypeVar, Union
 | 
					from typing import Callable, Generic, TypeVar, Union
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from sqlalchemy import func
 | 
					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:
 | 
					    def __init__(self, primary_key: Union[str, int], sql_model: D, schema: T) -> None:
 | 
				
			||||||
        self.primary_key = primary_key
 | 
					        self.primary_key = primary_key
 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.sql_model = sql_model
 | 
					        self.sql_model = sql_model
 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.schema = schema
 | 
					        self.schema = schema
 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.observers: list = []
 | 
					        self.observers: list = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def subscribe(self, func: Callable) -> None:
 | 
					    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]
 | 
					        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:
 | 
					    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.
 | 
					        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:
 | 
					        if match_key is None:
 | 
				
			||||||
            match_key = self.primary_key
 | 
					            match_key = self.primary_key
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return session.query(self.sql_model).filter_by(**{match_key: match_value}).one()
 | 
					        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(
 | 
					    def get(
 | 
				
			||||||
        self, session: Session, match_value: str, match_key: str = None, limit=1, any_case=False, override_schema=None
 | 
					        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
 | 
					        """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.
 | 
					        key is provided the class objects primary key will be used to match against.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Args:
 | 
					        Args:
 | 
				
			||||||
            match_value (str): A value used to match against the key/value in the database \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. \n
 | 
					            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. \n
 | 
					            limit (int, optional): A limit to returned responses. Defaults to 1.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Returns:
 | 
					        Returns:
 | 
				
			||||||
            dict or list[dict]:
 | 
					            dict or list[dict]:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        if match_key is None:
 | 
					        match_key = match_key or self.primary_key
 | 
				
			||||||
            match_key = self.primary_key
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if any_case:
 | 
					        if any_case:
 | 
				
			||||||
            search_attr = getattr(self.sql_model, match_key)
 | 
					            search_attr = getattr(self.sql_model, match_key)
 | 
				
			||||||
 | 
				
			|||||||
@ -3,6 +3,7 @@ from logging import getLogger
 | 
				
			|||||||
from sqlalchemy.orm.session import Session
 | 
					from sqlalchemy.orm.session import Session
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from mealie.db.data_access_layer.group_access_model import GroupDataAccessModel
 | 
					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.event import Event, EventNotification
 | 
				
			||||||
from mealie.db.models.group import Group
 | 
					from mealie.db.models.group import Group
 | 
				
			||||||
from mealie.db.models.mealplan import MealPlan
 | 
					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.comment import RecipeComment
 | 
				
			||||||
from mealie.db.models.recipe.ingredient import IngredientFoodModel, IngredientUnitModel
 | 
					from mealie.db.models.recipe.ingredient import IngredientFoodModel, IngredientUnitModel
 | 
				
			||||||
from mealie.db.models.recipe.recipe import RecipeModel, Tag
 | 
					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.shopping_list import ShoppingList
 | 
				
			||||||
from mealie.db.models.sign_up import SignUp
 | 
					from mealie.db.models.sign_up import SignUp
 | 
				
			||||||
from mealie.db.models.users import LongLiveToken, User
 | 
					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 SiteSettings as SiteSettingsSchema
 | 
				
			||||||
 | 
					from mealie.schema.cookbook import ReadCookBook
 | 
				
			||||||
from mealie.schema.events import Event as EventSchema
 | 
					from mealie.schema.events import Event as EventSchema
 | 
				
			||||||
from mealie.schema.events import EventNotificationIn
 | 
					from mealie.schema.events import EventNotificationIn
 | 
				
			||||||
from mealie.schema.meal_plan import MealPlanOut, ShoppingListOut
 | 
					from mealie.schema.meal_plan import MealPlanOut, ShoppingListOut
 | 
				
			||||||
@ -73,7 +74,6 @@ class DatabaseAccessLayer:
 | 
				
			|||||||
        # Site
 | 
					        # Site
 | 
				
			||||||
        self.settings = BaseAccessModel(DEFAULT_PK, SiteSettings, SiteSettingsSchema)
 | 
					        self.settings = BaseAccessModel(DEFAULT_PK, SiteSettings, SiteSettingsSchema)
 | 
				
			||||||
        self.sign_ups = BaseAccessModel("token", SignUp, SignUpOut)
 | 
					        self.sign_ups = BaseAccessModel("token", SignUp, SignUpOut)
 | 
				
			||||||
        self.custom_pages = BaseAccessModel(DEFAULT_PK, CustomPage, CustomPageOut)
 | 
					 | 
				
			||||||
        self.event_notifications = BaseAccessModel(DEFAULT_PK, EventNotification, EventNotificationIn)
 | 
					        self.event_notifications = BaseAccessModel(DEFAULT_PK, EventNotification, EventNotificationIn)
 | 
				
			||||||
        self.events = BaseAccessModel(DEFAULT_PK, Event, EventSchema)
 | 
					        self.events = BaseAccessModel(DEFAULT_PK, Event, EventSchema)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -83,3 +83,4 @@ class DatabaseAccessLayer:
 | 
				
			|||||||
        self.groups = GroupDataAccessModel(DEFAULT_PK, Group, GroupInDB)
 | 
					        self.groups = GroupDataAccessModel(DEFAULT_PK, Group, GroupInDB)
 | 
				
			||||||
        self.meals = BaseAccessModel(DEFAULT_PK, MealPlan, MealPlanOut)
 | 
					        self.meals = BaseAccessModel(DEFAULT_PK, MealPlan, MealPlanOut)
 | 
				
			||||||
        self.shopping_lists = BaseAccessModel(DEFAULT_PK, ShoppingList, ShoppingListOut)
 | 
					        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
 | 
					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 = []
 | 
					    elems_to_create = []
 | 
				
			||||||
    updated_elems = []
 | 
					    updated_elems = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -75,8 +75,17 @@ def auto_init(exclude: Union[set, list] = None):  # sourcery no-metrics
 | 
				
			|||||||
                    relation_cls = relationships[key].mapper.entity
 | 
					                    relation_cls = relationships[key].mapper.entity
 | 
				
			||||||
                    use_list = relationships[key].uselist
 | 
					                    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:
 | 
					                    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)
 | 
					                        setattr(self, key, instances)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    if relation_dir == ONETOMANY.name and not use_list:
 | 
					                    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:
 | 
					                    elif relation_dir == MANYTOONE.name and not use_list:
 | 
				
			||||||
                        if isinstance(val, dict):
 | 
					                        if isinstance(val, dict):
 | 
				
			||||||
                            val = val.get("id")
 | 
					                            val = val.get(get_attr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            if val is None:
 | 
					                            if val is None:
 | 
				
			||||||
                                raise ValueError(f"Expected 'id' to be provided for {key}")
 | 
					                                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}")
 | 
					                            raise ValueError(f"Expected many to many input to be of type list for {key}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        if len(val) > 0 and isinstance(val[0], dict):
 | 
					                        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]
 | 
					                        instances = [x for x in [relation_cls.get_ref(elem, session=session) for elem in val] if x]
 | 
				
			||||||
                        setattr(self, key, instances)
 | 
					                        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.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.cookbook import CookBook
 | 
				
			||||||
from mealie.db.models.recipe.category import Category, group2categories
 | 
					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)
 | 
					    id = sa.Column(sa.Integer, primary_key=True)
 | 
				
			||||||
    name = sa.Column(sa.String, index=True, nullable=False, unique=True)
 | 
					    name = sa.Column(sa.String, index=True, nullable=False, unique=True)
 | 
				
			||||||
    users = orm.relationship("User", back_populates="group")
 | 
					    users = orm.relationship("User", back_populates="group")
 | 
				
			||||||
    mealplans = orm.relationship(
 | 
					    mealplans = orm.relationship("MealPlan", back_populates="group", single_parent=True, order_by="MealPlan.start_date")
 | 
				
			||||||
        "MealPlan",
 | 
					    shopping_lists = orm.relationship("ShoppingList", back_populates="group", single_parent=True)
 | 
				
			||||||
        back_populates="group",
 | 
					    cookbooks = orm.relationship(CookBook, back_populates="group", single_parent=True)
 | 
				
			||||||
        single_parent=True,
 | 
					 | 
				
			||||||
        order_by="MealPlan.start_date",
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    shopping_lists = orm.relationship(
 | 
					 | 
				
			||||||
        "ShoppingList",
 | 
					 | 
				
			||||||
        back_populates="group",
 | 
					 | 
				
			||||||
        single_parent=True,
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    categories = orm.relationship("Category", secondary=group2categories, single_parent=True)
 | 
					    categories = orm.relationship("Category", secondary=group2categories, single_parent=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Webhook Settings
 | 
					    # Webhook Settings
 | 
				
			||||||
 | 
				
			|||||||
@ -29,10 +29,10 @@ recipes2categories = sa.Table(
 | 
				
			|||||||
    sa.Column("category_id", sa.Integer, sa.ForeignKey("categories.id")),
 | 
					    sa.Column("category_id", sa.Integer, sa.ForeignKey("categories.id")),
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
custom_pages2categories = sa.Table(
 | 
					cookbooks_to_categories = sa.Table(
 | 
				
			||||||
    "custom_pages2categories",
 | 
					    "cookbooks_to_categories",
 | 
				
			||||||
    SqlAlchemyBase.metadata,
 | 
					    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")),
 | 
					    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:
 | 
					        if not session or not match_value:
 | 
				
			||||||
            return None
 | 
					            return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        print(match_value)
 | 
				
			||||||
        slug = slugify(match_value)
 | 
					        slug = slugify(match_value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        result = session.query(Category).filter(Category.slug == slug).one_or_none()
 | 
					        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 sqlalchemy.orm import Session
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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 mealie.db.models.recipe.category import Category, site_settings2categories
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SiteSettings(SqlAlchemyBase, BaseMixins):
 | 
					class SiteSettings(SqlAlchemyBase, BaseMixins):
 | 
				
			||||||
@ -33,21 +33,3 @@ class SiteSettings(SqlAlchemyBase, BaseMixins):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def update(self, *args, **kwarg):
 | 
					    def update(self, *args, **kwarg):
 | 
				
			||||||
        self.__init__(*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)
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -1,8 +1,9 @@
 | 
				
			|||||||
from fastapi import APIRouter
 | 
					from fastapi import APIRouter
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from . import crud
 | 
					from . import cookbooks, crud
 | 
				
			||||||
 | 
					
 | 
				
			||||||
router = APIRouter()
 | 
					router = APIRouter()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					router.include_router(cookbooks.user_router)
 | 
				
			||||||
router.include_router(crud.user_router)
 | 
					router.include_router(crud.user_router)
 | 
				
			||||||
router.include_router(crud.admin_router)
 | 
					router.include_router(crud.admin_router)
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										49
									
								
								mealie/routes/groups/cookbooks.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								mealie/routes/groups/cookbooks.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,49 @@
 | 
				
			|||||||
 | 
					from fastapi import Depends
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from mealie.routes.routers import UserAPIRouter
 | 
				
			||||||
 | 
					from mealie.schema.cookbook.cookbook import CreateCookBook, ReadCookBook
 | 
				
			||||||
 | 
					from mealie.services.cookbook import CookbookService
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					user_router = UserAPIRouter(prefix="/groups/cookbooks", tags=["Groups: Cookbooks"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@user_router.get("", response_model=list[ReadCookBook])
 | 
				
			||||||
 | 
					def get_all_cookbook(cb_service: CookbookService = Depends(CookbookService.private)):
 | 
				
			||||||
 | 
					    """ Get cookbook from the Database """
 | 
				
			||||||
 | 
					    # Get Item
 | 
				
			||||||
 | 
					    return cb_service.get_all()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@user_router.post("", response_model=ReadCookBook)
 | 
				
			||||||
 | 
					def create_cookbook(data: CreateCookBook, cb_service: CookbookService = Depends(CookbookService.private)):
 | 
				
			||||||
 | 
					    """ Create cookbook in the Database """
 | 
				
			||||||
 | 
					    # Create Item
 | 
				
			||||||
 | 
					    return cb_service.create_one(data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@user_router.put("", response_model=list[ReadCookBook])
 | 
				
			||||||
 | 
					def update_many(data: list[ReadCookBook], cb_service: CookbookService = Depends(CookbookService.private)):
 | 
				
			||||||
 | 
					    """ Create cookbook in the Database """
 | 
				
			||||||
 | 
					    # Create Item
 | 
				
			||||||
 | 
					    return cb_service.update_many(data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@user_router.get("/{id}", response_model=ReadCookBook)
 | 
				
			||||||
 | 
					def get_cookbook(cb_service: CookbookService = Depends(CookbookService.write_existing)):
 | 
				
			||||||
 | 
					    """ Get cookbook from the Database """
 | 
				
			||||||
 | 
					    # Get Item
 | 
				
			||||||
 | 
					    return cb_service.cookbook
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@user_router.put("/{id}")
 | 
				
			||||||
 | 
					def update_cookbook(data: CreateCookBook, cb_service: CookbookService = Depends(CookbookService.write_existing)):
 | 
				
			||||||
 | 
					    """ Update cookbook in the Database """
 | 
				
			||||||
 | 
					    # Update Item
 | 
				
			||||||
 | 
					    return cb_service.update_one(data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@user_router.delete("/{id}")
 | 
				
			||||||
 | 
					def delete_cookbook(cd_service: CookbookService = Depends(CookbookService.write_existing)):
 | 
				
			||||||
 | 
					    """ Delete cookbook from the Database """
 | 
				
			||||||
 | 
					    # Delete Item
 | 
				
			||||||
 | 
					    return cd_service.delete_one()
 | 
				
			||||||
@ -31,13 +31,13 @@ def get_recipe(recipe_service: RecipeService = Depends(RecipeService.read_existi
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@user_router.post("", status_code=201, response_model=str)
 | 
					@user_router.post("", status_code=201, response_model=str)
 | 
				
			||||||
def create_from_name(data: CreateRecipe, recipe_service: RecipeService = Depends(RecipeService.base)) -> str:
 | 
					def create_from_name(data: CreateRecipe, recipe_service: RecipeService = Depends(RecipeService.private)) -> str:
 | 
				
			||||||
    """ Takes in a JSON string and loads data into the database as a new entry"""
 | 
					    """ Takes in a JSON string and loads data into the database as a new entry"""
 | 
				
			||||||
    return recipe_service.create_recipe(data).slug
 | 
					    return recipe_service.create_recipe(data).slug
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@user_router.post("/create-url", status_code=201, response_model=str)
 | 
					@user_router.post("/create-url", status_code=201, response_model=str)
 | 
				
			||||||
def parse_recipe_url(url: CreateRecipeByURL, recipe_service: RecipeService = Depends(RecipeService.base)):
 | 
					def parse_recipe_url(url: CreateRecipeByURL, recipe_service: RecipeService = Depends(RecipeService.private)):
 | 
				
			||||||
    """ Takes in a URL and attempts to scrape data and load it into the database """
 | 
					    """ Takes in a URL and attempts to scrape data and load it into the database """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    recipe = create_from_url(url.url)
 | 
					    recipe = create_from_url(url.url)
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										1
									
								
								mealie/schema/cookbook/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								mealie/schema/cookbook/__init__.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					from .cookbook import *
 | 
				
			||||||
							
								
								
									
										38
									
								
								mealie/schema/cookbook/cookbook.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								mealie/schema/cookbook/cookbook.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					from fastapi_camelcase import CamelModel
 | 
				
			||||||
 | 
					from pydantic import validator
 | 
				
			||||||
 | 
					from slugify import slugify
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from ..recipe.recipe_category import CategoryBase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CreateCookBook(CamelModel):
 | 
				
			||||||
 | 
					    name: str
 | 
				
			||||||
 | 
					    slug: str = None
 | 
				
			||||||
 | 
					    position: int = 1
 | 
				
			||||||
 | 
					    categories: list[CategoryBase] = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @validator("slug", always=True, pre=True)
 | 
				
			||||||
 | 
					    def validate_slug(slug: str, values):
 | 
				
			||||||
 | 
					        name: str = values["name"]
 | 
				
			||||||
 | 
					        calc_slug: str = slugify(name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if slug != calc_slug:
 | 
				
			||||||
 | 
					            slug = calc_slug
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return slug
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UpdateCookBook(CreateCookBook):
 | 
				
			||||||
 | 
					    id: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SaveCookBook(CreateCookBook):
 | 
				
			||||||
 | 
					    group_id: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ReadCookBook(UpdateCookBook):
 | 
				
			||||||
 | 
					    group_id: int
 | 
				
			||||||
 | 
					    categories: list[CategoryBase] = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class Config:
 | 
				
			||||||
 | 
					        orm_mode = True
 | 
				
			||||||
@ -19,7 +19,6 @@ class CategoryBase(CategoryIn):
 | 
				
			|||||||
        def getter_dict(_cls, name_orm):
 | 
					        def getter_dict(_cls, name_orm):
 | 
				
			||||||
            return {
 | 
					            return {
 | 
				
			||||||
                **GetterDict(name_orm),
 | 
					                **GetterDict(name_orm),
 | 
				
			||||||
                "total_recipes": len(name_orm.recipes),
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +0,0 @@
 | 
				
			|||||||
from .category import *
 | 
					 | 
				
			||||||
from .comments import *
 | 
					 | 
				
			||||||
from .helpers import *
 | 
					 | 
				
			||||||
from .recipe import *
 | 
					 | 
				
			||||||
@ -1,48 +0,0 @@
 | 
				
			|||||||
from typing import List, Optional
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from fastapi_camelcase import CamelModel
 | 
					 | 
				
			||||||
from pydantic.utils import GetterDict
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class CategoryIn(CamelModel):
 | 
					 | 
				
			||||||
    name: str
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class CategoryBase(CategoryIn):
 | 
					 | 
				
			||||||
    id: int
 | 
					 | 
				
			||||||
    slug: str
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    class Config:
 | 
					 | 
				
			||||||
        orm_mode = True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        @classmethod
 | 
					 | 
				
			||||||
        def getter_dict(_cls, name_orm):
 | 
					 | 
				
			||||||
            return {
 | 
					 | 
				
			||||||
                **GetterDict(name_orm),
 | 
					 | 
				
			||||||
                "total_recipes": len(name_orm.recipes),
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class RecipeCategoryResponse(CategoryBase):
 | 
					 | 
				
			||||||
    recipes: Optional[List["Recipe"]]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    class Config:
 | 
					 | 
				
			||||||
        schema_extra = {"example": {"id": 1, "name": "dinner", "recipes": [{}]}}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class TagIn(CategoryIn):
 | 
					 | 
				
			||||||
    pass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class TagBase(CategoryBase):
 | 
					 | 
				
			||||||
    pass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class RecipeTagResponse(RecipeCategoryResponse):
 | 
					 | 
				
			||||||
    pass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from .recipe import Recipe
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
RecipeCategoryResponse.update_forward_refs()
 | 
					 | 
				
			||||||
RecipeTagResponse.update_forward_refs()
 | 
					 | 
				
			||||||
@ -1,44 +0,0 @@
 | 
				
			|||||||
from datetime import datetime
 | 
					 | 
				
			||||||
from typing import Optional
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from fastapi_camelcase import CamelModel
 | 
					 | 
				
			||||||
from pydantic.utils import GetterDict
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class UserBase(CamelModel):
 | 
					 | 
				
			||||||
    id: int
 | 
					 | 
				
			||||||
    username: Optional[str]
 | 
					 | 
				
			||||||
    admin: bool
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    class Config:
 | 
					 | 
				
			||||||
        orm_mode = True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class CommentIn(CamelModel):
 | 
					 | 
				
			||||||
    text: str
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class CommentSaveToDB(CommentIn):
 | 
					 | 
				
			||||||
    recipe_slug: str
 | 
					 | 
				
			||||||
    user: int
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    class Config:
 | 
					 | 
				
			||||||
        orm_mode = True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class CommentOut(CommentIn):
 | 
					 | 
				
			||||||
    id: int
 | 
					 | 
				
			||||||
    uuid: str
 | 
					 | 
				
			||||||
    recipe_slug: str
 | 
					 | 
				
			||||||
    date_added: datetime
 | 
					 | 
				
			||||||
    user: UserBase
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    class Config:
 | 
					 | 
				
			||||||
        orm_mode = True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        @classmethod
 | 
					 | 
				
			||||||
        def getter_dict(_cls, name_orm):
 | 
					 | 
				
			||||||
            return {
 | 
					 | 
				
			||||||
                **GetterDict(name_orm),
 | 
					 | 
				
			||||||
                "recipe_slug": name_orm.recipe.slug,
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
@ -1,5 +0,0 @@
 | 
				
			|||||||
from fastapi_camelcase import CamelModel
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class RecipeSlug(CamelModel):
 | 
					 | 
				
			||||||
    slug: str
 | 
					 | 
				
			||||||
@ -1,243 +0,0 @@
 | 
				
			|||||||
import datetime
 | 
					 | 
				
			||||||
from enum import Enum
 | 
					 | 
				
			||||||
from pathlib import Path
 | 
					 | 
				
			||||||
from typing import Any, Optional
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from fastapi_camelcase import CamelModel
 | 
					 | 
				
			||||||
from pydantic import BaseModel, Field, validator
 | 
					 | 
				
			||||||
from pydantic.utils import GetterDict
 | 
					 | 
				
			||||||
from slugify import slugify
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from mealie.core.config import app_dirs, settings
 | 
					 | 
				
			||||||
from mealie.db.models.recipe.recipe import RecipeModel
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from .comments import CommentOut
 | 
					 | 
				
			||||||
from .units_and_foods import IngredientFood, IngredientUnit
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class CreateRecipe(CamelModel):
 | 
					 | 
				
			||||||
    name: str
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class RecipeImageTypes(str, Enum):
 | 
					 | 
				
			||||||
    original = "original.webp"
 | 
					 | 
				
			||||||
    min = "min-original.webp"
 | 
					 | 
				
			||||||
    tiny = "tiny-original.webp"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class RecipeSettings(CamelModel):
 | 
					 | 
				
			||||||
    public: bool = settings.RECIPE_PUBLIC
 | 
					 | 
				
			||||||
    show_nutrition: bool = settings.RECIPE_SHOW_NUTRITION
 | 
					 | 
				
			||||||
    show_assets: bool = settings.RECIPE_SHOW_ASSETS
 | 
					 | 
				
			||||||
    landscape_view: bool = settings.RECIPE_LANDSCAPE_VIEW
 | 
					 | 
				
			||||||
    disable_comments: bool = settings.RECIPE_DISABLE_COMMENTS
 | 
					 | 
				
			||||||
    disable_amount: bool = settings.RECIPE_DISABLE_AMOUNT
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    class Config:
 | 
					 | 
				
			||||||
        orm_mode = True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class RecipeNote(BaseModel):
 | 
					 | 
				
			||||||
    title: str
 | 
					 | 
				
			||||||
    text: str
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    class Config:
 | 
					 | 
				
			||||||
        orm_mode = True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class RecipeStep(CamelModel):
 | 
					 | 
				
			||||||
    title: Optional[str] = ""
 | 
					 | 
				
			||||||
    text: str
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    class Config:
 | 
					 | 
				
			||||||
        orm_mode = True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class RecipeAsset(CamelModel):
 | 
					 | 
				
			||||||
    name: str
 | 
					 | 
				
			||||||
    icon: str
 | 
					 | 
				
			||||||
    file_name: Optional[str]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    class Config:
 | 
					 | 
				
			||||||
        orm_mode = True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Nutrition(CamelModel):
 | 
					 | 
				
			||||||
    calories: Optional[str]
 | 
					 | 
				
			||||||
    fat_content: Optional[str]
 | 
					 | 
				
			||||||
    protein_content: Optional[str]
 | 
					 | 
				
			||||||
    carbohydrate_content: Optional[str]
 | 
					 | 
				
			||||||
    fiber_content: Optional[str]
 | 
					 | 
				
			||||||
    sodium_content: Optional[str]
 | 
					 | 
				
			||||||
    sugar_content: Optional[str]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    class Config:
 | 
					 | 
				
			||||||
        orm_mode = True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class RecipeIngredient(CamelModel):
 | 
					 | 
				
			||||||
    title: Optional[str]
 | 
					 | 
				
			||||||
    note: Optional[str]
 | 
					 | 
				
			||||||
    unit: Optional[IngredientUnit]
 | 
					 | 
				
			||||||
    food: Optional[IngredientFood]
 | 
					 | 
				
			||||||
    disable_amount: bool = True
 | 
					 | 
				
			||||||
    quantity: int = 1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    class Config:
 | 
					 | 
				
			||||||
        orm_mode = True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class RecipeSummary(CamelModel):
 | 
					 | 
				
			||||||
    id: Optional[int]
 | 
					 | 
				
			||||||
    name: Optional[str]
 | 
					 | 
				
			||||||
    slug: str = ""
 | 
					 | 
				
			||||||
    image: Optional[Any]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    description: Optional[str]
 | 
					 | 
				
			||||||
    recipe_category: Optional[list[str]] = []
 | 
					 | 
				
			||||||
    tags: Optional[list[str]] = []
 | 
					 | 
				
			||||||
    rating: Optional[int]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    date_added: Optional[datetime.date]
 | 
					 | 
				
			||||||
    date_updated: Optional[datetime.datetime]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    class Config:
 | 
					 | 
				
			||||||
        orm_mode = True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        @classmethod
 | 
					 | 
				
			||||||
        def getter_dict(_cls, name_orm: RecipeModel):
 | 
					 | 
				
			||||||
            return {
 | 
					 | 
				
			||||||
                **GetterDict(name_orm),
 | 
					 | 
				
			||||||
                "recipe_category": [x.name for x in name_orm.recipe_category],
 | 
					 | 
				
			||||||
                "tags": [x.name for x in name_orm.tags],
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Recipe(RecipeSummary):
 | 
					 | 
				
			||||||
    recipe_yield: Optional[str]
 | 
					 | 
				
			||||||
    recipe_ingredient: Optional[list[RecipeIngredient]] = []
 | 
					 | 
				
			||||||
    recipe_instructions: Optional[list[RecipeStep]] = []
 | 
					 | 
				
			||||||
    nutrition: Optional[Nutrition]
 | 
					 | 
				
			||||||
    tools: Optional[list[str]] = []
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    total_time: Optional[str] = None
 | 
					 | 
				
			||||||
    prep_time: Optional[str] = None
 | 
					 | 
				
			||||||
    perform_time: Optional[str] = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # Mealie Specific
 | 
					 | 
				
			||||||
    settings: Optional[RecipeSettings] = RecipeSettings()
 | 
					 | 
				
			||||||
    assets: Optional[list[RecipeAsset]] = []
 | 
					 | 
				
			||||||
    notes: Optional[list[RecipeNote]] = []
 | 
					 | 
				
			||||||
    org_url: Optional[str] = Field(None, alias="orgURL")
 | 
					 | 
				
			||||||
    extras: Optional[dict] = {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    comments: Optional[list[CommentOut]] = []
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @staticmethod
 | 
					 | 
				
			||||||
    def directory_from_slug(slug) -> Path:
 | 
					 | 
				
			||||||
        return app_dirs.RECIPE_DATA_DIR.joinpath(slug)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @property
 | 
					 | 
				
			||||||
    def directory(self) -> Path:
 | 
					 | 
				
			||||||
        dir = app_dirs.RECIPE_DATA_DIR.joinpath(self.slug)
 | 
					 | 
				
			||||||
        dir.mkdir(exist_ok=True, parents=True)
 | 
					 | 
				
			||||||
        return dir
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @property
 | 
					 | 
				
			||||||
    def asset_dir(self) -> Path:
 | 
					 | 
				
			||||||
        dir = self.directory.joinpath("assets")
 | 
					 | 
				
			||||||
        dir.mkdir(exist_ok=True, parents=True)
 | 
					 | 
				
			||||||
        return dir
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @property
 | 
					 | 
				
			||||||
    def image_dir(self) -> Path:
 | 
					 | 
				
			||||||
        dir = self.directory.joinpath("images")
 | 
					 | 
				
			||||||
        dir.mkdir(exist_ok=True, parents=True)
 | 
					 | 
				
			||||||
        return dir
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    class Config:
 | 
					 | 
				
			||||||
        orm_mode = True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        @classmethod
 | 
					 | 
				
			||||||
        def getter_dict(_cls, name_orm: RecipeModel):
 | 
					 | 
				
			||||||
            return {
 | 
					 | 
				
			||||||
                **GetterDict(name_orm),
 | 
					 | 
				
			||||||
                # "recipe_ingredient": [x.note for x in name_orm.recipe_ingredient],
 | 
					 | 
				
			||||||
                "recipe_category": [x.name for x in name_orm.recipe_category],
 | 
					 | 
				
			||||||
                "tags": [x.name for x in name_orm.tags],
 | 
					 | 
				
			||||||
                "tools": [x.tool for x in name_orm.tools],
 | 
					 | 
				
			||||||
                "extras": {x.key_name: x.value for x in name_orm.extras},
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        schema_extra = {
 | 
					 | 
				
			||||||
            "example": {
 | 
					 | 
				
			||||||
                "name": "Chicken and Rice With Leeks and Salsa Verde",
 | 
					 | 
				
			||||||
                "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",
 | 
					 | 
				
			||||||
                "recipe_yield": "4 Servings",
 | 
					 | 
				
			||||||
                "recipe_ingredient": [
 | 
					 | 
				
			||||||
                    "1 1/2 lb. skinless, boneless chicken thighs (4-8 depending on size)",
 | 
					 | 
				
			||||||
                    "Kosher salt, freshly ground pepper",
 | 
					 | 
				
			||||||
                    "3 Tbsp. unsalted butter, divided",
 | 
					 | 
				
			||||||
                ],
 | 
					 | 
				
			||||||
                "recipe_instructions": [
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        "text": "Season chicken with salt and pepper.",
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                ],
 | 
					 | 
				
			||||||
                "slug": "chicken-and-rice-with-leeks-and-salsa-verde",
 | 
					 | 
				
			||||||
                "tags": ["favorite", "yummy!"],
 | 
					 | 
				
			||||||
                "recipe_category": ["Dinner", "Pasta"],
 | 
					 | 
				
			||||||
                "notes": [{"title": "Watch Out!", "text": "Prep the day before!"}],
 | 
					 | 
				
			||||||
                "org_url": "https://www.bonappetit.com/recipe/chicken-and-rice-with-leeks-and-salsa-verde",
 | 
					 | 
				
			||||||
                "rating": 3,
 | 
					 | 
				
			||||||
                "extras": {"message": "Don't forget to defrost the chicken!"},
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @validator("slug", always=True, pre=True)
 | 
					 | 
				
			||||||
    def validate_slug(slug: str, values):
 | 
					 | 
				
			||||||
        if not values["name"]:
 | 
					 | 
				
			||||||
            return slug
 | 
					 | 
				
			||||||
        name: str = values["name"]
 | 
					 | 
				
			||||||
        calc_slug: str = slugify(name)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if slug != calc_slug:
 | 
					 | 
				
			||||||
            slug = calc_slug
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return slug
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @validator("recipe_ingredient", always=True, pre=True)
 | 
					 | 
				
			||||||
    def validate_ingredients(recipe_ingredient, values):
 | 
					 | 
				
			||||||
        if not recipe_ingredient or not isinstance(recipe_ingredient, list):
 | 
					 | 
				
			||||||
            return recipe_ingredient
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if all(isinstance(elem, str) for elem in recipe_ingredient):
 | 
					 | 
				
			||||||
            return [RecipeIngredient(note=x) for x in recipe_ingredient]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return recipe_ingredient
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class AllRecipeRequest(BaseModel):
 | 
					 | 
				
			||||||
    properties: list[str]
 | 
					 | 
				
			||||||
    limit: Optional[int]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    class Config:
 | 
					 | 
				
			||||||
        schema_extra = {
 | 
					 | 
				
			||||||
            "example": {
 | 
					 | 
				
			||||||
                "properties": ["name", "slug", "image"],
 | 
					 | 
				
			||||||
                "limit": 100,
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class RecipeURLIn(BaseModel):
 | 
					 | 
				
			||||||
    url: str
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    class Config:
 | 
					 | 
				
			||||||
        schema_extra = {"example": {"url": "https://myfavoriterecipes.com/recipes"}}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class SlugResponse(BaseModel):
 | 
					 | 
				
			||||||
    class Config:
 | 
					 | 
				
			||||||
        schema_extra = {"example": "adult-mac-and-cheese"}
 | 
					 | 
				
			||||||
@ -1,24 +0,0 @@
 | 
				
			|||||||
from fastapi_camelcase import CamelModel
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class CreateIngredientFood(CamelModel):
 | 
					 | 
				
			||||||
    name: str
 | 
					 | 
				
			||||||
    description: str = ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class CreateIngredientUnit(CreateIngredientFood):
 | 
					 | 
				
			||||||
    abbreviation: str = ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class IngredientFood(CreateIngredientFood):
 | 
					 | 
				
			||||||
    id: int
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    class Config:
 | 
					 | 
				
			||||||
        orm_mode = True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class IngredientUnit(CreateIngredientUnit):
 | 
					 | 
				
			||||||
    id: int
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    class Config:
 | 
					 | 
				
			||||||
        orm_mode = True
 | 
					 | 
				
			||||||
@ -48,25 +48,19 @@ class BaseHttpService(Generic[T, D]):
 | 
				
			|||||||
    def assert_existing(self, data: T) -> None:
 | 
					    def assert_existing(self, data: T) -> None:
 | 
				
			||||||
        raise NotImplementedError("`assert_existing` must by implemented by child class")
 | 
					        raise NotImplementedError("`assert_existing` must by implemented by child class")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _create_event(self, title: str, message: str) -> None:
 | 
				
			||||||
 | 
					        if not self.__class__.event_func:
 | 
				
			||||||
 | 
					            raise NotImplementedError("`event_func` must be set by child class")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.background_tasks.add_task(self.__class__.event_func, title, message, self.session)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @classmethod
 | 
					    @classmethod
 | 
				
			||||||
    def read_existing(cls, id: T, deps: ReadDeps = Depends()):
 | 
					    def read_existing(cls, id: T, deps: ReadDeps = Depends()):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Used for dependency injection for routes that require an existing recipe. If the recipe doesn't exist
 | 
					        Used for dependency injection for routes that require an existing recipe. If the recipe doesn't exist
 | 
				
			||||||
        or the user doens't not have the required permissions, the proper HTTP Status code will be raised.
 | 
					        or the user doens't not have the required permissions, the proper HTTP Status code will be raised.
 | 
				
			||||||
 | 
					 | 
				
			||||||
        Args:
 | 
					 | 
				
			||||||
            slug (str): Recipe Slug used to query the database
 | 
					 | 
				
			||||||
            session (Session, optional): The Injected SQLAlchemy Session.
 | 
					 | 
				
			||||||
            user (bool, optional): The injected determination of is_logged_in.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Raises:
 | 
					 | 
				
			||||||
            HTTPException: 404 Not Found
 | 
					 | 
				
			||||||
            HTTPException: 403 Forbidden
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Returns:
 | 
					 | 
				
			||||||
            RecipeService: The Recipe Service class with a populated recipe attribute
 | 
					 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        new_class = cls(deps.session, deps.user, deps.bg_tasks)
 | 
					        new_class = cls(deps.session, deps.user, deps.bg_task)
 | 
				
			||||||
        new_class.assert_existing(id)
 | 
					        new_class.assert_existing(id)
 | 
				
			||||||
        return new_class
 | 
					        return new_class
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -75,35 +69,21 @@ class BaseHttpService(Generic[T, D]):
 | 
				
			|||||||
        """
 | 
					        """
 | 
				
			||||||
        Used for dependency injection for routes that require an existing recipe. The only difference between
 | 
					        Used for dependency injection for routes that require an existing recipe. The only difference between
 | 
				
			||||||
        read_existing and write_existing is that the user is required to be logged in on write_existing method.
 | 
					        read_existing and write_existing is that the user is required to be logged in on write_existing method.
 | 
				
			||||||
 | 
					 | 
				
			||||||
        Args:
 | 
					 | 
				
			||||||
            slug (str): Recipe Slug used to query the database
 | 
					 | 
				
			||||||
            session (Session, optional): The Injected SQLAlchemy Session.
 | 
					 | 
				
			||||||
            user (bool, optional): The injected determination of is_logged_in.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Raises:
 | 
					 | 
				
			||||||
            HTTPException: 404 Not Found
 | 
					 | 
				
			||||||
            HTTPException: 403 Forbidden
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Returns:
 | 
					 | 
				
			||||||
            RecipeService: The Recipe Service class with a populated recipe attribute
 | 
					 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        new_class = cls(deps.session, deps.user, deps.bg_task)
 | 
					        new_class = cls(deps.session, deps.user, deps.bg_task)
 | 
				
			||||||
        new_class.assert_existing(id)
 | 
					        new_class.assert_existing(id)
 | 
				
			||||||
        return new_class
 | 
					        return new_class
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @classmethod
 | 
					    @classmethod
 | 
				
			||||||
    def base(cls, deps: WriteDeps = Depends()):
 | 
					    def public(cls, deps: ReadDeps = Depends()):
 | 
				
			||||||
        """A Base instance to be used as a router dependency
 | 
					        """
 | 
				
			||||||
 | 
					        A Base instance to be used as a router dependency
 | 
				
			||||||
        Raises:
 | 
					 | 
				
			||||||
            HTTPException: 400 Bad Request
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        return cls(deps.session, deps.user, deps.bg_task)
 | 
					        return cls(deps.session, deps.user, deps.bg_task)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _create_event(self, title: str, message: str) -> None:
 | 
					    @classmethod
 | 
				
			||||||
        if not self.__class__.event_func:
 | 
					    def private(cls, deps: WriteDeps = Depends()):
 | 
				
			||||||
            raise NotImplementedError("`event_func` must be set by child class")
 | 
					        """
 | 
				
			||||||
 | 
					        A Base instance to be used as a router dependency
 | 
				
			||||||
        self.background_tasks.add_task(self.__class__.event_func, title, message, self.session)
 | 
					        """
 | 
				
			||||||
 | 
					        return cls(deps.session, deps.user, deps.bg_task)
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										1
									
								
								mealie/services/cookbook/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								mealie/services/cookbook/__init__.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					from .cookbook_service import *
 | 
				
			||||||
							
								
								
									
										85
									
								
								mealie/services/cookbook/cookbook_service.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								mealie/services/cookbook/cookbook_service.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,85 @@
 | 
				
			|||||||
 | 
					from fastapi import HTTPException, status
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from mealie.core.root_logger import get_logger
 | 
				
			||||||
 | 
					from mealie.schema.cookbook.cookbook import CreateCookBook, ReadCookBook, SaveCookBook
 | 
				
			||||||
 | 
					from mealie.services.base_http_service.base_http_service import BaseHttpService
 | 
				
			||||||
 | 
					from mealie.services.events import create_group_event
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logger = get_logger(module=__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CookbookService(BaseHttpService[str, str]):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Class Methods:
 | 
				
			||||||
 | 
					        `read_existing`: Reads an existing recipe from the database.
 | 
				
			||||||
 | 
					        `write_existing`: Updates an existing recipe in the database.
 | 
				
			||||||
 | 
					        `base`: Requires write permissions, but doesn't perform recipe checks
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    event_func = create_group_event
 | 
				
			||||||
 | 
					    cookbook: ReadCookBook  # Required for proper type hints
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _group_id_cache = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def group_id(self):
 | 
				
			||||||
 | 
					        # TODO: Populate Group in Private User Call WARNING: May require significant refactoring
 | 
				
			||||||
 | 
					        if not self._group_id_cache:
 | 
				
			||||||
 | 
					            group = self.db.groups.get(self.session, self.user.group, "name")
 | 
				
			||||||
 | 
					            print(group)
 | 
				
			||||||
 | 
					            self._group_id_cache = group.id
 | 
				
			||||||
 | 
					        return self._group_id_cache
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def assert_existing(self, id: str):
 | 
				
			||||||
 | 
					        self.populate_cookbook(id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not self.cookbook:
 | 
				
			||||||
 | 
					            raise HTTPException(status.HTTP_404_NOT_FOUND)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.cookbook.group_id != self.group_id:
 | 
				
			||||||
 | 
					            raise HTTPException(status.HTTP_403_FORBIDDEN)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def populate_cookbook(self, id):
 | 
				
			||||||
 | 
					        self.cookbook = self.db.cookbooks.get(self.session, id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_all(self) -> list[ReadCookBook]:
 | 
				
			||||||
 | 
					        items = self.db.cookbooks.get(self.session, self.group_id, "group_id", limit=999)
 | 
				
			||||||
 | 
					        items.sort(key=lambda x: x.position)
 | 
				
			||||||
 | 
					        return items
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def create_one(self, data: CreateCookBook) -> ReadCookBook:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            self.cookbook = self.db.cookbooks.create(self.session, SaveCookBook(group_id=self.group_id, **data.dict()))
 | 
				
			||||||
 | 
					        except Exception as ex:
 | 
				
			||||||
 | 
					            raise HTTPException(
 | 
				
			||||||
 | 
					                status.HTTP_400_BAD_REQUEST, detail={"message": "PAGE_CREATION_ERROR", "exception": str(ex)}
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return self.cookbook
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def update_one(self, data: CreateCookBook, id: int = None) -> ReadCookBook:
 | 
				
			||||||
 | 
					        if not self.cookbook:
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        target_id = id or self.cookbook.id
 | 
				
			||||||
 | 
					        self.cookbook = self.db.cookbooks.update(self.session, target_id, data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return self.cookbook
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def update_many(self, data: list[ReadCookBook]) -> list[ReadCookBook]:
 | 
				
			||||||
 | 
					        updated = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for cookbook in data:
 | 
				
			||||||
 | 
					            cb = self.db.cookbooks.update(self.session, cookbook.id, cookbook)
 | 
				
			||||||
 | 
					            updated.append(cb)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return updated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def delete_one(self, id: int = None) -> ReadCookBook:
 | 
				
			||||||
 | 
					        if not self.cookbook:
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        target_id = id or self.cookbook.id
 | 
				
			||||||
 | 
					        self.cookbook = self.db.cookbooks.delete(self.session, target_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return self.cookbook
 | 
				
			||||||
@ -5,7 +5,7 @@ from typing import Union
 | 
				
			|||||||
from fastapi import Depends, HTTPException, status
 | 
					from fastapi import Depends, HTTPException, status
 | 
				
			||||||
from sqlalchemy.exc import IntegrityError
 | 
					from sqlalchemy.exc import IntegrityError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from mealie.core.dependencies.grouped import WriteDeps
 | 
					from mealie.core.dependencies.grouped import ReadDeps, WriteDeps
 | 
				
			||||||
from mealie.core.root_logger import get_logger
 | 
					from mealie.core.root_logger import get_logger
 | 
				
			||||||
from mealie.schema.recipe.recipe import CreateRecipe, Recipe
 | 
					from mealie.schema.recipe.recipe import CreateRecipe, Recipe
 | 
				
			||||||
from mealie.services.base_http_service.base_http_service import BaseHttpService
 | 
					from mealie.services.base_http_service.base_http_service import BaseHttpService
 | 
				
			||||||
@ -21,6 +21,7 @@ class RecipeService(BaseHttpService[str, str]):
 | 
				
			|||||||
        `write_existing`: Updates an existing recipe in the database.
 | 
					        `write_existing`: Updates an existing recipe in the database.
 | 
				
			||||||
        `base`: Requires write permissions, but doesn't perform recipe checks
 | 
					        `base`: Requires write permissions, but doesn't perform recipe checks
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    event_func = create_recipe_event
 | 
					    event_func = create_recipe_event
 | 
				
			||||||
    recipe: Recipe  # Required for proper type hints
 | 
					    recipe: Recipe  # Required for proper type hints
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -29,7 +30,7 @@ class RecipeService(BaseHttpService[str, str]):
 | 
				
			|||||||
        return super().write_existing(slug, deps)
 | 
					        return super().write_existing(slug, deps)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @classmethod
 | 
					    @classmethod
 | 
				
			||||||
    def read_existing(cls, slug: str, deps: WriteDeps = Depends()):
 | 
					    def read_existing(cls, slug: str, deps: ReadDeps = Depends()):
 | 
				
			||||||
        return super().write_existing(slug, deps)
 | 
					        return super().write_existing(slug, deps)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def assert_existing(self, slug: str):
 | 
					    def assert_existing(self, slug: str):
 | 
				
			||||||
 | 
				
			|||||||
@ -55,8 +55,8 @@ def _exec_crf_test(input_text):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def convert_list_to_crf_model(list_of_ingrdeint_text: list[str]):
 | 
					def convert_list_to_crf_model(list_of_ingrdeint_text: list[str]):
 | 
				
			||||||
 | 
					    print(list_of_ingrdeint_text)
 | 
				
			||||||
    crf_output = _exec_crf_test([pre_process_string(x) for x in list_of_ingrdeint_text])
 | 
					    crf_output = _exec_crf_test([pre_process_string(x) for x in list_of_ingrdeint_text])
 | 
				
			||||||
 | 
					 | 
				
			||||||
    crf_models = [CRFIngredient(**ingredient) for ingredient in utils.import_data(crf_output.split("\n"))]
 | 
					    crf_models = [CRFIngredient(**ingredient) for ingredient in utils.import_data(crf_output.split("\n"))]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for model in crf_models:
 | 
					    for model in crf_models:
 | 
				
			||||||
 | 
				
			|||||||
@ -11,6 +11,7 @@ def cleanUnicodeFractions(s):
 | 
				
			|||||||
    """
 | 
					    """
 | 
				
			||||||
    Replace unicode fractions with ascii representation, preceded by a
 | 
					    Replace unicode fractions with ascii representation, preceded by a
 | 
				
			||||||
    space.
 | 
					    space.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    "1\x215e" => "1 7/8"
 | 
					    "1\x215e" => "1 7/8"
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -47,7 +48,7 @@ def unclump(s):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
def normalizeToken(s):
 | 
					def normalizeToken(s):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    TODO: FIX THIS. We used to use the pattern.en package to singularize words, but
 | 
					    ToDo: FIX THIS. We used to use the pattern.en package to singularize words, but
 | 
				
			||||||
    in the name of simple deployments, we took it out. We should fix this at some
 | 
					    in the name of simple deployments, we took it out. We should fix this at some
 | 
				
			||||||
    point.
 | 
					    point.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
@ -133,12 +134,13 @@ def insideParenthesis(token, tokens):
 | 
				
			|||||||
        return True
 | 
					        return True
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        line = " ".join(tokens)
 | 
					        line = " ".join(tokens)
 | 
				
			||||||
        return re.match(r".*\(.*" + re.escape(token) + r".*\).*", line) is not None
 | 
					        return re.match(r".*\(.*" + re.escape(token) + ".*\).*", line) is not None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def displayIngredient(ingredient):
 | 
					def displayIngredient(ingredient):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Format a list of (tag, [tokens]) tuples as an HTML string for display.
 | 
					    Format a list of (tag, [tokens]) tuples as an HTML string for display.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        displayIngredient([("qty", ["1"]), ("name", ["cat", "pie"])])
 | 
					        displayIngredient([("qty", ["1"]), ("name", ["cat", "pie"])])
 | 
				
			||||||
        # => <span class='qty'>1</span> <span class='name'>cat pie</span>
 | 
					        # => <span class='qty'>1</span> <span class='name'>cat pie</span>
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
@ -220,21 +222,7 @@ def import_data(lines):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            # turn B-NAME/123 back into "name"
 | 
					            # turn B-NAME/123 back into "name"
 | 
				
			||||||
            tag, confidence = re.split(r"/", columns[-1], 1)
 | 
					            tag, confidence = re.split(r"/", columns[-1], 1)
 | 
				
			||||||
            tag = re.sub(r"^[BI]\-", "", tag).lower()
 | 
					            tag = re.sub("^[BI]\-", "", tag).lower()
 | 
				
			||||||
 | 
					 | 
				
			||||||
            # TODO: Integrate Confidence into API Response
 | 
					 | 
				
			||||||
            print("Confidence", confidence)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # new token
 | 
					 | 
				
			||||||
            if prevTag != tag or token == "n/a":
 | 
					 | 
				
			||||||
                display[-1].append((tag, [token]))
 | 
					 | 
				
			||||||
                data[-1][tag] = []
 | 
					 | 
				
			||||||
                prevTag = tag
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # continuation
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                display[-1][-1][1].append(token)
 | 
					 | 
				
			||||||
                data[-1][tag].append(token)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # ---- DISPLAY ----
 | 
					            # ---- DISPLAY ----
 | 
				
			||||||
            # build a structure which groups each token by its tag, so we can
 | 
					            # build a structure which groups each token by its tag, so we can
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user