From e5bd261a3c9a0f3d702f9bf960e47513856812ca Mon Sep 17 00:00:00 2001 From: hay-kot Date: Sun, 7 Mar 2021 17:28:28 -0900 Subject: [PATCH] site settings refactor --- mealie/db/database.py | 10 ++- mealie/db/init_db.py | 12 +-- mealie/db/models/group.py | 38 +++++++++- mealie/db/models/model_base.py | 3 +- mealie/db/models/recipe/category.py | 32 ++++---- mealie/db/models/settings.py | 113 ++++++++-------------------- mealie/db/models/theme.py | 2 +- mealie/db/models/users.py | 6 ++ mealie/routes/meal_routes.py | 5 +- mealie/routes/setting_routes.py | 10 +-- mealie/routes/users/crud.py | 2 - mealie/run.sh | 2 + mealie/schema/category.py | 11 ++- mealie/schema/recipe.py | 10 +-- mealie/schema/settings.py | 38 +++++----- mealie/schema/user.py | 11 ++- 16 files changed, 149 insertions(+), 156 deletions(-) diff --git a/mealie/db/database.py b/mealie/db/database.py index 6f3613bb7..0e6a11fab 100644 --- a/mealie/db/database.py +++ b/mealie/db/database.py @@ -1,5 +1,6 @@ from schema.meal import MealPlanInDB from schema.recipe import Recipe +from schema.settings import SiteSettings as SiteSettingsSchema from schema.sign_up import SignUpOut from schema.user import GroupInDB, UserInDB from sqlalchemy.orm.session import Session @@ -8,7 +9,7 @@ from db.db_base import BaseDocument from db.models.group import Group from db.models.mealplan import MealPlanModel from db.models.recipe.recipe import Category, RecipeModel, Tag -from db.models.settings import SiteSettingsModel +from db.models.settings import SiteSettings from db.models.sign_up import SignUp from db.models.theme import SiteThemeModel from db.models.users import User @@ -53,9 +54,10 @@ class _Meals(BaseDocument): class _Settings(BaseDocument): def __init__(self) -> None: - self.primary_key = "name" - self.sql_model = SiteSettingsModel - self.orm_mode = False + self.primary_key = "id" + self.sql_model = SiteSettings + self.orm_mode = True + self.schema = SiteSettingsSchema class _Themes(BaseDocument): diff --git a/mealie/db/init_db.py b/mealie/db/init_db.py index 539e3bda2..f83cc8c72 100644 --- a/mealie/db/init_db.py +++ b/mealie/db/init_db.py @@ -1,7 +1,7 @@ from core.config import DEFAULT_GROUP from core.security import get_password_hash from fastapi.logger import logger -from schema.settings import SiteSettings, Webhooks +from schema.settings import SiteSettings from sqlalchemy.orm import Session from sqlalchemy.orm.session import Session @@ -43,13 +43,9 @@ def default_theme_init(session: Session): def default_settings_init(session: Session): - try: - webhooks = Webhooks() - default_entry = SiteSettings(name="main", webhooks=webhooks) - document = db.settings.create(session, default_entry.dict()) - logger.info(f"Created Site Settings: \n {document}") - except: - pass + data = {"language": "en", "sidebar": {"categories": []}} + document = db.settings.create(session, data) + logger.info(f"Created Site Settings: \n {document}") def default_group_init(session: Session): diff --git a/mealie/db/models/group.py b/mealie/db/models/group.py index ed5e8e037..db68425d4 100644 --- a/mealie/db/models/group.py +++ b/mealie/db/models/group.py @@ -2,8 +2,8 @@ import sqlalchemy as sa import sqlalchemy.orm as orm from core.config import DEFAULT_GROUP from db.models.model_base import BaseMixins, SqlAlchemyBase +from db.models.recipe.category import group2categories from fastapi.logger import logger -from slugify import slugify class Group(SqlAlchemyBase, BaseMixins): @@ -12,6 +12,8 @@ class Group(SqlAlchemyBase, BaseMixins): name = sa.Column(sa.String, index=True, nullable=False, unique=True) users = orm.relationship("User", back_populates="group") mealplans = orm.relationship("MealPlanModel", back_populates="group") + categories = orm.relationship("Category", secondary=group2categories) + webhooks = orm.relationship("WebHookModel", uselist=False, cascade="all, delete") def __init__(self, name, session=None) -> None: self.name = name @@ -29,3 +31,37 @@ class Group(SqlAlchemyBase, BaseMixins): except: logger.info("Category doesn't exists, creating category") return Group(name=name) + + +class WebHookModel(SqlAlchemyBase, BaseMixins): + __tablename__ = "webhook_settings" + id = sa.Column(sa.Integer, primary_key=True) + parent_id = sa.Column(sa.String, sa.ForeignKey("groups.id")) + webhookURLs = orm.relationship( + "WebhookURLModel", uselist=True, cascade="all, delete" + ) + webhookTime = sa.Column(sa.String, default="00:00") + enabled = sa.Column(sa.Boolean, default=False) + + def __init__( + self, webhookURLs: list, webhookTime: str, enabled: bool = False, session=None + ) -> None: + + self.webhookURLs = [WebhookURLModel(url=x) for x in webhookURLs] + self.webhookTime = webhookTime + self.enabled = enabled + + def update( + self, session, webhookURLs: list, webhookTime: str, enabled: bool + ) -> None: + + self._sql_remove_list(session, [WebhookURLModel], self.id) + + self.__init__(webhookURLs, webhookTime, enabled) + + +class WebhookURLModel(SqlAlchemyBase): + __tablename__ = "webhook_urls" + id = sa.Column(sa.Integer, primary_key=True) + url = sa.Column(sa.String) + parent_id = sa.Column(sa.Integer, sa.ForeignKey("webhook_settings.id")) diff --git a/mealie/db/models/model_base.py b/mealie/db/models/model_base.py index 0c2ddaa87..9a77a5d76 100644 --- a/mealie/db/models/model_base.py +++ b/mealie/db/models/model_base.py @@ -8,9 +8,8 @@ SqlAlchemyBase = dec.declarative_base() class BaseMixins: @staticmethod def _sql_remove_list(session, list_of_tables: list, parent_id): - for table in list_of_tables: - session.query(table).filter_by(parent_id=parent_id).delete() + session.query(table).filter(parent_id == parent_id).delete() @staticmethod def _flatten_dict(list_of_dict: List[dict]): diff --git a/mealie/db/models/recipe/category.py b/mealie/db/models/recipe/category.py index ce5ac087b..cdccbea26 100644 --- a/mealie/db/models/recipe/category.py +++ b/mealie/db/models/recipe/category.py @@ -5,6 +5,20 @@ from fastapi.logger import logger from slugify import slugify from sqlalchemy.orm import validates +sidebar2categories = sa.Table( + "sidebar2categories", + SqlAlchemyBase.metadata, + sa.Column("sidebar_id", sa.Integer, sa.ForeignKey("site_sidebar.id")), + sa.Column("category_slug", sa.String, sa.ForeignKey("categories.slug")), +) + +group2categories = sa.Table( + "group2categories", + SqlAlchemyBase.metadata, + sa.Column("group_id", sa.Integer, sa.ForeignKey("groups.id")), + sa.Column("category_slug", sa.String, sa.ForeignKey("categories.slug")), +) + recipes2categories = sa.Table( "recipes2categories", SqlAlchemyBase.metadata, @@ -45,21 +59,3 @@ class Category(SqlAlchemyBase): except: logger.info("Category doesn't exists, creating category") return Category(name=name) - - def to_str(self): - return self.name - - def dict(self): - return { - "id": self.id, - "slug": self.slug, - "name": self.name, - "recipes": [x.dict() for x in self.recipes], - } - - def dict_no_recipes(self): - return { - "id": self.id, - "slug": self.slug, - "name": self.name, - } diff --git a/mealie/db/models/settings.py b/mealie/db/models/settings.py index 1ba470bd5..ca9b89592 100644 --- a/mealie/db/models/settings.py +++ b/mealie/db/models/settings.py @@ -1,93 +1,42 @@ import sqlalchemy as sa import sqlalchemy.orm as orm from db.models.model_base import BaseMixins, SqlAlchemyBase +from db.models.recipe.category import Category, sidebar2categories +from sqlalchemy.orm import Session -class SiteSettingsModel(SqlAlchemyBase, BaseMixins): +class Sidebar(SqlAlchemyBase, BaseMixins): + __tablename__ = "site_sidebar" + id = sa.Column(sa.Integer, primary_key=True) + parent_id = sa.Column(sa.Integer, sa.ForeignKey("site_settings.id")) + categories = orm.relationship("Category", secondary=sidebar2categories, cascade="delete") + + def __init__(self, session: Session, sidebar: dict) -> None: + categories = sidebar.get("categories") + new_categories = [] + if not categories: + return None + for cat in categories: + slug = cat.get("slug") + cat_in_db = session.query(Category).filter(Category.slug == slug).one() + new_categories.append(cat_in_db) + + self.categories = new_categories + + +class SiteSettings(SqlAlchemyBase, BaseMixins): __tablename__ = "site_settings" - name = sa.Column(sa.String, primary_key=True) - planCategories = orm.relationship( - "MealCategory", uselist=True, cascade="all, delete" - ) - webhooks = orm.relationship("WebHookModel", uselist=False, cascade="all, delete") + id = sa.Column(sa.Integer, primary_key=True) + language = sa.Column(sa.String) + sidebar = orm.relationship("Sidebar", uselist=False, cascade="all") def __init__( - self, name: str = None, webhooks: dict = None, planCategories=[], session=None + self, session=None, language="en", sidebar: list = {"categories": []} ) -> None: - self.name = name - self.planCategories = [MealCategory(cat) for cat in planCategories] - self.webhooks = WebHookModel(**webhooks) + self._sql_remove_list(session, [Sidebar], self.id) - def update(self, session, name, webhooks: dict, planCategories=[]) -> dict: + self.language = language + self.sidebar = Sidebar(session, sidebar) - self._sql_remove_list(session, [MealCategory], self.name) - self.name = name - self.planCategories = [MealCategory(x) for x in planCategories] - self.webhooks.update(session=session, **webhooks) - return - - def dict(self): - data = { - "name": self.name, - "planCategories": [cat.to_str() for cat in self.planCategories], - "webhooks": self.webhooks.dict(), - } - return data - - -class MealCategory(SqlAlchemyBase): - __tablename__ = "meal_plan_categories" - id = sa.Column(sa.Integer, primary_key=True) - name = sa.Column(sa.String) - parent_id = sa.Column(sa.Integer, sa.ForeignKey("site_settings.name")) - - def __init__(self, name) -> None: - self.name = name - - def to_str(self): - return self.name - - -class WebHookModel(SqlAlchemyBase, BaseMixins): - __tablename__ = "webhook_settings" - id = sa.Column(sa.Integer, primary_key=True) - parent_id = sa.Column(sa.String, sa.ForeignKey("site_settings.name")) - webhookURLs = orm.relationship( - "WebhookURLModel", uselist=True, cascade="all, delete" - ) - webhookTime = sa.Column(sa.String, default="00:00") - enabled = sa.Column(sa.Boolean, default=False) - - def __init__( - self, webhookURLs: list, webhookTime: str, enabled: bool = False, session=None - ) -> None: - - self.webhookURLs = [WebhookURLModel(url=x) for x in webhookURLs] - self.webhookTime = webhookTime - self.enabled = enabled - - def update( - self, session, webhookURLs: list, webhookTime: str, enabled: bool - ) -> None: - - self._sql_remove_list(session, [WebhookURLModel], self.id) - - self.__init__(webhookURLs, webhookTime, enabled) - - def dict(self): - data = { - "webhookURLs": [url.to_str() for url in self.webhookURLs], - "webhookTime": self.webhookTime, - "enabled": self.enabled, - } - return data - - -class WebhookURLModel(SqlAlchemyBase): - __tablename__ = "webhook_urls" - id = sa.Column(sa.Integer, primary_key=True) - url = sa.Column(sa.String) - parent_id = sa.Column(sa.Integer, sa.ForeignKey("webhook_settings.id")) - - def to_str(self): - return self.url + def update(self, session, language, sidebar): + self.__init__(session=session, language=language, sidebar=sidebar) diff --git a/mealie/db/models/theme.py b/mealie/db/models/theme.py index f778545ce..d30b03ceb 100644 --- a/mealie/db/models/theme.py +++ b/mealie/db/models/theme.py @@ -1,6 +1,6 @@ import sqlalchemy as sa import sqlalchemy.orm as orm -from db.models.model_base import BaseMixins, SqlAlchemyBase +from db.models.model_base import SqlAlchemyBase class SiteThemeModel(SqlAlchemyBase): diff --git a/mealie/db/models/users.py b/mealie/db/models/users.py index 422fc2ce0..30bef3d35 100644 --- a/mealie/db/models/users.py +++ b/mealie/db/models/users.py @@ -3,6 +3,12 @@ from db.models.group import Group from db.models.model_base import BaseMixins, SqlAlchemyBase from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, orm +# I'm not sure this is necessasry, browser based settings may be sufficient +# class UserSettings(SqlAlchemyBase, BaseMixins): +# __tablename__ = "user_settings" +# id = Column(Integer, primary_key=True, index=True) +# parent_id = Column(String, ForeignKey("users.id")) + class User(SqlAlchemyBase, BaseMixins): __tablename__ = "users" diff --git a/mealie/routes/meal_routes.py b/mealie/routes/meal_routes.py index 053dc3daf..9cd296945 100644 --- a/mealie/routes/meal_routes.py +++ b/mealie/routes/meal_routes.py @@ -5,6 +5,7 @@ from db.database import db from db.db_setup import generate_session from fastapi import APIRouter, Depends from schema.meal import MealPlanBase, MealPlanInDB +from schema.recipe import Recipe from schema.snackbar import SnackResponse from services.meal_services import get_todays_meal, process_meals from sqlalchemy.orm.session import Session @@ -26,9 +27,9 @@ def get_shopping_list(id: str, session: Session = Depends(generate_session)): mealplan = db.meals.get(session, id) mealplan: MealPlanInDB slugs = [x.slug for x in mealplan.meals] - recipes = [db.recipes.get(session, x) for x in slugs] + recipes: list[Recipe] = [db.recipes.get(session, x) for x in slugs] ingredients = [ - {"name": x.get("name"), "recipeIngredient": x.get("recipeIngredient")} + {"name": x.name, "recipeIngredient": x.recipeIngredient} for x in recipes if x ] diff --git a/mealie/routes/setting_routes.py b/mealie/routes/setting_routes.py index b59cd6677..51317d283 100644 --- a/mealie/routes/setting_routes.py +++ b/mealie/routes/setting_routes.py @@ -2,9 +2,9 @@ from db.database import db from db.db_setup import generate_session from fastapi import APIRouter, Depends from schema.settings import SiteSettings +from schema.snackbar import SnackResponse from sqlalchemy.orm.session import Session from utils.post_webhooks import post_webhooks -from schema.snackbar import SnackResponse router = APIRouter(prefix="/api/site-settings", tags=["Settings"]) @@ -13,17 +13,15 @@ router = APIRouter(prefix="/api/site-settings", tags=["Settings"]) def get_main_settings(session: Session = Depends(generate_session)): """ Returns basic site settings """ - try: - data = db.settings.get(session, "main") - except: - return + data = db.settings.get(session, 1) + return data @router.put("") def update_settings(data: SiteSettings, session: Session = Depends(generate_session)): """ Returns Site Settings """ - db.settings.update(session, "main", data.dict()) + db.settings.update(session, 1, data.dict()) return SnackResponse.success("Settings Updated") diff --git a/mealie/routes/users/crud.py b/mealie/routes/users/crud.py index 336376281..e2d62e4a6 100644 --- a/mealie/routes/users/crud.py +++ b/mealie/routes/users/crud.py @@ -82,7 +82,6 @@ async def get_user_image(id: str): """ Returns a users profile picture """ user_dir = USER_DIR.joinpath(id) for recipe_image in user_dir.glob("profile_image.*"): - print(recipe_image) return FileResponse(recipe_image) else: return False @@ -128,7 +127,6 @@ async def update_password( match_passwords = verify_password( password_change.current_password, current_user.password ) - print(match_passwords) match_id = current_user.id == id if match_passwords and match_id: diff --git a/mealie/run.sh b/mealie/run.sh index 1c72a05d6..308be5b8b 100644 --- a/mealie/run.sh +++ b/mealie/run.sh @@ -3,6 +3,8 @@ ## Migrations # TODO +# Database Init + ## Web Server caddy start --config ./Caddyfile diff --git a/mealie/schema/category.py b/mealie/schema/category.py index de74f1003..0df0838a7 100644 --- a/mealie/schema/category.py +++ b/mealie/schema/category.py @@ -1,13 +1,20 @@ from typing import List, Optional -from pydantic.main import BaseModel +from fastapi_camelcase import CamelModel + from schema.recipe import Recipe -class RecipeCategoryResponse(BaseModel): +class CategoryBase(CamelModel): id: int name: str slug: str + + class Config: + orm_mode = True + + +class RecipeCategoryResponse(CategoryBase): recipes: Optional[List[Recipe]] class Config: diff --git a/mealie/schema/recipe.py b/mealie/schema/recipe.py index 2a5e12c67..63cd5a71f 100644 --- a/mealie/schema/recipe.py +++ b/mealie/schema/recipe.py @@ -1,9 +1,8 @@ import datetime from typing import Any, List, Optional -from db.models.recipe.ingredient import RecipeIngredient from db.models.recipe.recipe import RecipeModel -from pydantic import BaseModel, Schema, validator +from pydantic import BaseModel, validator from pydantic.utils import GetterDict from slugify import slugify @@ -36,7 +35,6 @@ class Nutrition(BaseModel): class Recipe(BaseModel): - # Standard Schema name: str description: Optional[str] image: Optional[Any] @@ -63,13 +61,13 @@ class Recipe(BaseModel): orm_mode = True @classmethod - def getter_dict(cls, name_orm: RecipeModel): - print(name_orm.recipeIngredient) + def getter_dict(_cls, name_orm: RecipeModel): return { **GetterDict(name_orm), "recipeIngredient": [x.ingredient for x in name_orm.recipeIngredient], "recipeCategory": [x.name for x in name_orm.recipeCategory], - "tags": [x.name for x in name_orm.tags] + "tags": [x.name for x in name_orm.tags], + "extras": {x.key_name: x.value for x in name_orm.extras}, } schema_extra = { diff --git a/mealie/schema/settings.py b/mealie/schema/settings.py index 7cd6abd95..bfdd4cec4 100644 --- a/mealie/schema/settings.py +++ b/mealie/schema/settings.py @@ -1,28 +1,24 @@ -from typing import List, Optional +from typing import Optional -from pydantic import BaseModel +from fastapi_camelcase import CamelModel + +from schema.category import CategoryBase -class Webhooks(BaseModel): - webhookTime: str = "00:00" - webhookURLs: Optional[List[str]] = [] - enabled: bool = False - - -class SiteSettings(BaseModel): - name: str = "main" - planCategories: list[str] = [] - webhooks: Webhooks +class Sidebar(CamelModel): + categories: Optional[list[CategoryBase]] class Config: + orm_mode = True + + +class SiteSettings(CamelModel): + language: str + sidebar: Sidebar + + class Config: + orm_mode = True + schema_extra = { - "example": { - "name": "main", - "planCategories": ["dinner", "lunch"], - "webhooks": { - "webhookTime": "00:00", - "webhookURLs": ["https://mywebhookurl.com/webhook"], - "enable": False, - }, - } + "example": {"id": "1", "language": "en", "sidebar": ["// TODO"]} } diff --git a/mealie/schema/user.py b/mealie/schema/user.py index 27b9f69a3..885593508 100644 --- a/mealie/schema/user.py +++ b/mealie/schema/user.py @@ -1,7 +1,9 @@ from typing import Optional from core.config import DEFAULT_GROUP +from db.models.users import User from fastapi_camelcase import CamelModel +from pydantic.utils import GetterDict class ChangePassword(CamelModel): @@ -40,11 +42,18 @@ class UserIn(UserBase): class UserOut(UserBase): id: int - group: GroupBase + group: str class Config: orm_mode = True + @classmethod + def getter_dict(cls, ormModel: User): + return { + **GetterDict(ormModel), + "group": ormModel.group.name, + } + class UserInDB(UserOut): password: str