mirror of
https://github.com/hay-kot/mealie.git
synced 2025-08-22 14:33:33 -07:00
database refactoring
This commit is contained in:
parent
9507514935
commit
db61ac8a31
26 changed files with 234 additions and 113 deletions
|
@ -1,10 +1,15 @@
|
|||
# Release Notes
|
||||
|
||||
## v0.4.0 Whoa, What a Release! [DRAFT]
|
||||
- Authentication! Tons of stuff went into creating a flexible authentication platform for a lot of different use cases. Review the documentation for more information on how to use the authentication, and how everything works together.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
### Features and Improvements
|
||||
- Authentication! Tons of stuff went into creating a flexible authentication platform for a lot of different use cases. Review the documentation for more information on how to use the authentication, and how everything works together. Some key features include
|
||||
- Sign Up Links
|
||||
- Admin and User Roles
|
||||
- Group Management
|
||||
- Create/Edit/Delete Restrictions
|
||||
- Recipe Database Refactoring. Tons of new information is now stored for recipes in the database. Not all is accessible via the UI, but it's coming.
|
||||
- Nutrition Information
|
||||
- calories
|
||||
|
@ -13,9 +18,18 @@
|
|||
- proteinContent
|
||||
- sodiumContent
|
||||
- sugarContent
|
||||
- recipeCuisine
|
||||
- recipeCuisine has been added
|
||||
- "categories" has been migrated to "recipeCategory" to adhear closer to the standard schema
|
||||
- "tool" - a list of tools used for the recipe
|
||||
- Removed CDN dependencies
|
||||
- Completed Redesign of the Admin Panel
|
||||
- Profile Pages
|
||||
- Side Panel Menu
|
||||
- Language selector is now displayed on all pages and does not require an account
|
||||
|
||||
### Development / Misc
|
||||
- Database Model Refactoring
|
||||
- File/Folder Name Refactoring
|
||||
|
||||
## v0.3.0
|
||||
|
||||
|
|
|
@ -63,8 +63,8 @@
|
|||
</v-col>
|
||||
<v-col cols="12" sm="12" md="6">
|
||||
<v-text-field
|
||||
v-model="editedItem.family"
|
||||
label="Family Group"
|
||||
v-model="editedItem.group"
|
||||
label="Group Group"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="12" md="6" v-if="showPassword">
|
||||
|
@ -143,7 +143,7 @@ export default {
|
|||
},
|
||||
{ text: "Full Name", value: "fullName" },
|
||||
{ text: "Email", value: "email" },
|
||||
{ text: "Family", value: "family" },
|
||||
{ text: "Group", value: "group" },
|
||||
{ text: "Admin", value: "admin" },
|
||||
{ text: "", value: "actions", sortable: false, align: "center" },
|
||||
],
|
||||
|
@ -154,7 +154,7 @@ export default {
|
|||
fullName: "",
|
||||
password: "",
|
||||
email: "",
|
||||
family: "",
|
||||
group: "",
|
||||
admin: false,
|
||||
},
|
||||
defaultItem: {
|
||||
|
@ -162,7 +162,7 @@ export default {
|
|||
fullName: "",
|
||||
password: "",
|
||||
email: "",
|
||||
family: "",
|
||||
group: "",
|
||||
admin: false,
|
||||
},
|
||||
}),
|
||||
|
|
|
@ -63,8 +63,8 @@
|
|||
</v-col>
|
||||
<v-col cols="12" sm="12" md="6">
|
||||
<v-text-field
|
||||
v-model="editedItem.family"
|
||||
label="Family Group"
|
||||
v-model="editedItem.group"
|
||||
label="Group Group"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="12" md="6" v-if="showPassword">
|
||||
|
@ -143,7 +143,7 @@ export default {
|
|||
},
|
||||
{ text: "Full Name", value: "fullName" },
|
||||
{ text: "Email", value: "email" },
|
||||
{ text: "Family", value: "family" },
|
||||
{ text: "Group", value: "group" },
|
||||
{ text: "Admin", value: "admin" },
|
||||
{ text: "", value: "actions", sortable: false, align: "center" },
|
||||
],
|
||||
|
@ -154,7 +154,7 @@ export default {
|
|||
fullName: "",
|
||||
password: "",
|
||||
email: "",
|
||||
family: "",
|
||||
group: "",
|
||||
admin: false,
|
||||
},
|
||||
defaultItem: {
|
||||
|
@ -162,7 +162,7 @@ export default {
|
|||
fullName: "",
|
||||
password: "",
|
||||
email: "",
|
||||
family: "",
|
||||
group: "",
|
||||
admin: false,
|
||||
},
|
||||
}),
|
||||
|
|
|
@ -121,7 +121,7 @@ export default {
|
|||
const userData = {
|
||||
fullName: this.user.name,
|
||||
email: this.user.email,
|
||||
family: "public",
|
||||
group: "default",
|
||||
password: this.user.password,
|
||||
admin: false,
|
||||
};
|
||||
|
|
|
@ -55,11 +55,11 @@
|
|||
>
|
||||
</v-text-field>
|
||||
<v-text-field
|
||||
label="Family"
|
||||
label="Group"
|
||||
readonly
|
||||
v-model="user.family"
|
||||
v-model="user.group"
|
||||
persistent-hint
|
||||
hint="Family groups can only be set by administrators"
|
||||
hint="Group groups can only be set by administrators"
|
||||
>
|
||||
</v-text-field>
|
||||
</v-form>
|
||||
|
@ -167,7 +167,7 @@ export default {
|
|||
user: {
|
||||
fullName: "Change Me",
|
||||
email: "changeme@email.com",
|
||||
family: "public",
|
||||
group: "public",
|
||||
admin: true,
|
||||
id: 1,
|
||||
},
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import uvicorn
|
||||
from fastapi import FastAPI
|
||||
from fastapi.logger import logger
|
||||
|
||||
# import utils.startup as startup
|
||||
from core.config import APP_VERSION, PORT, SECRET, docs_url, redoc_url
|
||||
|
@ -13,6 +14,7 @@ from routes import (
|
|||
setting_routes,
|
||||
theme_routes,
|
||||
)
|
||||
from routes.groups import groups
|
||||
from routes.recipe import (
|
||||
all_recipe_routes,
|
||||
category_routes,
|
||||
|
@ -20,7 +22,6 @@ from routes.recipe import (
|
|||
tag_routes,
|
||||
)
|
||||
from routes.users import users
|
||||
from fastapi.logger import logger
|
||||
|
||||
app = FastAPI(
|
||||
title="Mealie",
|
||||
|
@ -42,11 +43,13 @@ def start_scheduler():
|
|||
def api_routers():
|
||||
# Authentication
|
||||
app.include_router(users.router)
|
||||
app.include_router(groups.router)
|
||||
# Recipes
|
||||
app.include_router(all_recipe_routes.router)
|
||||
app.include_router(category_routes.router)
|
||||
app.include_router(tag_routes.router)
|
||||
app.include_router(recipe_crud_routes.router)
|
||||
|
||||
# Meal Routes
|
||||
app.include_router(meal_routes.router)
|
||||
# Settings Routes
|
||||
|
|
|
@ -83,6 +83,7 @@ else:
|
|||
|
||||
# Mongo Database
|
||||
MEALIE_DB_NAME = os.getenv("mealie_db_name", "mealie")
|
||||
DEFAULT_GROUP = os.getenv("default_group", "home")
|
||||
DB_USERNAME = os.getenv("db_username", "root")
|
||||
DB_PASSWORD = os.getenv("db_password", "example")
|
||||
DB_HOST = os.getenv("db_host", "mongo")
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
from schema.user import GroupInDB, UserInDB
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
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
|
||||
|
@ -8,16 +10,12 @@ from db.models.sign_up import SignUp
|
|||
from db.models.theme import SiteThemeModel
|
||||
from db.models.users import User
|
||||
|
||||
"""
|
||||
# TODO
|
||||
- [ ] Abstract Classes to use save_new, and update from base models
|
||||
"""
|
||||
|
||||
|
||||
class _Recipes(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "slug"
|
||||
self.sql_model: RecipeModel = RecipeModel
|
||||
self.orm_mode = False
|
||||
|
||||
def update_image(self, session: Session, slug: str, extension: str = None) -> str:
|
||||
entry: RecipeModel = self._query_one(session, match_value=slug)
|
||||
|
@ -31,36 +29,43 @@ class _Categories(BaseDocument):
|
|||
def __init__(self) -> None:
|
||||
self.primary_key = "slug"
|
||||
self.sql_model = Category
|
||||
self.orm_mode = False
|
||||
|
||||
|
||||
class _Tags(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "slug"
|
||||
self.sql_model = Tag
|
||||
self.orm_mode = False
|
||||
|
||||
|
||||
class _Meals(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "uid"
|
||||
self.sql_model = MealPlanModel
|
||||
self.orm_mode = False
|
||||
|
||||
|
||||
class _Settings(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "name"
|
||||
self.sql_model = SiteSettingsModel
|
||||
self.orm_mode = False
|
||||
|
||||
|
||||
class _Themes(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "name"
|
||||
self.sql_model = SiteThemeModel
|
||||
self.orm_mode = False
|
||||
|
||||
|
||||
class _Users(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "id"
|
||||
self.sql_model = User
|
||||
self.orm_mode = True
|
||||
self.schema = UserInDB
|
||||
|
||||
def update_password(self, session, id, password: str):
|
||||
entry = self._query_one(session=session, match_value=id)
|
||||
|
@ -71,16 +76,19 @@ class _Users(BaseDocument):
|
|||
return return_data
|
||||
|
||||
|
||||
class _SignUps(BaseDocument):
|
||||
class _Groups(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "token"
|
||||
self.sql_model = SignUp
|
||||
self.primary_key = "id"
|
||||
self.sql_model = Group
|
||||
self.orm_mode = True
|
||||
self.schema = GroupInDB
|
||||
|
||||
|
||||
class _SignUps(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "token"
|
||||
self.sql_model = SignUp
|
||||
self.orm_mode = False
|
||||
|
||||
|
||||
class Database:
|
||||
|
@ -93,6 +101,7 @@ class Database:
|
|||
self.tags = _Tags()
|
||||
self.users = _Users()
|
||||
self.sign_ups = _SignUps()
|
||||
self.groups = _Groups()
|
||||
|
||||
|
||||
db = Database()
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from typing import List
|
||||
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy.orm import load_only
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
|
@ -11,11 +12,20 @@ class BaseDocument:
|
|||
self.primary_key: str
|
||||
self.store: str
|
||||
self.sql_model: SqlAlchemyBase
|
||||
self.orm_mode = False
|
||||
self.schema: BaseModel
|
||||
|
||||
# TODO: Improve Get All Query Functionality
|
||||
def get_all(
|
||||
self, session: Session, limit: int = None, order_by: str = None
|
||||
) -> List[dict]:
|
||||
|
||||
if self.orm_mode:
|
||||
return [
|
||||
self.schema.from_orm(x)
|
||||
for x in session.query(self.sql_model).limit(limit).all()
|
||||
]
|
||||
|
||||
list = [x.dict() for x in session.query(self.sql_model).limit(limit).all()]
|
||||
|
||||
if limit == 1:
|
||||
|
@ -105,6 +115,15 @@ class BaseDocument:
|
|||
.limit(limit)
|
||||
.all()
|
||||
)
|
||||
|
||||
if self.orm_mode:
|
||||
if limit == 1:
|
||||
try:
|
||||
return self.schema.from_orm(result[0])
|
||||
except IndexError:
|
||||
return None
|
||||
return [self.schema(x) for x in result]
|
||||
|
||||
db_entries = [x.dict() for x in result]
|
||||
|
||||
if limit == 1:
|
||||
|
@ -128,6 +147,10 @@ class BaseDocument:
|
|||
new_document = self.sql_model(session=session, **document)
|
||||
session.add(new_document)
|
||||
session.commit()
|
||||
|
||||
if self.orm_mode:
|
||||
return self.schema.from_orm(new_document)
|
||||
|
||||
return_data = new_document.dict()
|
||||
return return_data
|
||||
|
||||
|
@ -145,9 +168,13 @@ class BaseDocument:
|
|||
|
||||
entry = self._query_one(session=session, match_value=match_value)
|
||||
entry.update(session=session, **new_data)
|
||||
|
||||
if self.orm_mode:
|
||||
session.commit()
|
||||
return self.schema.from_orm(entry)
|
||||
|
||||
return_data = entry.dict()
|
||||
session.commit()
|
||||
|
||||
return return_data
|
||||
|
||||
def delete(self, session: Session, primary_key_value) -> dict:
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from core.config import DEFAULT_GROUP
|
||||
from core.security import get_password_hash
|
||||
from fastapi.logger import logger
|
||||
from schema.settings import SiteSettings, Webhooks
|
||||
|
@ -12,6 +13,7 @@ def init_db(db: Session = None) -> None:
|
|||
if not db:
|
||||
db = create_session()
|
||||
|
||||
default_group_init(db)
|
||||
default_settings_init(db)
|
||||
default_theme_init(db)
|
||||
default_user_init(db)
|
||||
|
@ -50,12 +52,19 @@ def default_settings_init(session: Session):
|
|||
pass
|
||||
|
||||
|
||||
def default_group_init(session: Session):
|
||||
default_group = {"name": DEFAULT_GROUP}
|
||||
logger.info("Generating Default Group")
|
||||
db.groups.create(session, default_group)
|
||||
pass
|
||||
|
||||
|
||||
def default_user_init(session: Session):
|
||||
default_user = {
|
||||
"full_name": "Change Me",
|
||||
"email": "changeme@email.com",
|
||||
"password": get_password_hash("MyPassword"),
|
||||
"family": "public",
|
||||
"group": DEFAULT_GROUP,
|
||||
"admin": True,
|
||||
}
|
||||
|
||||
|
|
|
@ -4,3 +4,4 @@ from db.models.settings import *
|
|||
from db.models.theme import *
|
||||
from db.models.users import *
|
||||
from db.models.sign_up import *
|
||||
from db.models.group import *
|
||||
|
|
|
@ -1,41 +1,31 @@
|
|||
import sqlalchemy as sa
|
||||
import sqlalchemy.orm as orm
|
||||
from core.config import DEFAULT_GROUP
|
||||
from db.models.model_base import BaseMixins, SqlAlchemyBase
|
||||
from sqlalchemy import Boolean, Column, Integer, String
|
||||
from fastapi.logger import logger
|
||||
from slugify import slugify
|
||||
|
||||
|
||||
class Group(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "groups"
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String, index=True)
|
||||
slug = Column(String, unique=True, index=True)
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
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")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
session,
|
||||
full_name,
|
||||
email,
|
||||
password,
|
||||
family="public",
|
||||
admin=False,
|
||||
) -> None:
|
||||
self.full_name = full_name
|
||||
self.email = email
|
||||
self.family = family
|
||||
self.admin = admin
|
||||
self.password = password
|
||||
|
||||
def dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"full_name": self.full_name,
|
||||
"email": self.email,
|
||||
"admin": self.admin,
|
||||
"family": self.family,
|
||||
"password": self.password,
|
||||
}
|
||||
|
||||
def update(self, full_name, email, family, admin, session=None):
|
||||
self.full_name = full_name
|
||||
self.email = email
|
||||
self.family = family
|
||||
self.admin = admin
|
||||
def __init__(self, name, session=None) -> None:
|
||||
self.name = name
|
||||
|
||||
@staticmethod
|
||||
def create_if_not_exist(session, name: str = DEFAULT_GROUP):
|
||||
try:
|
||||
result = session.query(Group).filter(Group.name == name).one()
|
||||
if result:
|
||||
logger.info("Category exists, associating recipe")
|
||||
return result
|
||||
else:
|
||||
logger.info("Category doesn't exists, creating tag")
|
||||
return Group(name=name)
|
||||
except:
|
||||
logger.info("Category doesn't exists, creating category")
|
||||
return Group(name=name)
|
||||
|
|
|
@ -46,6 +46,8 @@ class MealPlanModel(SqlAlchemyBase, BaseMixins):
|
|||
startDate = sa.Column(sa.Date)
|
||||
endDate = sa.Column(sa.Date)
|
||||
meals: List[Meal] = orm.relation(Meal)
|
||||
group_id = sa.Column(sa.String, sa.ForeignKey("groups.id"))
|
||||
group = orm.relationship("Group", back_populates="mealplans")
|
||||
|
||||
def __init__(self, startDate, endDate, meals, uid=None, session=None) -> None:
|
||||
self.startDate = startDate
|
||||
|
|
|
@ -6,12 +6,12 @@ class Nutrition(SqlAlchemyBase):
|
|||
__tablename__ = "recipe_nutrition"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id"))
|
||||
calories = sa.Column(sa.Integer)
|
||||
fatContent = sa.Column(sa.Integer)
|
||||
fiberContent = sa.Column(sa.Integer)
|
||||
proteinContent = sa.Column(sa.Integer)
|
||||
sodiumContent = sa.Column(sa.Integer)
|
||||
sugarContent = sa.Column(sa.Integer)
|
||||
calories = sa.Column(sa.String)
|
||||
fatContent = sa.Column(sa.String)
|
||||
fiberContent = sa.Column(sa.String)
|
||||
proteinContent = sa.Column(sa.String)
|
||||
sodiumContent = sa.Column(sa.String)
|
||||
sugarContent = sa.Column(sa.String)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
from core.config import DEFAULT_GROUP
|
||||
from db.models.group import Group
|
||||
from db.models.model_base import BaseMixins, SqlAlchemyBase
|
||||
from sqlalchemy import Boolean, Column, Integer, String
|
||||
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, orm
|
||||
|
||||
|
||||
class User(SqlAlchemyBase, BaseMixins):
|
||||
|
@ -8,9 +10,9 @@ class User(SqlAlchemyBase, BaseMixins):
|
|||
full_name = Column(String, index=True)
|
||||
email = Column(String, unique=True, index=True)
|
||||
password = Column(String)
|
||||
is_active = Column(Boolean(), default=True)
|
||||
family = Column(String)
|
||||
admin = Column(Boolean(), default=False)
|
||||
group_id = Column(String, ForeignKey("groups.id"))
|
||||
group = orm.relationship("Group", back_populates="users")
|
||||
admin = Column(Boolean, default=False)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
@ -18,29 +20,21 @@ class User(SqlAlchemyBase, BaseMixins):
|
|||
full_name,
|
||||
email,
|
||||
password,
|
||||
family="public",
|
||||
group: str = DEFAULT_GROUP,
|
||||
admin=False,
|
||||
) -> None:
|
||||
|
||||
group = group if group else DEFAULT_GROUP
|
||||
self.full_name = full_name
|
||||
self.email = email
|
||||
self.family = family
|
||||
self.group = Group.create_if_not_exist(session, group)
|
||||
self.admin = admin
|
||||
self.password = password
|
||||
|
||||
def dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"full_name": self.full_name,
|
||||
"email": self.email,
|
||||
"admin": self.admin,
|
||||
"family": self.family,
|
||||
"password": self.password,
|
||||
}
|
||||
|
||||
def update(self, full_name, email, family, admin, session=None):
|
||||
def update(self, full_name, email, group, admin, session=None):
|
||||
self.full_name = full_name
|
||||
self.email = email
|
||||
self.family = family
|
||||
self.group = Group.create_if_not_exist(session, group)
|
||||
self.admin = admin
|
||||
|
||||
def update_password(self, password):
|
||||
|
|
|
@ -20,5 +20,5 @@ def query_user(user_email: str, session: Session = None) -> UserInDB:
|
|||
session = session if session else create_session()
|
||||
user = db.users.get(session, user_email, "email")
|
||||
session.close()
|
||||
return UserInDB(**user)
|
||||
return user
|
||||
|
||||
|
|
24
mealie/routes/groups/crud.py
Normal file
24
mealie/routes/groups/crud.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
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 routes.deps import manager
|
||||
from schema.snackbar import SnackResponse
|
||||
from schema.user import GroupBase, GroupInDB
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
router = APIRouter(prefix="/api/groups", tags=["Groups"])
|
||||
|
||||
|
||||
@router.get("", response_model=list[GroupInDB])
|
||||
async def get_all_groups(
|
||||
current_user=Depends(manager),
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Returns a list of all groups in the database """
|
||||
return db.groups.get_all(session)
|
7
mealie/routes/groups/groups.py
Normal file
7
mealie/routes/groups/groups.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
from fastapi import APIRouter
|
||||
from routes.groups import crud
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
router.include_router(crud.router)
|
||||
|
|
@ -66,8 +66,8 @@ async def update_user(
|
|||
):
|
||||
|
||||
if current_user.id == id or current_user.admin:
|
||||
updated_user = db.users.update(session, id, new_data.dict())
|
||||
email = updated_user.get("email")
|
||||
updated_user: UserInDB = db.users.update(session, id, new_data.dict())
|
||||
email = updated_user.email
|
||||
if current_user.id == id:
|
||||
access_token = manager.create_access_token(
|
||||
data=dict(sub=email), expires=timedelta(hours=2)
|
||||
|
|
|
@ -15,12 +15,12 @@ class RecipeStep(BaseModel):
|
|||
|
||||
|
||||
class Nutrition(BaseModel):
|
||||
calories: Optional[int]
|
||||
fatContent: Optional[int]
|
||||
fiberContent: Optional[int]
|
||||
proteinContent: Optional[int]
|
||||
sodiumContent: Optional[int]
|
||||
sugarContent: Optional[int]
|
||||
calories: Optional[str]
|
||||
fatContent: Optional[str]
|
||||
fiberContent: Optional[str]
|
||||
proteinContent: Optional[str]
|
||||
sodiumContent: Optional[str]
|
||||
sugarContent: Optional[str]
|
||||
|
||||
|
||||
class Recipe(BaseModel):
|
||||
|
|
|
@ -1,26 +1,35 @@
|
|||
from typing import Optional
|
||||
|
||||
from core.config import DEFAULT_GROUP
|
||||
from fastapi_camelcase import CamelModel
|
||||
|
||||
# from pydantic import EmailStr
|
||||
|
||||
|
||||
class ChangePassword(CamelModel):
|
||||
current_password: str
|
||||
new_password: str
|
||||
|
||||
|
||||
class GroupBase(CamelModel):
|
||||
name: str
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
class UserBase(CamelModel):
|
||||
full_name: Optional[str] = None
|
||||
email: str
|
||||
family: str
|
||||
admin: bool
|
||||
group: Optional[str]
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
class Config:
|
||||
schema_extra = {
|
||||
"fullName": "Change Me",
|
||||
"email": "changeme@email.com",
|
||||
"family": "public",
|
||||
"group": DEFAULT_GROUP,
|
||||
"admin": "false",
|
||||
}
|
||||
|
||||
|
@ -31,7 +40,24 @@ class UserIn(UserBase):
|
|||
|
||||
class UserOut(UserBase):
|
||||
id: int
|
||||
group: GroupBase
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
class UserInDB(UserIn, UserOut):
|
||||
class UserInDB(UserOut):
|
||||
password: str
|
||||
pass
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
class GroupInDB(GroupBase):
|
||||
id: int
|
||||
name: str
|
||||
users: Optional[list[UserOut]]
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
|
|
@ -20,12 +20,12 @@ class RecipeStep(BaseModel):
|
|||
|
||||
|
||||
class Nutrition(BaseModel):
|
||||
calories: Optional[int]
|
||||
fatContent: Optional[int]
|
||||
fiberContent: Optional[int]
|
||||
proteinContent: Optional[int]
|
||||
sodiumContent: Optional[int]
|
||||
sugarContent: Optional[int]
|
||||
calories: Optional[str]
|
||||
fatContent: Optional[str]
|
||||
fiberContent: Optional[str]
|
||||
proteinContent: Optional[str]
|
||||
sodiumContent: Optional[str]
|
||||
sugarContent: Optional[str]
|
||||
|
||||
|
||||
class Recipe(BaseModel):
|
||||
|
|
|
@ -31,6 +31,9 @@ class Cleaner:
|
|||
recipe_data["prepTime"] = Cleaner.time(recipe_data.get("prepTime", None))
|
||||
recipe_data["performTime"] = Cleaner.time(recipe_data.get("performTime", None))
|
||||
recipe_data["totalTime"] = Cleaner.time(recipe_data.get("totalTime", None))
|
||||
recipe_data["recipeCategory"] = Cleaner.category(
|
||||
recipe_data.get("recipeCategory", [])
|
||||
)
|
||||
|
||||
recipe_data["recipeYield"] = Cleaner.yield_amount(
|
||||
recipe_data.get("recipeYield")
|
||||
|
@ -47,6 +50,13 @@ class Cleaner:
|
|||
|
||||
return recipe_data
|
||||
|
||||
@staticmethod
|
||||
def category(category: str):
|
||||
if type(category) == type(str):
|
||||
return [category]
|
||||
else:
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def html(raw_html):
|
||||
cleanr = re.compile("<.*?>")
|
||||
|
|
|
@ -34,7 +34,7 @@ def api_client():
|
|||
|
||||
yield TestClient(app)
|
||||
|
||||
SQLITE_FILE.unlink()
|
||||
# SQLITE_FILE.unlink()
|
||||
|
||||
|
||||
@fixture(scope="session")
|
||||
|
|
|
@ -63,7 +63,7 @@ def test_read_update(api_client, recipe_data):
|
|||
recipe["notes"] = test_notes
|
||||
|
||||
test_categories = ["one", "two", "three"]
|
||||
recipe["categories"] = test_categories
|
||||
recipe["recipeCategory"] = test_categories
|
||||
|
||||
response = api_client.put(
|
||||
f"{RECIPES_PREFIX}/{recipe_data.expected_slug}", json=recipe
|
||||
|
@ -77,7 +77,7 @@ def test_read_update(api_client, recipe_data):
|
|||
recipe = json.loads(response.content)
|
||||
|
||||
assert recipe["notes"] == test_notes
|
||||
assert recipe["categories"].sort() == test_categories.sort()
|
||||
assert recipe["recipeCategory"].sort() == test_categories.sort()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("recipe_data", recipe_test_data)
|
||||
|
|
|
@ -16,7 +16,9 @@ def default_user():
|
|||
"id": 1,
|
||||
"fullName": "Change Me",
|
||||
"email": "changeme@email.com",
|
||||
"family": "public",
|
||||
"group": {
|
||||
"name": "home"
|
||||
},
|
||||
"admin": True
|
||||
}
|
||||
|
||||
|
@ -27,7 +29,9 @@ def new_user():
|
|||
"id": 2,
|
||||
"fullName": "My New User",
|
||||
"email": "newuser@email.com",
|
||||
"family": "public",
|
||||
"group": {
|
||||
"name": "home"
|
||||
},
|
||||
"admin": False
|
||||
}
|
||||
|
||||
|
@ -54,7 +58,7 @@ def test_create_user(api_client: requests, token, new_user):
|
|||
"fullName": "My New User",
|
||||
"email": "newuser@email.com",
|
||||
"password": "MyStrongPassword",
|
||||
"family": "public",
|
||||
"group": "home",
|
||||
"admin": False
|
||||
}
|
||||
|
||||
|
@ -78,7 +82,7 @@ def test_update_user(api_client: requests, token):
|
|||
"id": 1,
|
||||
"fullName": "Updated Name",
|
||||
"email": "updated@email.com",
|
||||
"family": "public",
|
||||
"group": "home",
|
||||
"admin": True
|
||||
}
|
||||
response = api_client.put(f"{BASE}/1", headers=token, json=update_data)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue