mirror of
https://github.com/hay-kot/mealie.git
synced 2025-08-22 14:33:33 -07:00
continued model refactor
This commit is contained in:
parent
e5bd261a3c
commit
1ac2d7a4ce
17 changed files with 198 additions and 139 deletions
14
frontend/src/api/groups.js
Normal file
14
frontend/src/api/groups.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { baseURL } from "./api-utils";
|
||||
import { apiReq } from "./api-utils";
|
||||
const groupPrefix = baseURL + "groups";
|
||||
|
||||
const groupsURLs = {
|
||||
groups: `${groupPrefix}`,
|
||||
};
|
||||
|
||||
export default {
|
||||
async allGroups() {
|
||||
let response = await apiReq.get(groupsURLs.groups);
|
||||
return response.data;
|
||||
},
|
||||
};
|
|
@ -9,6 +9,7 @@ import category from "./category";
|
|||
import meta from "./meta";
|
||||
import users from "./users";
|
||||
import signUps from "./signUps";
|
||||
import groups from "./groups";
|
||||
|
||||
export default {
|
||||
recipes: recipe,
|
||||
|
@ -22,4 +23,5 @@ export default {
|
|||
meta: meta,
|
||||
users: users,
|
||||
signUps: signUps,
|
||||
groups: groups,
|
||||
};
|
||||
|
|
|
@ -19,6 +19,10 @@
|
|||
User Groups
|
||||
</v-toolbar-title>
|
||||
|
||||
<div>
|
||||
<v-select class="mt-7 ml-5" dense flat solo> </v-select>
|
||||
</div>
|
||||
|
||||
<v-spacer> </v-spacer>
|
||||
<v-dialog v-model="dialog" max-width="600px">
|
||||
<template v-slot:activator="{ on, attrs }">
|
||||
|
@ -136,7 +140,7 @@ export default {
|
|||
activeName: null,
|
||||
headers: [
|
||||
{
|
||||
text: "User ID",
|
||||
text: "User IDs",
|
||||
align: "start",
|
||||
sortable: false,
|
||||
value: "id",
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
</v-tab>
|
||||
</v-tabs>
|
||||
|
||||
<v-tabs-items v-model="tab" >
|
||||
<v-tabs-items v-model="tab">
|
||||
<v-tab-item>
|
||||
<TheUserTable />
|
||||
</v-tab-item>
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
from schema.category import RecipeCategoryResponse
|
||||
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.theme import SiteTheme
|
||||
from schema.user import GroupInDB, UserInDB
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
|
@ -34,7 +36,8 @@ class _Categories(BaseDocument):
|
|||
def __init__(self) -> None:
|
||||
self.primary_key = "slug"
|
||||
self.sql_model = Category
|
||||
self.orm_mode = False
|
||||
self.orm_mode = True
|
||||
self.schema = RecipeCategoryResponse
|
||||
|
||||
|
||||
class _Tags(BaseDocument):
|
||||
|
@ -64,7 +67,8 @@ class _Themes(BaseDocument):
|
|||
def __init__(self) -> None:
|
||||
self.primary_key = "name"
|
||||
self.sql_model = SiteThemeModel
|
||||
self.orm_mode = False
|
||||
self.orm_mode = True
|
||||
self.schema = SiteTheme
|
||||
|
||||
|
||||
class _Users(BaseDocument):
|
||||
|
|
|
@ -2,6 +2,7 @@ from core.config import DEFAULT_GROUP
|
|||
from core.security import get_password_hash
|
||||
from fastapi.logger import logger
|
||||
from schema.settings import SiteSettings
|
||||
from schema.theme import SiteTheme
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
|
@ -22,29 +23,17 @@ def init_db(db: Session = None) -> None:
|
|||
|
||||
|
||||
def default_theme_init(session: Session):
|
||||
default_theme = {
|
||||
"name": "default",
|
||||
"colors": {
|
||||
"primary": "#E58325",
|
||||
"accent": "#00457A",
|
||||
"secondary": "#973542",
|
||||
"success": "#5AB1BB",
|
||||
"info": "#4990BA",
|
||||
"warning": "#FF4081",
|
||||
"error": "#EF5350",
|
||||
},
|
||||
}
|
||||
db.themes.create(session, SiteTheme().dict())
|
||||
|
||||
try:
|
||||
db.themes.create(session, default_theme)
|
||||
logger.info("Generating default theme...")
|
||||
except:
|
||||
logger.info("Default Theme Exists.. skipping generation")
|
||||
|
||||
|
||||
def default_settings_init(session: Session):
|
||||
data = {"language": "en", "sidebar": {"categories": []}}
|
||||
document = db.settings.create(session, data)
|
||||
data = {"language": "en", "home_page_settings": {"categories": []}}
|
||||
document = db.settings.create(session, SiteSettings().dict())
|
||||
logger.info(f"Created Site Settings: \n {document}")
|
||||
|
||||
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
from typing import List
|
||||
|
||||
import sqlalchemy.ext.declarative as dec
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
SqlAlchemyBase = dec.declarative_base()
|
||||
|
||||
|
||||
class BaseMixins:
|
||||
@staticmethod
|
||||
def _sql_remove_list(session, list_of_tables: list, parent_id):
|
||||
def _sql_remove_list(session: Session, list_of_tables: list, parent_id):
|
||||
for table in list_of_tables:
|
||||
session.query(table).filter(parent_id == parent_id).delete()
|
||||
|
||||
|
@ -19,3 +20,29 @@ class BaseMixins:
|
|||
finalMap.update(d.dict())
|
||||
|
||||
return finalMap
|
||||
|
||||
|
||||
# ! Don't use!
|
||||
def update_generics(func):
|
||||
"""An experimental function that does the initial work of updating attributes on a class
|
||||
and passing "complex" data types recuresively to an "self.update()" function if one exists.
|
||||
|
||||
Args:
|
||||
func ([type]): [description]
|
||||
"""
|
||||
|
||||
def wrapper(class_object, session, new_data: dict):
|
||||
complex_attributed = {}
|
||||
for key, value in new_data.items():
|
||||
|
||||
attribute = getattr(class_object, key, None)
|
||||
|
||||
if attribute and isinstance(attribute, SqlAlchemyBase):
|
||||
attribute.update(session, value)
|
||||
|
||||
elif attribute:
|
||||
setattr(class_object, key, value)
|
||||
print("Complex", complex_attributed)
|
||||
func(class_object, complex_attributed)
|
||||
|
||||
return wrapper
|
||||
|
|
|
@ -5,10 +5,10 @@ from fastapi.logger import logger
|
|||
from slugify import slugify
|
||||
from sqlalchemy.orm import validates
|
||||
|
||||
sidebar2categories = sa.Table(
|
||||
"sidebar2categories",
|
||||
site_settings2categories = sa.Table(
|
||||
"site_settings2categoories",
|
||||
SqlAlchemyBase.metadata,
|
||||
sa.Column("sidebar_id", sa.Integer, sa.ForeignKey("site_sidebar.id")),
|
||||
sa.Column("sidebar_id", sa.Integer, sa.ForeignKey("site_settings.id")),
|
||||
sa.Column("category_slug", sa.String, sa.ForeignKey("categories.slug")),
|
||||
)
|
||||
|
||||
|
|
|
@ -182,30 +182,3 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
|
|||
orgURL=orgURL,
|
||||
extras=extras,
|
||||
)
|
||||
|
||||
# def dict(self):
|
||||
# data = {
|
||||
# "name": self.name,
|
||||
# "description": self.description,
|
||||
# "image": self.image,
|
||||
# "recipeYield": self.recipeYield,
|
||||
# "recipeCuisine": self.recipeCuisine,
|
||||
# "recipeCategory": [x.to_str() for x in self.recipeCategory],
|
||||
# "recipeIngredient": [x.to_str() for x in self.recipeIngredient],
|
||||
# "recipeInstructions": [x.dict() for x in self.recipeInstructions],
|
||||
# "nutrition": self.nutrition.dict(),
|
||||
# "totalTime": self.totalTime,
|
||||
# "prepTime": self.prepTime,
|
||||
# "performTime": self.performTime,
|
||||
# "tool": [x.str() for x in self.tool],
|
||||
# # Mealie Specific
|
||||
# "slug": self.slug,
|
||||
# "tags": [x.to_str() for x in self.tags],
|
||||
# "dateAdded": self.dateAdded,
|
||||
# "notes": [x.dict() for x in self.notes],
|
||||
# "rating": self.rating,
|
||||
# "orgURL": self.orgURL,
|
||||
# "extras": RecipeModel._flatten_dict(self.extras),
|
||||
# }
|
||||
|
||||
# return data
|
||||
|
|
|
@ -1,42 +1,32 @@
|
|||
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 db.models.recipe.category import Category, site_settings2categories
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
|
||||
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"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
language = sa.Column(sa.String)
|
||||
sidebar = orm.relationship("Sidebar", uselist=False, cascade="all")
|
||||
categories = orm.relationship(
|
||||
"Category",
|
||||
secondary=site_settings2categories,
|
||||
single_parent=True,
|
||||
)
|
||||
show_recent = sa.Column(sa.Boolean, default=True)
|
||||
|
||||
def __init__(
|
||||
self, session=None, language="en", sidebar: list = {"categories": []}
|
||||
self, session: Session = None, language="en", categories: list = [], show_recent=True
|
||||
) -> None:
|
||||
self._sql_remove_list(session, [Sidebar], self.id)
|
||||
|
||||
session.commit()
|
||||
self.language = language
|
||||
self.sidebar = Sidebar(session, sidebar)
|
||||
|
||||
def update(self, session, language, sidebar):
|
||||
self.__init__(session=session, language=language, sidebar=sidebar)
|
||||
self.show_recent = show_recent
|
||||
self.categories = [
|
||||
Category.create_if_not_exist(session=session, name=cat.get("name"))
|
||||
for cat in categories
|
||||
]
|
||||
|
||||
def update(self, *args, **kwarg):
|
||||
self.__init__(*args, **kwarg)
|
||||
|
|
|
@ -1,12 +1,6 @@
|
|||
import shutil
|
||||
from datetime import timedelta
|
||||
|
||||
from core.config import USER_DIR
|
||||
from core.security import get_password_hash, verify_password
|
||||
from db.database import db
|
||||
from db.db_setup import generate_session
|
||||
from fastapi import APIRouter, Depends, File, UploadFile
|
||||
from fastapi.responses import FileResponse
|
||||
from fastapi import APIRouter, Depends
|
||||
from routes.deps import manager
|
||||
from schema.snackbar import SnackResponse
|
||||
from schema.user import GroupBase, GroupInDB
|
||||
|
@ -21,4 +15,52 @@ async def get_all_groups(
|
|||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Returns a list of all groups in the database """
|
||||
|
||||
return db.groups.get_all(session)
|
||||
|
||||
|
||||
@router.post("")
|
||||
async def create_group(
|
||||
group_data: GroupBase,
|
||||
current_user=Depends(manager),
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Creates a Group in the Database """
|
||||
|
||||
db.groups.create(session, group_data.dict())
|
||||
|
||||
return
|
||||
|
||||
|
||||
@router.put("/{id}")
|
||||
async def update_group_data(
|
||||
id: int,
|
||||
group_data: GroupInDB,
|
||||
current_user=Depends(manager),
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Updates a User Group """
|
||||
|
||||
return db.groups.update(session, id, group_data)
|
||||
|
||||
|
||||
@router.delete("/{id}")
|
||||
async def delete_user_group(
|
||||
id: int, current_user=Depends(manager), session: Session = Depends(generate_session)
|
||||
):
|
||||
""" Removes a user group from the database """
|
||||
|
||||
if id == 1:
|
||||
return SnackResponse.error("Cannot delete default group")
|
||||
|
||||
group: GroupInDB = db.groups.get(session, id)
|
||||
|
||||
if not group:
|
||||
return SnackResponse.error("Group not found")
|
||||
|
||||
if not group.users == []:
|
||||
return SnackResponse.error("Cannot delete group with users")
|
||||
|
||||
db.groups.delete(session, id)
|
||||
|
||||
return
|
||||
|
|
|
@ -29,9 +29,11 @@ class MealPlanBase(BaseModel):
|
|||
raise ValueError("EndDate should be greater than StartDate")
|
||||
return v
|
||||
|
||||
|
||||
class MealPlanProcessed(MealPlanBase):
|
||||
meals: list[MealOut]
|
||||
|
||||
|
||||
class MealPlanInDB(MealPlanProcessed):
|
||||
uid: str
|
||||
|
||||
|
@ -39,7 +41,6 @@ class MealPlanInDB(MealPlanProcessed):
|
|||
orm_mode = True
|
||||
|
||||
|
||||
|
||||
class MealPlan(BaseModel):
|
||||
uid: Optional[str]
|
||||
|
||||
|
|
|
@ -5,20 +5,22 @@ from fastapi_camelcase import CamelModel
|
|||
from schema.category import CategoryBase
|
||||
|
||||
|
||||
class Sidebar(CamelModel):
|
||||
categories: Optional[list[CategoryBase]]
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
class SiteSettings(CamelModel):
|
||||
language: str
|
||||
sidebar: Sidebar
|
||||
language: str = "en"
|
||||
show_recent: bool = True
|
||||
categories: Optional[list[CategoryBase]] = []
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
schema_extra = {
|
||||
"example": {"id": "1", "language": "en", "sidebar": ["// TODO"]}
|
||||
"example": {
|
||||
"language": "en",
|
||||
"showRecent": True,
|
||||
"categories": [
|
||||
{"id": 1, "name": "thanksgiving", "slug": "thanksgiving"},
|
||||
{"id": 2, "name": "homechef", "slug": "homechef"},
|
||||
{"id": 3, "name": "potatoes", "slug": "potatoes"},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,25 @@
|
|||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Colors(BaseModel):
|
||||
primary: str
|
||||
accent: str
|
||||
secondary: str
|
||||
success: str
|
||||
info: str
|
||||
warning: str
|
||||
error: str
|
||||
primary: str = "#E58325"
|
||||
accent: str = "#00457A"
|
||||
secondary: str = "#973542"
|
||||
success: str = "#5AB1BB"
|
||||
info: str = "#4990BA"
|
||||
warning: str = "#FF4081"
|
||||
error: str = "#EF5350"
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
class SiteTheme(BaseModel):
|
||||
name: str
|
||||
colors: Colors
|
||||
name: str = "default"
|
||||
colors: Colors = Colors()
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
schema_extra = {
|
||||
"example": {
|
||||
"name": "default",
|
||||
|
@ -28,4 +33,4 @@ class SiteTheme(BaseModel):
|
|||
"error": "#EF5350",
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
from typing import Optional
|
||||
from typing import Any, Optional
|
||||
|
||||
from core.config import DEFAULT_GROUP
|
||||
from db.models.group import WebHookModel
|
||||
from db.models.users import User
|
||||
from fastapi_camelcase import CamelModel
|
||||
from pydantic.utils import GetterDict
|
||||
|
||||
from schema.category import CategoryBase
|
||||
from schema.meal import MealPlanInDB
|
||||
|
||||
|
||||
class ChangePassword(CamelModel):
|
||||
current_password: str
|
||||
|
@ -27,7 +31,13 @@ class UserBase(CamelModel):
|
|||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
class Config:
|
||||
@classmethod
|
||||
def getter_dict(_cls, name_orm: User):
|
||||
return {
|
||||
**GetterDict(name_orm),
|
||||
"group": name_orm.group.name,
|
||||
}
|
||||
|
||||
schema_extra = {
|
||||
"fullName": "Change Me",
|
||||
"email": "changeme@email.com",
|
||||
|
@ -63,10 +73,29 @@ class UserInDB(UserOut):
|
|||
orm_mode = True
|
||||
|
||||
|
||||
class Webhooks(CamelModel):
|
||||
webhookURLs: list[str] = []
|
||||
webhookTime: str = "00:00"
|
||||
enabled: bool = False
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
@classmethod
|
||||
def getter_dict(_cls, orm_model: WebHookModel):
|
||||
return {
|
||||
**GetterDict(orm_model),
|
||||
"webookURLs": [x.url for x in orm_model.webhookURLs],
|
||||
}
|
||||
|
||||
|
||||
class GroupInDB(GroupBase):
|
||||
id: int
|
||||
name: str
|
||||
users: Optional[list[UserOut]]
|
||||
mealplans: Optional[list[MealPlanInDB]]
|
||||
categories: Optional[list[CategoryBase]]
|
||||
webhooks: Optional[Webhooks]
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import json
|
||||
from schema.settings import SiteSettings
|
||||
from schema.theme import SiteTheme
|
||||
|
||||
import pytest
|
||||
from tests.utils.routes import (
|
||||
|
@ -11,30 +13,12 @@ from tests.utils.routes import (
|
|||
|
||||
@pytest.fixture(scope="function")
|
||||
def default_settings():
|
||||
return {
|
||||
"name": "main",
|
||||
"planCategories": [],
|
||||
"webhooks": {"webhookTime": "00:00", "webhookURLs": [], "enabled": False},
|
||||
}
|
||||
return SiteSettings().dict(by_alias=True)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def default_theme(api_client):
|
||||
|
||||
default_theme = {
|
||||
"name": "default",
|
||||
"colors": {
|
||||
"primary": "#E58325",
|
||||
"accent": "#00457A",
|
||||
"secondary": "#973542",
|
||||
"success": "#5AB1BB",
|
||||
"info": "#4990BA",
|
||||
"warning": "#FF4081",
|
||||
"error": "#EF5350",
|
||||
},
|
||||
}
|
||||
|
||||
return default_theme
|
||||
def default_theme():
|
||||
return SiteTheme().dict()
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
|
@ -62,11 +46,8 @@ def test_default_settings(api_client, default_settings):
|
|||
|
||||
|
||||
def test_update_settings(api_client, default_settings):
|
||||
default_settings["webhooks"]["webhookURLs"] = [
|
||||
"https://test1.url.com",
|
||||
"https://test2.url.com",
|
||||
"https://test3.url.com",
|
||||
]
|
||||
default_settings["language"] = "fr"
|
||||
default_settings["showRecent"] = False
|
||||
|
||||
response = api_client.put(SETTINGS_UPDATE, json=default_settings)
|
||||
|
||||
|
|
|
@ -16,9 +16,7 @@ def default_user():
|
|||
"id": 1,
|
||||
"fullName": "Change Me",
|
||||
"email": "changeme@email.com",
|
||||
"group": {
|
||||
"name": "home"
|
||||
},
|
||||
"group": "home",
|
||||
"admin": True
|
||||
}
|
||||
|
||||
|
@ -29,9 +27,7 @@ def new_user():
|
|||
"id": 2,
|
||||
"fullName": "My New User",
|
||||
"email": "newuser@email.com",
|
||||
"group": {
|
||||
"name": "home"
|
||||
},
|
||||
"group": "home",
|
||||
"admin": False
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue