continued model refactor

This commit is contained in:
hay-kot 2021-03-08 19:23:41 -09:00
commit 1ac2d7a4ce
17 changed files with 198 additions and 139 deletions

View 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;
},
};

View file

@ -9,6 +9,7 @@ import category from "./category";
import meta from "./meta"; import meta from "./meta";
import users from "./users"; import users from "./users";
import signUps from "./signUps"; import signUps from "./signUps";
import groups from "./groups";
export default { export default {
recipes: recipe, recipes: recipe,
@ -22,4 +23,5 @@ export default {
meta: meta, meta: meta,
users: users, users: users,
signUps: signUps, signUps: signUps,
groups: groups,
}; };

View file

@ -19,6 +19,10 @@
User Groups User Groups
</v-toolbar-title> </v-toolbar-title>
<div>
<v-select class="mt-7 ml-5" dense flat solo> </v-select>
</div>
<v-spacer> </v-spacer> <v-spacer> </v-spacer>
<v-dialog v-model="dialog" max-width="600px"> <v-dialog v-model="dialog" max-width="600px">
<template v-slot:activator="{ on, attrs }"> <template v-slot:activator="{ on, attrs }">
@ -136,7 +140,7 @@ export default {
activeName: null, activeName: null,
headers: [ headers: [
{ {
text: "User ID", text: "User IDs",
align: "start", align: "start",
sortable: false, sortable: false,
value: "id", value: "id",

View file

@ -26,7 +26,7 @@
</v-tab> </v-tab>
</v-tabs> </v-tabs>
<v-tabs-items v-model="tab" > <v-tabs-items v-model="tab">
<v-tab-item> <v-tab-item>
<TheUserTable /> <TheUserTable />
</v-tab-item> </v-tab-item>

View file

@ -1,7 +1,9 @@
from schema.category import RecipeCategoryResponse
from schema.meal import MealPlanInDB from schema.meal import MealPlanInDB
from schema.recipe import Recipe from schema.recipe import Recipe
from schema.settings import SiteSettings as SiteSettingsSchema from schema.settings import SiteSettings as SiteSettingsSchema
from schema.sign_up import SignUpOut from schema.sign_up import SignUpOut
from schema.theme import SiteTheme
from schema.user import GroupInDB, UserInDB from schema.user import GroupInDB, UserInDB
from sqlalchemy.orm.session import Session from sqlalchemy.orm.session import Session
@ -34,7 +36,8 @@ class _Categories(BaseDocument):
def __init__(self) -> None: def __init__(self) -> None:
self.primary_key = "slug" self.primary_key = "slug"
self.sql_model = Category self.sql_model = Category
self.orm_mode = False self.orm_mode = True
self.schema = RecipeCategoryResponse
class _Tags(BaseDocument): class _Tags(BaseDocument):
@ -64,7 +67,8 @@ class _Themes(BaseDocument):
def __init__(self) -> None: def __init__(self) -> None:
self.primary_key = "name" self.primary_key = "name"
self.sql_model = SiteThemeModel self.sql_model = SiteThemeModel
self.orm_mode = False self.orm_mode = True
self.schema = SiteTheme
class _Users(BaseDocument): class _Users(BaseDocument):

View file

@ -2,6 +2,7 @@ from core.config import DEFAULT_GROUP
from core.security import get_password_hash from core.security import get_password_hash
from fastapi.logger import logger from fastapi.logger import logger
from schema.settings import SiteSettings from schema.settings import SiteSettings
from schema.theme import SiteTheme
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from sqlalchemy.orm.session 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): def default_theme_init(session: Session):
default_theme = { db.themes.create(session, SiteTheme().dict())
"name": "default",
"colors": {
"primary": "#E58325",
"accent": "#00457A",
"secondary": "#973542",
"success": "#5AB1BB",
"info": "#4990BA",
"warning": "#FF4081",
"error": "#EF5350",
},
}
try: try:
db.themes.create(session, default_theme)
logger.info("Generating default theme...") logger.info("Generating default theme...")
except: except:
logger.info("Default Theme Exists.. skipping generation") logger.info("Default Theme Exists.. skipping generation")
def default_settings_init(session: Session): def default_settings_init(session: Session):
data = {"language": "en", "sidebar": {"categories": []}} data = {"language": "en", "home_page_settings": {"categories": []}}
document = db.settings.create(session, data) document = db.settings.create(session, SiteSettings().dict())
logger.info(f"Created Site Settings: \n {document}") logger.info(f"Created Site Settings: \n {document}")

View file

@ -1,13 +1,14 @@
from typing import List from typing import List
import sqlalchemy.ext.declarative as dec import sqlalchemy.ext.declarative as dec
from sqlalchemy.orm.session import Session
SqlAlchemyBase = dec.declarative_base() SqlAlchemyBase = dec.declarative_base()
class BaseMixins: class BaseMixins:
@staticmethod @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: for table in list_of_tables:
session.query(table).filter(parent_id == parent_id).delete() session.query(table).filter(parent_id == parent_id).delete()
@ -19,3 +20,29 @@ class BaseMixins:
finalMap.update(d.dict()) finalMap.update(d.dict())
return finalMap 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

View file

@ -5,10 +5,10 @@ from fastapi.logger import logger
from slugify import slugify from slugify import slugify
from sqlalchemy.orm import validates from sqlalchemy.orm import validates
sidebar2categories = sa.Table( site_settings2categories = sa.Table(
"sidebar2categories", "site_settings2categoories",
SqlAlchemyBase.metadata, 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")), sa.Column("category_slug", sa.String, sa.ForeignKey("categories.slug")),
) )

View file

@ -182,30 +182,3 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
orgURL=orgURL, orgURL=orgURL,
extras=extras, 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

View file

@ -1,42 +1,32 @@
import sqlalchemy as sa import sqlalchemy as sa
import sqlalchemy.orm as orm import sqlalchemy.orm as orm
from db.models.model_base import BaseMixins, SqlAlchemyBase 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 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): class SiteSettings(SqlAlchemyBase, BaseMixins):
__tablename__ = "site_settings" __tablename__ = "site_settings"
id = sa.Column(sa.Integer, primary_key=True) id = sa.Column(sa.Integer, primary_key=True)
language = sa.Column(sa.String) 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__( def __init__(
self, session=None, language="en", sidebar: list = {"categories": []} self, session: Session = None, language="en", categories: list = [], show_recent=True
) -> None: ) -> None:
self._sql_remove_list(session, [Sidebar], self.id) session.commit()
self.language = language self.language = language
self.sidebar = Sidebar(session, sidebar)
def update(self, session, language, sidebar): self.show_recent = show_recent
self.__init__(session=session, language=language, sidebar=sidebar) 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)

View file

@ -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.database import db
from db.db_setup import generate_session from db.db_setup import generate_session
from fastapi import APIRouter, Depends, File, UploadFile from fastapi import APIRouter, Depends
from fastapi.responses import FileResponse
from routes.deps import manager from routes.deps import manager
from schema.snackbar import SnackResponse from schema.snackbar import SnackResponse
from schema.user import GroupBase, GroupInDB from schema.user import GroupBase, GroupInDB
@ -21,4 +15,52 @@ async def get_all_groups(
session: Session = Depends(generate_session), session: Session = Depends(generate_session),
): ):
""" Returns a list of all groups in the database """ """ Returns a list of all groups in the database """
return db.groups.get_all(session) 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

View file

@ -29,9 +29,11 @@ class MealPlanBase(BaseModel):
raise ValueError("EndDate should be greater than StartDate") raise ValueError("EndDate should be greater than StartDate")
return v return v
class MealPlanProcessed(MealPlanBase): class MealPlanProcessed(MealPlanBase):
meals: list[MealOut] meals: list[MealOut]
class MealPlanInDB(MealPlanProcessed): class MealPlanInDB(MealPlanProcessed):
uid: str uid: str
@ -39,7 +41,6 @@ class MealPlanInDB(MealPlanProcessed):
orm_mode = True orm_mode = True
class MealPlan(BaseModel): class MealPlan(BaseModel):
uid: Optional[str] uid: Optional[str]

View file

@ -5,20 +5,22 @@ from fastapi_camelcase import CamelModel
from schema.category import CategoryBase from schema.category import CategoryBase
class Sidebar(CamelModel):
categories: Optional[list[CategoryBase]]
class Config:
orm_mode = True
class SiteSettings(CamelModel): class SiteSettings(CamelModel):
language: str language: str = "en"
sidebar: Sidebar show_recent: bool = True
categories: Optional[list[CategoryBase]] = []
class Config: class Config:
orm_mode = True orm_mode = True
schema_extra = { 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"},
],
}
} }

