mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-02-10 18:03:11 -05:00
Refactor/define repository layer (#883)
* move data access layer * rename dal -> repo
This commit is contained in:
@@ -1 +0,0 @@
|
||||
from .access_model_factory import Database
|
||||
@@ -1,307 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Callable, Generic, TypeVar, Union
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import UUID4
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.orm import load_only
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from mealie.core.root_logger import get_logger
|
||||
|
||||
logger = get_logger()
|
||||
|
||||
T = TypeVar("T")
|
||||
D = TypeVar("D")
|
||||
|
||||
|
||||
class AccessModel(Generic[T, D]):
|
||||
"""A Generic BaseAccess Model method to perform common operations on the database
|
||||
|
||||
Args:
|
||||
Generic ([T]): Represents the Pydantic Model
|
||||
Generic ([D]): Represents the SqlAlchemyModel Model
|
||||
"""
|
||||
|
||||
def __init__(self, session: Session, primary_key: Union[str, int], sql_model: D, schema: T) -> None:
|
||||
self.session = session
|
||||
self.primary_key = primary_key
|
||||
self.sql_model = sql_model
|
||||
self.schema = schema
|
||||
self.observers: list = []
|
||||
|
||||
self.limit_by_group = False
|
||||
self.user_id = None
|
||||
|
||||
self.limit_by_user = False
|
||||
self.group_id = None
|
||||
|
||||
def subscribe(self, func: Callable) -> None:
|
||||
self.observers.append(func)
|
||||
|
||||
def by_user(self, user_id: UUID4) -> AccessModel:
|
||||
self.limit_by_user = True
|
||||
self.user_id = user_id
|
||||
return self
|
||||
|
||||
def by_group(self, group_id: UUID) -> AccessModel:
|
||||
self.limit_by_group = True
|
||||
self.group_id = group_id
|
||||
return self
|
||||
|
||||
def _filter_builder(self, **kwargs) -> dict[str, Any]:
|
||||
dct = {}
|
||||
|
||||
if self.limit_by_user:
|
||||
dct["user_id"] = self.user_id
|
||||
|
||||
if self.limit_by_group:
|
||||
dct["group_id"] = self.group_id
|
||||
|
||||
return {**dct, **kwargs}
|
||||
|
||||
# TODO: Run Observer in Async Background Task
|
||||
def update_observers(self) -> None:
|
||||
if self.observers:
|
||||
for observer in self.observers:
|
||||
observer()
|
||||
|
||||
def get_all(self, limit: int = None, order_by: str = None, start=0, override_schema=None) -> list[T]:
|
||||
eff_schema = override_schema or self.schema
|
||||
|
||||
order_attr = None
|
||||
if order_by:
|
||||
order_attr = getattr(self.sql_model, str(order_by))
|
||||
order_attr = order_attr.desc()
|
||||
|
||||
return [
|
||||
eff_schema.from_orm(x)
|
||||
for x in self.session.query(self.sql_model).order_by(order_attr).offset(start).limit(limit).all()
|
||||
]
|
||||
|
||||
return [eff_schema.from_orm(x) for x in self.session.query(self.sql_model).offset(start).limit(limit).all()]
|
||||
|
||||
def multi_query(
|
||||
self,
|
||||
query_by: dict[str, str],
|
||||
start=0,
|
||||
limit: int = None,
|
||||
override_schema=None,
|
||||
order_by: str = None,
|
||||
) -> list[T]:
|
||||
eff_schema = override_schema or self.schema
|
||||
|
||||
order_attr = None
|
||||
if order_by:
|
||||
order_attr = getattr(self.sql_model, str(order_by))
|
||||
order_attr = order_attr.desc()
|
||||
|
||||
return [
|
||||
eff_schema.from_orm(x)
|
||||
for x in self.session.query(self.sql_model)
|
||||
.filter_by(**query_by)
|
||||
.order_by(order_attr)
|
||||
.offset(start)
|
||||
.limit(limit)
|
||||
.all()
|
||||
]
|
||||
|
||||
def get_all_limit_columns(self, fields: list[str], limit: int = None) -> list[D]:
|
||||
"""Queries the database for the selected model. Restricts return responses to the
|
||||
keys specified under "fields"
|
||||
|
||||
Args:
|
||||
session (Session): Database Session Object
|
||||
fields (list[str]): list of column names to query
|
||||
limit (int): A limit of values to return
|
||||
|
||||
Returns:
|
||||
list[SqlAlchemyBase]: Returns a list of ORM objects
|
||||
"""
|
||||
return self.session.query(self.sql_model).options(load_only(*fields)).limit(limit).all()
|
||||
|
||||
def get_all_primary_keys(self) -> list[str]:
|
||||
"""Queries the database of the selected model and returns a list
|
||||
of all primary_key values
|
||||
|
||||
Args:
|
||||
session (Session): Database Session object
|
||||
|
||||
Returns:
|
||||
list[str]:
|
||||
"""
|
||||
results = self.session.query(self.sql_model).options(load_only(str(self.primary_key)))
|
||||
results_as_dict = [x.dict() for x in results]
|
||||
return [x.get(self.primary_key) for x in results_as_dict]
|
||||
|
||||
def _query_one(self, match_value: str, match_key: str = None) -> D:
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
if match_key is None:
|
||||
match_key = self.primary_key
|
||||
|
||||
filter = self._filter_builder(**{match_key: match_value})
|
||||
return self.session.query(self.sql_model).filter_by(**filter).one()
|
||||
|
||||
def get_one(self, value: str | int, key: str = None, any_case=False, override_schema=None) -> T:
|
||||
key = key or self.primary_key
|
||||
|
||||
q = self.session.query(self.sql_model)
|
||||
|
||||
if any_case:
|
||||
search_attr = getattr(self.sql_model, key)
|
||||
q = q.filter(func.lower(search_attr) == key.lower()).filter_by(**self._filter_builder())
|
||||
else:
|
||||
q = self.session.query(self.sql_model).filter_by(**self._filter_builder(**{key: value}))
|
||||
|
||||
result = q.one_or_none()
|
||||
|
||||
if not result:
|
||||
return
|
||||
|
||||
eff_schema = override_schema or self.schema
|
||||
return eff_schema.from_orm(result)
|
||||
|
||||
def get(
|
||||
self, match_value: str, match_key: str = None, limit=1, any_case=False, override_schema=None
|
||||
) -> 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
|
||||
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]:
|
||||
|
||||
"""
|
||||
match_key = match_key or self.primary_key
|
||||
|
||||
if any_case:
|
||||
search_attr = getattr(self.sql_model, match_key)
|
||||
result = (
|
||||
self.session.query(self.sql_model)
|
||||
.filter(func.lower(search_attr) == match_value.lower())
|
||||
.limit(limit)
|
||||
.all()
|
||||
)
|
||||
else:
|
||||
result = self.session.query(self.sql_model).filter_by(**{match_key: match_value}).limit(limit).all()
|
||||
|
||||
eff_schema = override_schema or self.schema
|
||||
|
||||
if limit == 1:
|
||||
try:
|
||||
return eff_schema.from_orm(result[0])
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
return [eff_schema.from_orm(x) for x in result]
|
||||
|
||||
def create(self, document: T) -> T:
|
||||
"""Creates a new database entry for the given SQL Alchemy Model.
|
||||
|
||||
Args:
|
||||
session (Session): A Database Session
|
||||
document (dict): A python dictionary representing the data structure
|
||||
|
||||
Returns:
|
||||
dict: A dictionary representation of the database entry
|
||||
"""
|
||||
document = document if isinstance(document, dict) else document.dict()
|
||||
new_document = self.sql_model(session=self.session, **document)
|
||||
self.session.add(new_document)
|
||||
self.session.commit()
|
||||
self.session.refresh(new_document)
|
||||
|
||||
if self.observers:
|
||||
self.update_observers()
|
||||
|
||||
return self.schema.from_orm(new_document)
|
||||
|
||||
def update(self, match_value: str, new_data: dict) -> T:
|
||||
"""Update a database entry.
|
||||
Args:
|
||||
session (Session): Database Session
|
||||
match_value (str): Match "key"
|
||||
new_data (str): Match "value"
|
||||
|
||||
Returns:
|
||||
dict: Returns a dictionary representation of the database entry
|
||||
"""
|
||||
new_data = new_data if isinstance(new_data, dict) else new_data.dict()
|
||||
|
||||
entry = self._query_one(match_value=match_value)
|
||||
entry.update(session=self.session, **new_data)
|
||||
|
||||
if self.observers:
|
||||
self.update_observers()
|
||||
|
||||
self.session.commit()
|
||||
return self.schema.from_orm(entry)
|
||||
|
||||
def patch(self, match_value: str, new_data: dict) -> T:
|
||||
new_data = new_data if isinstance(new_data, dict) else new_data.dict()
|
||||
|
||||
entry = self._query_one(match_value=match_value)
|
||||
|
||||
if not entry:
|
||||
return
|
||||
|
||||
entry_as_dict = self.schema.from_orm(entry).dict()
|
||||
entry_as_dict.update(new_data)
|
||||
|
||||
return self.update(match_value, entry_as_dict)
|
||||
|
||||
def delete(self, primary_key_value) -> D:
|
||||
result = self.session.query(self.sql_model).filter_by(**{self.primary_key: primary_key_value}).one()
|
||||
results_as_model = self.schema.from_orm(result)
|
||||
|
||||
try:
|
||||
self.session.delete(result)
|
||||
self.session.commit()
|
||||
except Exception as e:
|
||||
self.session.rollback()
|
||||
raise e
|
||||
|
||||
if self.observers:
|
||||
self.update_observers()
|
||||
|
||||
return results_as_model
|
||||
|
||||
def delete_all(self) -> None:
|
||||
self.session.query(self.sql_model).delete()
|
||||
self.session.commit()
|
||||
|
||||
if self.observers:
|
||||
self.update_observers()
|
||||
|
||||
def count_all(self, match_key=None, match_value=None) -> int:
|
||||
if None in [match_key, match_value]:
|
||||
return self.session.query(self.sql_model).count()
|
||||
else:
|
||||
return self.session.query(self.sql_model).filter_by(**{match_key: match_value}).count()
|
||||
|
||||
def _count_attribute(
|
||||
self,
|
||||
attribute_name: str,
|
||||
attr_match: str = None,
|
||||
count=True,
|
||||
override_schema=None,
|
||||
) -> Union[int, T]:
|
||||
eff_schema = override_schema or self.schema
|
||||
# attr_filter = getattr(self.sql_model, attribute_name)
|
||||
|
||||
if count:
|
||||
return self.session.query(self.sql_model).filter(attribute_name == attr_match).count() # noqa: 711
|
||||
else:
|
||||
return [
|
||||
eff_schema.from_orm(x)
|
||||
for x in self.session.query(self.sql_model).filter(attribute_name == attr_match).all() # noqa: 711
|
||||
]
|
||||
@@ -1,178 +0,0 @@
|
||||
from functools import cached_property
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from mealie.db.models.event import Event, EventNotification
|
||||
from mealie.db.models.group import Group, GroupMealPlan, ReportEntryModel, ReportModel
|
||||
from mealie.db.models.group.cookbook import CookBook
|
||||
from mealie.db.models.group.exports import GroupDataExportsModel
|
||||
from mealie.db.models.group.invite_tokens import GroupInviteToken
|
||||
from mealie.db.models.group.preferences import GroupPreferencesModel
|
||||
from mealie.db.models.group.webhooks import GroupWebhooksModel
|
||||
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
|
||||
from mealie.db.models.recipe.shared import RecipeShareTokenModel
|
||||
from mealie.db.models.recipe.tag import Tag
|
||||
from mealie.db.models.recipe.tool import Tool
|
||||
from mealie.db.models.server.task import ServerTaskModel
|
||||
from mealie.db.models.sign_up import SignUp
|
||||
from mealie.db.models.users import LongLiveToken, User
|
||||
from mealie.db.models.users.password_reset import PasswordResetModel
|
||||
from mealie.schema.cookbook.cookbook import ReadCookBook
|
||||
from mealie.schema.events import Event as EventSchema
|
||||
from mealie.schema.events import EventNotificationIn
|
||||
from mealie.schema.group.group_exports import GroupDataExport
|
||||
from mealie.schema.group.group_preferences import ReadGroupPreferences
|
||||
from mealie.schema.group.invite_token import ReadInviteToken
|
||||
from mealie.schema.group.webhook import ReadWebhook
|
||||
from mealie.schema.meal_plan.new_meal import ReadPlanEntry
|
||||
from mealie.schema.recipe import Recipe, RecipeCategoryResponse, RecipeCommentOut, RecipeTagResponse, RecipeTool
|
||||
from mealie.schema.recipe.recipe_ingredient import IngredientFood, IngredientUnit
|
||||
from mealie.schema.recipe.recipe_share_token import RecipeShareToken
|
||||
from mealie.schema.reports.reports import ReportEntryOut, ReportOut
|
||||
from mealie.schema.server import ServerTask
|
||||
from mealie.schema.user import GroupInDB, LongLiveTokenInDB, PrivateUser, SignUpOut
|
||||
from mealie.schema.user.user_passwords import PrivatePasswordResetToken
|
||||
|
||||
from ._access_model import AccessModel
|
||||
from .group_access_model import GroupDataAccessModel
|
||||
from .meal_access_model import MealDataAccessModel
|
||||
from .recipe_access_model import RecipeDataAccessModel
|
||||
from .user_access_model import UserDataAccessModel
|
||||
|
||||
pk_id = "id"
|
||||
pk_slug = "slug"
|
||||
pk_token = "token"
|
||||
pk_group_id = "group_id"
|
||||
|
||||
|
||||
class CategoryDataAccessModel(AccessModel):
|
||||
def get_empty(self):
|
||||
return self.session.query(Category).filter(~Category.recipes.any()).all()
|
||||
|
||||
|
||||
class TagsDataAccessModel(AccessModel):
|
||||
def get_empty(self):
|
||||
return self.session.query(Tag).filter(~Tag.recipes.any()).all()
|
||||
|
||||
|
||||
class Database:
|
||||
def __init__(self, session: Session) -> None:
|
||||
"""
|
||||
`DatabaseAccessLayer` class is the data access layer for all database actions within
|
||||
Mealie. Database uses composition from classes derived from AccessModel. These
|
||||
can be substantiated from the AccessModel class or through inheritance when
|
||||
additional methods are required.
|
||||
"""
|
||||
|
||||
self.session = session
|
||||
|
||||
# ================================================================
|
||||
# Recipe Items
|
||||
|
||||
@cached_property
|
||||
def recipes(self) -> RecipeDataAccessModel:
|
||||
return RecipeDataAccessModel(self.session, pk_slug, RecipeModel, Recipe)
|
||||
|
||||
@cached_property
|
||||
def ingredient_foods(self) -> AccessModel[IngredientFood, IngredientFoodModel]:
|
||||
return AccessModel(self.session, pk_id, IngredientFoodModel, IngredientFood)
|
||||
|
||||
@cached_property
|
||||
def ingredient_units(self) -> AccessModel[IngredientUnit, IngredientUnitModel]:
|
||||
return AccessModel(self.session, pk_id, IngredientUnitModel, IngredientUnit)
|
||||
|
||||
@cached_property
|
||||
def tools(self) -> AccessModel[RecipeTool, Tool]:
|
||||
return AccessModel(self.session, pk_id, Tool, RecipeTool)
|
||||
|
||||
@cached_property
|
||||
def comments(self) -> AccessModel[RecipeCommentOut, RecipeComment]:
|
||||
return AccessModel(self.session, pk_id, RecipeComment, RecipeCommentOut)
|
||||
|
||||
@cached_property
|
||||
def categories(self) -> CategoryDataAccessModel:
|
||||
return CategoryDataAccessModel(self.session, pk_slug, Category, RecipeCategoryResponse)
|
||||
|
||||
@cached_property
|
||||
def tags(self) -> TagsDataAccessModel:
|
||||
return TagsDataAccessModel(self.session, pk_slug, Tag, RecipeTagResponse)
|
||||
|
||||
@cached_property
|
||||
def recipe_share_tokens(self) -> AccessModel[RecipeShareToken, RecipeShareTokenModel]:
|
||||
return AccessModel(self.session, pk_id, RecipeShareTokenModel, RecipeShareToken)
|
||||
|
||||
# ================================================================
|
||||
# Site Items
|
||||
|
||||
@cached_property
|
||||
def sign_up(self) -> AccessModel[SignUpOut, SignUp]:
|
||||
return AccessModel(self.session, pk_id, SignUp, SignUpOut)
|
||||
|
||||
@cached_property
|
||||
def event_notifications(self) -> AccessModel[EventNotificationIn, EventNotification]:
|
||||
return AccessModel(self.session, pk_id, EventNotification, EventNotificationIn)
|
||||
|
||||
@cached_property
|
||||
def events(self) -> AccessModel[EventSchema, Event]:
|
||||
return AccessModel(self.session, pk_id, Event, EventSchema)
|
||||
|
||||
# ================================================================
|
||||
# User Items
|
||||
|
||||
@cached_property
|
||||
def users(self) -> UserDataAccessModel:
|
||||
return UserDataAccessModel(self.session, pk_id, User, PrivateUser)
|
||||
|
||||
@cached_property
|
||||
def api_tokens(self) -> AccessModel[LongLiveTokenInDB, LongLiveToken]:
|
||||
return AccessModel(self.session, pk_id, LongLiveToken, LongLiveTokenInDB)
|
||||
|
||||
@cached_property
|
||||
def tokens_pw_reset(self) -> AccessModel[PrivatePasswordResetToken, PasswordResetModel]:
|
||||
return AccessModel(self.session, pk_token, PasswordResetModel, PrivatePasswordResetToken)
|
||||
|
||||
# ================================================================
|
||||
# Group Items
|
||||
|
||||
@cached_property
|
||||
def server_tasks(self) -> AccessModel[ServerTask, ServerTaskModel]:
|
||||
return AccessModel(self.session, pk_id, ServerTaskModel, ServerTask)
|
||||
|
||||
@cached_property
|
||||
def groups(self) -> GroupDataAccessModel:
|
||||
return GroupDataAccessModel(self.session, pk_id, Group, GroupInDB)
|
||||
|
||||
@cached_property
|
||||
def group_invite_tokens(self) -> AccessModel[ReadInviteToken, GroupInviteToken]:
|
||||
return AccessModel(self.session, pk_token, GroupInviteToken, ReadInviteToken)
|
||||
|
||||
@cached_property
|
||||
def group_preferences(self) -> AccessModel[ReadGroupPreferences, GroupPreferencesModel]:
|
||||
return AccessModel(self.session, pk_group_id, GroupPreferencesModel, ReadGroupPreferences)
|
||||
|
||||
@cached_property
|
||||
def group_exports(self) -> AccessModel[GroupDataExport, GroupDataExportsModel]:
|
||||
return AccessModel(self.session, pk_id, GroupDataExportsModel, GroupDataExport)
|
||||
|
||||
@cached_property
|
||||
def meals(self) -> MealDataAccessModel:
|
||||
return MealDataAccessModel(self.session, pk_id, GroupMealPlan, ReadPlanEntry)
|
||||
|
||||
@cached_property
|
||||
def cookbooks(self) -> AccessModel[ReadCookBook, CookBook]:
|
||||
return AccessModel(self.session, pk_id, CookBook, ReadCookBook)
|
||||
|
||||
@cached_property
|
||||
def webhooks(self) -> AccessModel[ReadWebhook, GroupWebhooksModel]:
|
||||
return AccessModel(self.session, pk_id, GroupWebhooksModel, ReadWebhook)
|
||||
|
||||
@cached_property
|
||||
def group_reports(self) -> AccessModel[ReportOut, ReportModel]:
|
||||
return AccessModel(self.session, pk_id, ReportModel, ReportOut)
|
||||
|
||||
@cached_property
|
||||
def group_report_entries(self) -> AccessModel[ReportEntryOut, ReportEntryModel]:
|
||||
return AccessModel(self.session, pk_id, ReportEntryModel, ReportEntryOut)
|
||||
@@ -1,24 +0,0 @@
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from mealie.db.models.group import Group
|
||||
from mealie.schema.meal_plan.meal import MealPlanOut
|
||||
from mealie.schema.user.user import GroupInDB
|
||||
|
||||
from ._access_model import AccessModel
|
||||
|
||||
|
||||
class GroupDataAccessModel(AccessModel[GroupInDB, Group]):
|
||||
def get_meals(self, session: Session, match_value: str, match_key: str = "name") -> list[MealPlanOut]:
|
||||
"""A Helper function to get the group from the database and return a sorted list of
|
||||
|
||||
Args:
|
||||
session (Session): SqlAlchemy Session
|
||||
match_value (str): Match Value
|
||||
match_key (str, optional): Match Key. Defaults to "name".
|
||||
|
||||
Returns:
|
||||
list[MealPlanOut]: [description]
|
||||
"""
|
||||
group: GroupInDB = session.query(self.sql_model).filter_by(**{match_key: match_value}).one_or_none()
|
||||
|
||||
return group.mealplans
|
||||
@@ -1,25 +0,0 @@
|
||||
from datetime import date
|
||||
from uuid import UUID
|
||||
|
||||
from mealie.db.models.group import GroupMealPlan
|
||||
from mealie.schema.meal_plan.new_meal import ReadPlanEntry
|
||||
|
||||
from ._access_model import AccessModel
|
||||
|
||||
|
||||
class MealDataAccessModel(AccessModel[ReadPlanEntry, GroupMealPlan]):
|
||||
def get_slice(self, start: date, end: date, group_id: UUID) -> list[ReadPlanEntry]:
|
||||
start = start.strftime("%Y-%m-%d")
|
||||
end = end.strftime("%Y-%m-%d")
|
||||
qry = self.session.query(GroupMealPlan).filter(
|
||||
GroupMealPlan.date.between(start, end),
|
||||
GroupMealPlan.group_id == group_id,
|
||||
)
|
||||
|
||||
return [self.schema.from_orm(x) for x in qry.all()]
|
||||
|
||||
def get_today(self, group_id: UUID) -> list[ReadPlanEntry]:
|
||||
today = date.today()
|
||||
qry = self.session.query(GroupMealPlan).filter(GroupMealPlan.date == today, GroupMealPlan.group_id == group_id)
|
||||
|
||||
return [self.schema.from_orm(x) for x in qry.all()]
|
||||
@@ -1,82 +0,0 @@
|
||||
from random import randint
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy.orm import joinedload
|
||||
|
||||
from mealie.db.models.recipe.ingredient import RecipeIngredient
|
||||
from mealie.db.models.recipe.recipe import RecipeModel
|
||||
from mealie.db.models.recipe.settings import RecipeSettings
|
||||
from mealie.schema.recipe import Recipe
|
||||
|
||||
from ._access_model import AccessModel
|
||||
|
||||
|
||||
class RecipeDataAccessModel(AccessModel[Recipe, RecipeModel]):
|
||||
def get_all_public(self, limit: int = None, order_by: str = None, start=0, override_schema=None):
|
||||
eff_schema = override_schema or self.schema
|
||||
|
||||
if order_by:
|
||||
order_attr = getattr(self.sql_model, str(order_by))
|
||||
|
||||
return [
|
||||
eff_schema.from_orm(x)
|
||||
for x in self.session.query(self.sql_model)
|
||||
.join(RecipeSettings)
|
||||
.filter(RecipeSettings.public == True) # noqa: 711
|
||||
.order_by(order_attr.desc())
|
||||
.offset(start)
|
||||
.limit(limit)
|
||||
.all()
|
||||
]
|
||||
|
||||
return [
|
||||
eff_schema.from_orm(x)
|
||||
for x in self.session.query(self.sql_model)
|
||||
.join(RecipeSettings)
|
||||
.filter(RecipeSettings.public == True) # noqa: 711
|
||||
.offset(start)
|
||||
.limit(limit)
|
||||
.all()
|
||||
]
|
||||
|
||||
def update_image(self, slug: str, _: str = None) -> str:
|
||||
entry: RecipeModel = self._query_one(match_value=slug)
|
||||
entry.image = randint(0, 255)
|
||||
self.session.commit()
|
||||
|
||||
return entry.image
|
||||
|
||||
def count_uncategorized(self, count=True, override_schema=None) -> int:
|
||||
return self._count_attribute(
|
||||
attribute_name=RecipeModel.recipe_category,
|
||||
attr_match=None,
|
||||
count=count,
|
||||
override_schema=override_schema,
|
||||
)
|
||||
|
||||
def count_untagged(self, count=True, override_schema=None) -> int:
|
||||
return self._count_attribute(
|
||||
attribute_name=RecipeModel.tags,
|
||||
attr_match=None,
|
||||
count=count,
|
||||
override_schema=override_schema,
|
||||
)
|
||||
|
||||
def summary(self, group_id, start=0, limit=99999, load_foods=False) -> Any:
|
||||
args = [
|
||||
joinedload(RecipeModel.recipe_category),
|
||||
joinedload(RecipeModel.tags),
|
||||
joinedload(RecipeModel.tools),
|
||||
]
|
||||
|
||||
if load_foods:
|
||||
args.append(joinedload(RecipeModel.recipe_ingredient).options(joinedload(RecipeIngredient.food)))
|
||||
|
||||
return (
|
||||
self.session.query(RecipeModel)
|
||||
.options(*args)
|
||||
.filter(RecipeModel.group_id == group_id)
|
||||
.offset(start)
|
||||
.limit(limit)
|
||||
.all()
|
||||
)
|
||||
@@ -1,36 +0,0 @@
|
||||
import random
|
||||
import shutil
|
||||
|
||||
from mealie.assets import users as users_assets
|
||||
from mealie.schema.user.user import PrivateUser, User
|
||||
|
||||
from ._access_model import AccessModel
|
||||
|
||||
|
||||
class UserDataAccessModel(AccessModel[PrivateUser, User]):
|
||||
def update_password(self, id, password: str):
|
||||
entry = self._query_one(match_value=id)
|
||||
entry.update_password(password)
|
||||
self.session.commit()
|
||||
|
||||
return self.schema.from_orm(entry)
|
||||
|
||||
def create(self, user: PrivateUser):
|
||||
new_user = super().create(user)
|
||||
|
||||
# Select Random Image
|
||||
all_images = [
|
||||
users_assets.img_random_1,
|
||||
users_assets.img_random_2,
|
||||
users_assets.img_random_3,
|
||||
]
|
||||
random_image = random.choice(all_images)
|
||||
shutil.copy(random_image, new_user.directory() / "profile.webp")
|
||||
|
||||
return new_user
|
||||
|
||||
def delete(self, id: str) -> User:
|
||||
entry = super().delete(id)
|
||||
# Delete the user's directory
|
||||
shutil.rmtree(PrivateUser.get_directory(id))
|
||||
return entry
|
||||
@@ -1,40 +0,0 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from mealie.core.root_logger import get_logger
|
||||
from mealie.db.data_access_layer.access_model_factory import Database
|
||||
from mealie.schema.recipe import CreateIngredientFood, CreateIngredientUnit
|
||||
|
||||
CWD = Path(__file__).parent
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
def get_default_foods():
|
||||
with open(CWD.joinpath("resources", "foods", "en-us.json"), "r") as f:
|
||||
foods = json.loads(f.read())
|
||||
return foods
|
||||
|
||||
|
||||
def get_default_units() -> dict[str, str]:
|
||||
with open(CWD.joinpath("resources", "units", "en-us.json"), "r") as f:
|
||||
units = json.loads(f.read())
|
||||
return units
|
||||
|
||||
|
||||
def default_recipe_unit_init(db: Database) -> None:
|
||||
for unit in get_default_units().values():
|
||||
try:
|
||||
db.ingredient_units.create(
|
||||
CreateIngredientUnit(
|
||||
name=unit["name"], description=unit["description"], abbreviation=unit["abbreviation"]
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
|
||||
for food in get_default_foods():
|
||||
try:
|
||||
|
||||
db.ingredient_foods.create(CreateIngredientFood(name=food, description=""))
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
@@ -1,62 +0,0 @@
|
||||
from mealie.core import root_logger
|
||||
from mealie.core.config import get_app_settings
|
||||
from mealie.core.security import hash_password
|
||||
from mealie.db.data_access_layer.access_model_factory import Database
|
||||
|
||||
logger = root_logger.get_logger("init_users")
|
||||
settings = get_app_settings()
|
||||
|
||||
|
||||
def dev_users() -> list[dict]:
|
||||
return [
|
||||
{
|
||||
"full_name": "Jason",
|
||||
"username": "jason",
|
||||
"email": "jason@email.com",
|
||||
"password": hash_password(settings.DEFAULT_PASSWORD),
|
||||
"group": settings.DEFAULT_GROUP,
|
||||
"admin": False,
|
||||
},
|
||||
{
|
||||
"full_name": "Bob",
|
||||
"username": "bob",
|
||||
"email": "bob@email.com",
|
||||
"password": hash_password(settings.DEFAULT_PASSWORD),
|
||||
"group": settings.DEFAULT_GROUP,
|
||||
"admin": False,
|
||||
},
|
||||
{
|
||||
"full_name": "Sarah",
|
||||
"username": "sarah",
|
||||
"email": "sarah@email.com",
|
||||
"password": hash_password(settings.DEFAULT_PASSWORD),
|
||||
"group": settings.DEFAULT_GROUP,
|
||||
"admin": False,
|
||||
},
|
||||
{
|
||||
"full_name": "Sammy",
|
||||
"username": "sammy",
|
||||
"email": "sammy@email.com",
|
||||
"password": hash_password(settings.DEFAULT_PASSWORD),
|
||||
"group": settings.DEFAULT_GROUP,
|
||||
"admin": False,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def default_user_init(db: Database):
|
||||
default_user = {
|
||||
"full_name": "Change Me",
|
||||
"username": "admin",
|
||||
"email": settings.DEFAULT_EMAIL,
|
||||
"password": hash_password(settings.DEFAULT_PASSWORD),
|
||||
"group": settings.DEFAULT_GROUP,
|
||||
"admin": True,
|
||||
}
|
||||
|
||||
logger.info("Generating Default User")
|
||||
db.users.create(default_user)
|
||||
|
||||
if not settings.PRODUCTION:
|
||||
for user in dev_users():
|
||||
db.users.create(user)
|
||||
@@ -1,224 +0,0 @@
|
||||
{
|
||||
"acorn-squash": "acorn squash",
|
||||
"alfalfa-sprouts": "alfalfa sprouts",
|
||||
"anchovies": "anchovies",
|
||||
"apples": "apples",
|
||||
"artichoke": "artichoke",
|
||||
"arugula": "arugula",
|
||||
"asparagus": "asparagus",
|
||||
"aubergine": "aubergine",
|
||||
"avocado": "avocado",
|
||||
"bacon": "bacon",
|
||||
"baking-powder": "baking powder",
|
||||
"baking-soda": "baking soda",
|
||||
"baking-sugar": "baking sugar",
|
||||
"bar-sugar": "bar sugar",
|
||||
"basil": "basil",
|
||||
"bell-peppers": "bell peppers",
|
||||
"blackberries": "blackberries",
|
||||
"brassicas": "brassicas",
|
||||
"bok-choy": "bok choy",
|
||||
"broccoflower": "broccoflower",
|
||||
"broccoli": "broccoli",
|
||||
"broccolini": "broccolini",
|
||||
"broccoli-rabe": "broccoli rabe",
|
||||
"brussels-sprouts": "brussels sprouts",
|
||||
"cabbage": "cabbage",
|
||||
"cauliflower": "cauliflower",
|
||||
"chinese-leaves": "chinese leaves",
|
||||
"collard-greens": "collard greens",
|
||||
"kohlrabi": "kohlrabi",
|
||||
"bread": "bread",
|
||||
"breadfruit": "breadfruit",
|
||||
"broad-beans": "broad beans",
|
||||
"brown-sugar": "brown sugar",
|
||||
"butter": "butter",
|
||||
"butternut-pumpkin": "butternut pumpkin",
|
||||
"butternut-squash": "butternut squash",
|
||||
"cactus-edible": "cactus, edible",
|
||||
"calabrese": "calabrese",
|
||||
"cannabis": "cannabis",
|
||||
"capsicum": "capsicum",
|
||||
"caraway": "caraway",
|
||||
"carrot": "carrot",
|
||||
"castor-sugar": "castor sugar",
|
||||
"cayenne-pepper": "cayenne pepper",
|
||||
"celeriac": "celeriac",
|
||||
"celery": "celery",
|
||||
"cereal-grains": "cereal grains",
|
||||
"rice": "rice",
|
||||
"chard": "chard",
|
||||
"cheese": "cheese",
|
||||
"chicory": "chicory",
|
||||
"chilli-peppers": "chilli peppers",
|
||||
"chives": "chives",
|
||||
"chocolate": "chocolate",
|
||||
"cilantro": "cilantro",
|
||||
"cinnamon": "cinnamon",
|
||||
"clarified-butter": "clarified butter",
|
||||
"coconut": "coconut",
|
||||
"coconut-milk": "coconut milk",
|
||||
"coffee": "coffee",
|
||||
"confectioners-sugar": "confectioners' sugar",
|
||||
"coriander": "coriander",
|
||||
"corn": "corn",
|
||||
"corn-syrup": "corn syrup",
|
||||
"cottonseed-oil": "cottonseed oil",
|
||||
"courgette": "courgette",
|
||||
"cream-of-tartar": "cream of tartar",
|
||||
"cucumber": "cucumber",
|
||||
"cumin": "cumin",
|
||||
"daikon": "daikon",
|
||||
"dairy-products-and-dairy-substitutes": "dairy products and dairy substitutes",
|
||||
"eggs": "eggs",
|
||||
"ghee": "ghee",
|
||||
"milk": "milk",
|
||||
"dandelion": "dandelion",
|
||||
"demerara-sugar": "demerara sugar",
|
||||
"dough": "dough",
|
||||
"edible-cactus": "edible cactus",
|
||||
"eggplant": "eggplant",
|
||||
"endive": "endive",
|
||||
"fats": "fats",
|
||||
"spek": "spek",
|
||||
"fava-beans": "fava beans",
|
||||
"fiddlehead": "fiddlehead",
|
||||
"fish": "fish",
|
||||
"catfish": "catfish ",
|
||||
"cod": "cod",
|
||||
"salt-cod": "salt cod",
|
||||
"salmon": "salmon",
|
||||
"skate": "skate",
|
||||
"stockfish": "stockfish",
|
||||
"trout": "trout",
|
||||
"tuna": "tuna",
|
||||
"five-spice-powder": "five spice powder",
|
||||
"flour": "flour",
|
||||
"frisee": "frisee",
|
||||
"fructose": "fructose",
|
||||
"fruit": "fruit",
|
||||
"apple": "apple",
|
||||
"oranges": "oranges",
|
||||
"pear": "pear",
|
||||
"tomato": "tomato ",
|
||||
"fruit-sugar": "fruit sugar",
|
||||
"garam-masala": "garam masala",
|
||||
"garlic": "garlic",
|
||||
"gem-squash": "gem squash",
|
||||
"ginger": "ginger",
|
||||
"giblets": "giblets",
|
||||
"grains": "grains",
|
||||
"maize": "maize",
|
||||
"sweetcorn": "sweetcorn",
|
||||
"teff": "teff",
|
||||
"grape-seed-oil": "grape seed oil",
|
||||
"green-onion": "green onion",
|
||||
"heart-of-palm": "heart of palm",
|
||||
"hemp": "hemp",
|
||||
"herbs": "herbs",
|
||||
"oregano": "oregano",
|
||||
"parsley": "parsley",
|
||||
"honey": "honey",
|
||||
"horse": "horse",
|
||||
"icing-sugar": "icing sugar",
|
||||
"isomalt": "isomalt",
|
||||
"jackfruit": "jackfruit",
|
||||
"jaggery": "jaggery",
|
||||
"jams": "jams",
|
||||
"jellies": "jellies",
|
||||
"jerusalem-artichoke": "jerusalem artichoke",
|
||||
"jicama": "jicama",
|
||||
"kale": "kale",
|
||||
"kumara": "kumara",
|
||||
"leavening-agents": "leavening agents",
|
||||
"leek": "leek",
|
||||
"legumes": "legumes ",
|
||||
"peas": "peas",
|
||||
"beans": "beans",
|
||||
"lentils": "lentils",
|
||||
"lemongrass": "lemongrass",
|
||||
"lettuce": "lettuce",
|
||||
"liver": "liver",
|
||||
"maple-syrup": "maple syrup",
|
||||
"meat": "meat",
|
||||
"mortadella": "mortadella",
|
||||
"mushroom": "mushroom",
|
||||
"white-mushroom": "white mushroom",
|
||||
"mussels": "mussels",
|
||||
"nori": "nori",
|
||||
"nutmeg": "nutmeg",
|
||||
"nutritional-yeast-flakes": "nutritional yeast flakes",
|
||||
"nuts": "nuts",
|
||||
"nanaimo-bar-mix": "nanaimo bar mix",
|
||||
"octopuses": "octopuses",
|
||||
"oils": "oils",
|
||||
"olive-oil": "olive oil",
|
||||
"okra": "okra",
|
||||
"olive": "olive",
|
||||
"onion-family": "onion family",
|
||||
"onion": "onion",
|
||||
"scallion": "scallion",
|
||||
"shallot": "shallot",
|
||||
"spring-onion": "spring onion",
|
||||
"orange-blossom-water": "orange blossom water",
|
||||
"oysters": "oysters",
|
||||
"panch-puran": "panch puran",
|
||||
"paprika": "paprika",
|
||||
"parsnip": "parsnip",
|
||||
"pepper": "pepper",
|
||||
"peppers": "peppers",
|
||||
"plantain": "plantain",
|
||||
"pineapple": "pineapple",
|
||||
"poppy-seeds": "poppy seeds",
|
||||
"potatoes": "potatoes",
|
||||
"poultry": "poultry",
|
||||
"powdered-sugar": "powdered sugar",
|
||||
"pumpkin": "pumpkin",
|
||||
"pumpkin-seeds": "pumpkin seeds",
|
||||
"radish": "radish",
|
||||
"rape": "rape",
|
||||
"raw-sugar": "raw sugar",
|
||||
"refined-sugar": "refined sugar",
|
||||
"rice-flour": "rice flour",
|
||||
"rock-sugar": "rock sugar",
|
||||
"rum": "rum",
|
||||
"salt": "salt",
|
||||
"seafood": "seafood",
|
||||
"seeds": "seeds",
|
||||
"sesame-seeds": "sesame seeds",
|
||||
"sunflower-seeds": "sunflower seeds",
|
||||
"soda": "soda",
|
||||
"soda-baking": "soda, baking",
|
||||
"soybean": "soybean",
|
||||
"spaghetti-squash": "spaghetti squash",
|
||||
"spices": "spices",
|
||||
"spinach": "spinach",
|
||||
"squash-family": "squash family",
|
||||
"squash": "squash",
|
||||
"zucchini": "zucchini",
|
||||
"sugar": "sugar",
|
||||
"caster-sugar": "caster sugar",
|
||||
"granulated-sugar": "granulated sugar",
|
||||
"superfine-sugar": "superfine sugar",
|
||||
"turbanado-sugar": "turbanado sugar",
|
||||
"unrefined-sugar": "unrefined sugar",
|
||||
"white-sugar": "white sugar",
|
||||
"sweet-potato": "sweet potato",
|
||||
"sweeteners": "sweeteners",
|
||||
"cane-sugar": "cane sugar",
|
||||
"tahini": "tahini",
|
||||
"tubers": "tubers",
|
||||
"potato": "potato",
|
||||
"sunchoke": "sunchoke",
|
||||
"taro": "taro",
|
||||
"yam": "yam",
|
||||
"turnip": "turnip",
|
||||
"vanilla": "vanilla",
|
||||
"vegetables": "vegetables",
|
||||
"fiddlehead-fern": "fiddlehead fern",
|
||||
"ful": "ful",
|
||||
"watercress": "watercress",
|
||||
"watermelon": "watermelon",
|
||||
"xanthan-gum": "xanthan gum",
|
||||
"yeast": "yeast"
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
{
|
||||
"teaspoon": {
|
||||
"name": "teaspoon",
|
||||
"description": "",
|
||||
"abbreviation": "tsp"
|
||||
},
|
||||
"tablespoon": {
|
||||
"name": "tablespoon",
|
||||
"description": "",
|
||||
"abbreviation": "tbsp"
|
||||
},
|
||||
"cup": {
|
||||
"name": "cup",
|
||||
"description": "",
|
||||
"abbreviation": "cup"
|
||||
},
|
||||
"fluid-ounce": {
|
||||
"name": "fluid ounce",
|
||||
"description": "",
|
||||
"abbreviation": "fl oz"
|
||||
},
|
||||
"pint": {
|
||||
"name": "pint",
|
||||
"description": "",
|
||||
"abbreviation": "pt"
|
||||
},
|
||||
"quart": {
|
||||
"name": "quart",
|
||||
"description": "",
|
||||
"abbreviation": "qt"
|
||||
},
|
||||
"gallon": {
|
||||
"name": "gallon",
|
||||
"description": "",
|
||||
"abbreviation": "gal"
|
||||
},
|
||||
"milliliter": {
|
||||
"name": "milliliter",
|
||||
"description": "",
|
||||
"abbreviation": "ml"
|
||||
},
|
||||
"liter": {
|
||||
"name": "liter",
|
||||
"description": "",
|
||||
"abbreviation": "l"
|
||||
},
|
||||
"pound": {
|
||||
"name": "pound",
|
||||
"description": "",
|
||||
"abbreviation": "lb"
|
||||
},
|
||||
"ounce": {
|
||||
"name": "ounce",
|
||||
"description": "",
|
||||
"abbreviation": "oz"
|
||||
},
|
||||
"gram": {
|
||||
"name": "gram",
|
||||
"description": "",
|
||||
"abbreviation": "g"
|
||||
},
|
||||
"kilogram": {
|
||||
"name": "kilogram",
|
||||
"description": "",
|
||||
"abbreviation": "kg"
|
||||
},
|
||||
"milligram": {
|
||||
"name": "milligram",
|
||||
"description": "",
|
||||
"abbreviation": "mg"
|
||||
},
|
||||
"splash": {
|
||||
"name": "splash",
|
||||
"description": "",
|
||||
"abbreviation": ""
|
||||
},
|
||||
"dash": {
|
||||
"name": "dash",
|
||||
"description": "",
|
||||
"abbreviation": ""
|
||||
},
|
||||
"serving": {
|
||||
"name": "serving",
|
||||
"description": "",
|
||||
"abbreviation": ""
|
||||
},
|
||||
"head": {
|
||||
"name": "head",
|
||||
"description": "",
|
||||
"abbreviation": ""
|
||||
},
|
||||
"clove": {
|
||||
"name": "clove",
|
||||
"description": "",
|
||||
"abbreviation": ""
|
||||
},
|
||||
"can": {
|
||||
"name": "can",
|
||||
"description": "",
|
||||
"abbreviation": ""
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from .data_access_layer.access_model_factory import Database
|
||||
|
||||
|
||||
def get_database(session: Session):
|
||||
return Database(session)
|
||||
@@ -1,11 +1,11 @@
|
||||
from mealie.core import root_logger
|
||||
from mealie.core.config import get_app_settings
|
||||
from mealie.db.data_access_layer.access_model_factory import Database
|
||||
from mealie.db.data_initialization.init_units_foods import default_recipe_unit_init
|
||||
from mealie.db.data_initialization.init_users import default_user_init
|
||||
from mealie.db.database import get_database
|
||||
from mealie.db.db_setup import create_session, engine
|
||||
from mealie.db.models._model_base import SqlAlchemyBase
|
||||
from mealie.repos.all_repositories import get_repositories
|
||||
from mealie.repos.repository_factory import AllRepositories
|
||||
from mealie.repos.seed.init_units_foods import default_recipe_unit_init
|
||||
from mealie.repos.seed.init_users import default_user_init
|
||||
from mealie.schema.user.user import GroupBase
|
||||
from mealie.services.events import create_general_event
|
||||
from mealie.services.group_services.group_utils import create_new_group
|
||||
@@ -21,13 +21,13 @@ def create_all_models():
|
||||
SqlAlchemyBase.metadata.create_all(engine)
|
||||
|
||||
|
||||
def init_db(db: Database) -> None:
|
||||
def init_db(db: AllRepositories) -> None:
|
||||
default_group_init(db)
|
||||
default_user_init(db)
|
||||
default_recipe_unit_init(db)
|
||||
|
||||
|
||||
def default_group_init(db: Database):
|
||||
def default_group_init(db: AllRepositories):
|
||||
logger.info("Generating Default Group")
|
||||
create_new_group(db, GroupBase(name=settings.DEFAULT_GROUP))
|
||||
|
||||
@@ -36,7 +36,7 @@ def main():
|
||||
create_all_models()
|
||||
|
||||
session = create_session()
|
||||
db = get_database(session)
|
||||
db = get_repositories(session)
|
||||
|
||||
try:
|
||||
init_user = db.users.get_all()
|
||||
|
||||
Reference in New Issue
Block a user