View file

@ -1,20 +1,25 @@
from pydantic import BaseModel from pydantic import BaseModel
class Colors(BaseModel): class Colors(BaseModel):
primary: str primary: str = "#E58325"
accent: str accent: str = "#00457A"
secondary: str secondary: str = "#973542"
success: str success: str = "#5AB1BB"
info: str info: str = "#4990BA"
warning: str warning: str = "#FF4081"
error: str error: str = "#EF5350"
class Config:
orm_mode = True
class SiteTheme(BaseModel): class SiteTheme(BaseModel):
name: str name: str = "default"
colors: Colors colors: Colors = Colors()
class Config: class Config:
orm_mode = True
schema_extra = { schema_extra = {
"example": { "example": {
"name": "default", "name": "default",

View file

@ -1,10 +1,14 @@
from typing import Optional from typing import Any, Optional
from core.config import DEFAULT_GROUP from core.config import DEFAULT_GROUP
from db.models.group import WebHookModel
from db.models.users import User from db.models.users import User
from fastapi_camelcase import CamelModel from fastapi_camelcase import CamelModel
from pydantic.utils import GetterDict from pydantic.utils import GetterDict
from schema.category import CategoryBase
from schema.meal import MealPlanInDB
class ChangePassword(CamelModel): class ChangePassword(CamelModel):
current_password: str current_password: str
@ -27,7 +31,13 @@ class UserBase(CamelModel):
class Config: class Config:
orm_mode = True orm_mode = True
class Config: @classmethod
def getter_dict(_cls, name_orm: User):
return {
**GetterDict(name_orm),
"group": name_orm.group.name,
}
schema_extra = { schema_extra = {
"fullName": "Change Me", "fullName": "Change Me",
"email": "changeme@email.com", "email": "changeme@email.com",
@ -63,10 +73,29 @@ class UserInDB(UserOut):
orm_mode = True 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): class GroupInDB(GroupBase):
id: int id: int
name: str name: str
users: Optional[list[UserOut]] users: Optional[list[UserOut]]
mealplans: Optional[list[MealPlanInDB]]
categories: Optional[list[CategoryBase]]
webhooks: Optional[Webhooks]
class Config: class Config:
orm_mode = True orm_mode = True

View file

@ -1,4 +1,6 @@
import json import json
from schema.settings import SiteSettings
from schema.theme import SiteTheme
import pytest import pytest
from tests.utils.routes import ( from tests.utils.routes import (
@ -11,30 +13,12 @@ from tests.utils.routes import (
@pytest.fixture(scope="function") @pytest.fixture(scope="function")
def default_settings(): def default_settings():
return { return SiteSettings().dict(by_alias=True)
"name": "main",
"planCategories": [],
"webhooks": {"webhookTime": "00:00", "webhookURLs": [], "enabled": False},
}
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def default_theme(api_client): def default_theme():
return SiteTheme().dict()
default_theme = {
"name": "default",
"colors": {
"primary": "#E58325",
"accent": "#00457A",
"secondary": "#973542",
"success": "#5AB1BB",
"info": "#4990BA",
"warning": "#FF4081",
"error": "#EF5350",
},
}
return default_theme
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
@ -62,11 +46,8 @@ def test_default_settings(api_client, default_settings):
def test_update_settings(api_client, default_settings): def test_update_settings(api_client, default_settings):
default_settings["webhooks"]["webhookURLs"] = [ default_settings["language"] = "fr"
"https://test1.url.com", default_settings["showRecent"] = False
"https://test2.url.com",
"https://test3.url.com",
]
response = api_client.put(SETTINGS_UPDATE, json=default_settings) response = api_client.put(SETTINGS_UPDATE, json=default_settings)

View file

@ -16,9 +16,7 @@ def default_user():
"id": 1, "id": 1,
"fullName": "Change Me", "fullName": "Change Me",
"email": "changeme@email.com", "email": "changeme@email.com",
"group": { "group": "home",
"name": "home"
},
"admin": True "admin": True
} }
@ -29,9 +27,7 @@ def new_user():
"id": 2, "id": 2,
"fullName": "My New User", "fullName": "My New User",
"email": "newuser@email.com", "email": "newuser@email.com",
"group": { "group": "home",
"name": "home"
},
"admin": False "admin": False
} }