+
-
+
@@ -15,15 +15,21 @@
- {{$t('general.sort')}}
+ {{
+ $t("general.sort")
+ }}
- {{$t('general.recent')}}
+ {{
+ $t("general.recent")
+ }}
- {{$t('general.sort-alphabetically')}}
+ {{
+ $t("general.sort-alphabetically")
+ }}
@@ -31,44 +37,45 @@
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
@@ -84,10 +91,12 @@ export default {
sortable: {
default: false,
},
- title: String,
+ title: {
+ default: null,
+ },
recipes: Array,
cardLimit: {
- default: 6,
+ default: 999,
},
},
computed: {
diff --git a/frontend/src/components/UI/CategorySidebar.vue b/frontend/src/components/UI/CategorySidebar.vue
index 8c8337bc1..390a03c56 100644
--- a/frontend/src/components/UI/CategorySidebar.vue
+++ b/frontend/src/components/UI/CategorySidebar.vue
@@ -33,6 +33,7 @@
diff --git a/frontend/src/pages/HomePage.vue b/frontend/src/pages/HomePage.vue
index ffe054190..322c612d5 100644
--- a/frontend/src/pages/HomePage.vue
+++ b/frontend/src/pages/HomePage.vue
@@ -2,10 +2,10 @@
@@ -35,14 +35,9 @@ export default {
};
},
computed: {
- showRecent() {
- return this.$store.getters.getShowRecent;
- },
- showLimit() {
- return this.$store.getters.getShowLimit;
- },
- homeCategories() {
- return this.$store.getters.getHomeCategories;
+ siteSettings() {
+ console.log(this.$store.getters.getSiteSettings);
+ return this.$store.getters.getSiteSettings;
},
recentRecipes() {
let recipes = this.$store.getters.getRecentRecipes;
@@ -55,9 +50,11 @@ export default {
},
methods: {
async buildPage() {
- this.homeCategories.forEach(async element => {
+ await this.$store.dispatch("requestSiteSettings");
+ this.siteSettings.categories.forEach(async element => {
let recipes = await this.getRecipeByCategory(element.slug);
- recipes.position = element.position;
+ if (recipes.recipes.length < 0 ) recipes.recipes = []
+ console.log(recipes)
this.recipeByCategory.push(recipes);
});
},
diff --git a/frontend/src/pages/Recipes/CustomPage.vue b/frontend/src/pages/Recipes/CustomPage.vue
new file mode 100644
index 000000000..45fe6993a
--- /dev/null
+++ b/frontend/src/pages/Recipes/CustomPage.vue
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
+ {{ title.toUpperCase() }}
+
+
+
+
+
+
+
+ {{ item.name }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/routes/index.js b/frontend/src/routes/index.js
index df94b1512..73cd7bcfb 100644
--- a/frontend/src/routes/index.js
+++ b/frontend/src/routes/index.js
@@ -3,6 +3,7 @@ import Page404 from "@/pages/404Page";
import SearchPage from "@/pages/SearchPage";
import ViewRecipe from "@/pages/Recipe/ViewRecipe";
import NewRecipe from "@/pages/Recipe/NewRecipe";
+import CustomPage from "@/pages/Recipes/CustomPage";
import AllRecipes from "@/pages/Recipes/AllRecipes";
import CategoryPage from "@/pages/Recipes/CategoryPage";
import Planner from "@/pages/MealPlan/Planner";
@@ -31,6 +32,7 @@ export const routes = [
{ path: "/debug", component: Debug },
{ path: "/search", component: SearchPage },
{ path: "/recipes/all", component: AllRecipes },
+ { path: "/pages/:customPage", component: CustomPage },
{ path: "/recipes/:category", component: CategoryPage },
{ path: "/recipe/:recipe", component: ViewRecipe },
{ path: "/new/", component: NewRecipe },
diff --git a/frontend/src/store/index.js b/frontend/src/store/index.js
index b6bbc277c..3ee9a449a 100644
--- a/frontend/src/store/index.js
+++ b/frontend/src/store/index.js
@@ -4,7 +4,6 @@ import api from "@/api";
import createPersistedState from "vuex-persistedstate";
import userSettings from "./modules/userSettings";
import language from "./modules/language";
-import homePage from "./modules/homePage";
import siteSettings from "./modules/siteSettings";
import groups from "./modules/groups";
@@ -13,13 +12,12 @@ Vue.use(Vuex);
const store = new Vuex.Store({
plugins: [
createPersistedState({
- paths: ["userSettings", "language", "homePage", "SideSettings"],
+ paths: ["userSettings", "language", "SideSettings"],
}),
],
modules: {
userSettings,
language,
- homePage,
siteSettings,
groups,
},
@@ -28,6 +26,7 @@ const store = new Vuex.Store({
recentRecipes: [],
allRecipes: [],
mealPlanCategories: [],
+ allCategories: [],
},
mutations: {
@@ -38,6 +37,9 @@ const store = new Vuex.Store({
setMealPlanCategories(state, payload) {
state.mealPlanCategories = payload;
},
+ setAllCategories(state, payload) {
+ state.allCategories = payload;
+ },
},
actions: {
@@ -54,11 +56,16 @@ const store = new Vuex.Store({
this.commit("setRecentRecipes", payload);
},
+ async requestCategories({ commit }) {
+ const categories = await api.categories.getAll();
+ commit("setAllCategories", categories);
+ },
},
getters: {
getRecentRecipes: state => state.recentRecipes,
getMealPlanCategories: state => state.mealPlanCategories,
+ getAllCategories: state => state.allCategories,
},
});
diff --git a/frontend/src/store/modules/siteSettings.js b/frontend/src/store/modules/siteSettings.js
index d8357fa9b..1495b4c72 100644
--- a/frontend/src/store/modules/siteSettings.js
+++ b/frontend/src/store/modules/siteSettings.js
@@ -11,7 +11,7 @@ const state = {
const mutations = {
setSettings(state, payload) {
- state.settings = payload;
+ state.siteSettings = payload;
},
};
diff --git a/mealie/app.py b/mealie/app.py
index f4faeafd1..89ee63842 100644
--- a/mealie/app.py
+++ b/mealie/app.py
@@ -4,7 +4,8 @@ from fastapi.logger import logger
# import utils.startup as startup
from mealie.core.config import APP_VERSION, PORT, docs_url, redoc_url
-from mealie.routes import backup_routes, debug_routes, migration_routes, setting_routes, theme_routes
+from mealie.routes import backup_routes, debug_routes, migration_routes, theme_routes
+from mealie.routes.site_settings import all_settings
from mealie.routes.groups import groups
from mealie.routes.mealplans import mealplans
from mealie.routes.recipe import all_recipe_routes, category_routes, recipe_crud_routes, tag_routes
@@ -36,7 +37,7 @@ def api_routers():
# Meal Routes
app.include_router(mealplans.router)
# Settings Routes
- app.include_router(setting_routes.router)
+ app.include_router(all_settings.router)
app.include_router(theme_routes.router)
# Backups/Imports Routes
app.include_router(backup_routes.router)
diff --git a/mealie/core/config.py b/mealie/core/config.py
index 1f99bc0e5..fdaaa6e77 100644
--- a/mealie/core/config.py
+++ b/mealie/core/config.py
@@ -1,4 +1,5 @@
import os
+import secrets
from pathlib import Path
import dotenv
@@ -17,12 +18,10 @@ def ensure_dirs():
# Register ENV
ENV = CWD.joinpath(".env") #! I'm Broken Fix Me!
dotenv.load_dotenv(ENV)
+PRODUCTION = os.environ.get("ENV")
-SECRET = "test-secret-shhh"
-
# General
-PRODUCTION = os.environ.get("ENV")
PORT = int(os.getenv("mealie_port", 9000))
API = os.getenv("api_docs", True)
@@ -72,7 +71,7 @@ LOGGER_FILE = DATA_DIR.joinpath("mealie.log")
# DATABASE ENV
SQLITE_FILE = None
-DATABASE_TYPE = os.getenv("db_type", "sqlite")
+DATABASE_TYPE = os.getenv("DB_TYPE", "sqlite")
if DATABASE_TYPE == "sqlite":
USE_SQL = True
SQLITE_FILE = SQLITE_DIR.joinpath(f"mealie_{DB_VERSION}.sqlite")
@@ -80,9 +79,28 @@ if DATABASE_TYPE == "sqlite":
else:
raise Exception("Unable to determine database type. Acceptible options are 'sqlite' ")
+
+def determine_secrets() -> str:
+ if not PRODUCTION:
+ return "shh-secret-test-key"
+
+ secrets_file = DATA_DIR.joinpath(".secret")
+ if secrets_file.is_file():
+ with open(secrets_file, "r") as f:
+ return f.read()
+ else:
+ with open(secrets_file, "w") as f:
+ f.write(secrets.token_hex(32))
+
+
+SECRET = "determine_secrets()"
+
# Mongo Database
+DEFAULT_GROUP = os.getenv("DEFAULT_GROUP", "Home")
+DEFAULT_PASSWORD = os.getenv("DEFAULT_PASSWORD", "MyPassword")
+
+# 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")
diff --git a/mealie/core/security.py b/mealie/core/security.py
index 2091e024e..c4380cb25 100644
--- a/mealie/core/security.py
+++ b/mealie/core/security.py
@@ -17,8 +17,7 @@ def create_access_token(data: dict(), expires_delta: timedelta = None) -> str:
else:
expire = datetime.utcnow() + timedelta(minutes=120)
to_encode.update({"exp": expire})
- encoded_jwt = jwt.encode(to_encode, SECRET, algorithm=ALGORITHM)
- return encoded_jwt
+ return jwt.encode(to_encode, SECRET, algorithm=ALGORITHM)
def authenticate_user(session, email: str, password: str) -> UserInDB:
diff --git a/mealie/db/database.py b/mealie/db/database.py
index a519ed309..5a1a66343 100644
--- a/mealie/db/database.py
+++ b/mealie/db/database.py
@@ -2,14 +2,14 @@ from mealie.db.db_base import BaseDocument
from mealie.db.models.group import Group
from mealie.db.models.mealplan import MealPlanModel
from mealie.db.models.recipe.recipe import Category, RecipeModel, Tag
-from mealie.db.models.settings import SiteSettings
+from mealie.db.models.settings import CustomPage, SiteSettings
from mealie.db.models.sign_up import SignUp
from mealie.db.models.theme import SiteThemeModel
from mealie.db.models.users import User
from mealie.schema.category import RecipeCategoryResponse, RecipeTagResponse
from mealie.schema.meal import MealPlanInDB
from mealie.schema.recipe import Recipe
-from mealie.schema.settings import SiteSettings as SiteSettingsSchema
+from mealie.schema.settings import CustomPageOut, SiteSettings as SiteSettingsSchema
from mealie.schema.sign_up import SignUpOut
from mealie.schema.theme import SiteTheme
from mealie.schema.user import GroupInDB, UserInDB
@@ -118,6 +118,13 @@ class _SignUps(BaseDocument):
self.orm_mode = True
self.schema = SignUpOut
+class _CustomPages(BaseDocument):
+ def __init__(self) -> None:
+ self.primary_key = "id"
+ self.sql_model = CustomPage
+ self.orm_mode = True
+ self.schema = CustomPageOut
+
class Database:
def __init__(self) -> None:
@@ -130,6 +137,7 @@ class Database:
self.users = _Users()
self.sign_ups = _SignUps()
self.groups = _Groups()
+ self.custom_pages = _CustomPages()
db = Database()
diff --git a/mealie/db/db_base.py b/mealie/db/db_base.py
index 68c06dbeb..c1a302f49 100644
--- a/mealie/db/db_base.py
+++ b/mealie/db/db_base.py
@@ -40,9 +40,12 @@ class BaseDocument:
Returns:
list[SqlAlchemyBase]: Returns a list of ORM objects
"""
- results = session.query(self.sql_model).options(load_only(*fields)).limit(limit).all()
-
- return results
+ return (
+ session.query(self.sql_model)
+ .options(load_only(*fields))
+ .limit(limit)
+ .all()
+ )
def get_all_primary_keys(self, session: Session) -> List[str]:
"""Queries the database of the selected model and returns a list
@@ -69,12 +72,14 @@ class BaseDocument:
Returns:
Union[Session, SqlAlchemyBase]: Will return both the session and found model
"""
- if match_key == None:
+ if match_key is None:
match_key = self.primary_key
- result = session.query(self.sql_model).filter_by(**{match_key: match_value}).one()
-
- return result
+ return (
+ session.query(self.sql_model)
+ .filter_by(**{match_key: match_value})
+ .one()
+ )
def get(self, session: Session, match_value: str, match_key: str = None, limit=1) -> BaseModel or List[BaseModel]:
"""Retrieves an entry from the database by matching a key/value pair. If no
@@ -89,7 +94,7 @@ class BaseDocument:
Returns:
dict or list[dict]:
"""
- if match_key == None:
+ if match_key is None:
match_key = self.primary_key
result = session.query(self.sql_model).filter_by(**{match_key: match_value}).limit(limit).all()
@@ -118,8 +123,7 @@ class BaseDocument:
if self.orm_mode:
return self.schema.from_orm(new_document)
- return_data = new_document.dict()
- return return_data
+ return new_document.dict()
def update(self, session: Session, match_value: str, new_data: str) -> BaseModel:
"""Update a database entry.
diff --git a/mealie/db/init_db.py b/mealie/db/init_db.py
index 6108d60fd..a1737c622 100644
--- a/mealie/db/init_db.py
+++ b/mealie/db/init_db.py
@@ -1,5 +1,5 @@
from fastapi.logger import logger
-from mealie.core.config import DEFAULT_GROUP
+from mealie.core.config import DEFAULT_GROUP, DEFAULT_PASSWORD
from mealie.core.security import get_password_hash
from mealie.db.database import db
from mealie.db.db_setup import create_session, sql_exists
@@ -40,14 +40,13 @@ 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"),
+ "password": get_password_hash(DEFAULT_PASSWORD),
"group": DEFAULT_GROUP,
"admin": True,
}
@@ -62,4 +61,4 @@ if __name__ == "__main__":
exit()
else:
print("Database Doesn't Exists, Initializing...")
- init_db()
\ No newline at end of file
+ init_db()
diff --git a/mealie/db/models/group.py b/mealie/db/models/group.py
index 07b7763f5..2f5760ba1 100644
--- a/mealie/db/models/group.py
+++ b/mealie/db/models/group.py
@@ -57,12 +57,10 @@ class Group(SqlAlchemyBase, BaseMixins):
@staticmethod
def get_ref(session: Session, name: str):
- item = session.query(Group).filter(Group.name == name).one()
- if item:
- return item
-
- else:
- return session.query(Group).filter(Group.id == 1).one()
+ item = session.query(Group).filter(Group.name == name).one_or_none()
+ if item is None:
+ item = session.query(Group).filter(Group.id == 1).one()
+ return item
@staticmethod
def create_if_not_exist(session, name: str = DEFAULT_GROUP):
diff --git a/mealie/db/models/recipe/category.py b/mealie/db/models/recipe/category.py
index e954bc02b..a4ee7b0b3 100644
--- a/mealie/db/models/recipe/category.py
+++ b/mealie/db/models/recipe/category.py
@@ -26,6 +26,13 @@ recipes2categories = sa.Table(
sa.Column("category_slug", sa.String, sa.ForeignKey("categories.slug")),
)
+custom_pages2categories = sa.Table(
+ "custom_pages2categories",
+ SqlAlchemyBase.metadata,
+ sa.Column("custom_page_id", sa.Integer, sa.ForeignKey("custom_pages.id")),
+ sa.Column("category_slug", sa.String, sa.ForeignKey("categories.slug")),
+)
+
class Category(SqlAlchemyBase):
__tablename__ = "categories"
@@ -36,7 +43,7 @@ class Category(SqlAlchemyBase):
@validates("name")
def validate_name(self, key, name):
- assert not name == ""
+ assert name != ""
return name
def __init__(self, name) -> None:
diff --git a/mealie/db/models/settings.py b/mealie/db/models/settings.py
index b26e80ed1..cc40dd787 100644
--- a/mealie/db/models/settings.py
+++ b/mealie/db/models/settings.py
@@ -1,7 +1,7 @@
import sqlalchemy as sa
import sqlalchemy.orm as orm
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
-from mealie.db.models.recipe.category import Category, site_settings2categories
+from mealie.db.models.recipe.category import Category, custom_pages2categories, site_settings2categories
from sqlalchemy.orm import Session
@@ -29,7 +29,29 @@ class SiteSettings(SqlAlchemyBase, BaseMixins):
self.language = language
self.cards_per_section = cards_per_section
self.show_recent = show_recent
- self.categories = [Category.get_ref(session=session, name=cat.get("slug")) for cat in categories]
+ self.categories = [Category.get_ref(session=session, slug=cat.get("slug")) for cat in categories]
+
+ def update(self, *args, **kwarg):
+ self.__init__(*args, **kwarg)
+
+
+class CustomPage(SqlAlchemyBase, BaseMixins):
+ __tablename__ = "custom_pages"
+ id = sa.Column(sa.Integer, primary_key=True)
+ position = sa.Column(sa.Integer, nullable=False)
+ name = sa.Column(sa.String, nullable=False)
+ slug = sa.Column(sa.String, nullable=False)
+ categories = orm.relationship(
+ "Category",
+ secondary=custom_pages2categories,
+ single_parent=True,
+ )
+
+ def __init__(self, session=None, name=None, slug=None, position=0, categories=[], *args, **kwargs) -> None:
+ self.name = name
+ self.slug = slug
+ self.position = position
+ self.categories = [Category.get_ref(session=session, slug=cat.get("slug")) for cat in categories]
def update(self, *args, **kwarg):
self.__init__(*args, **kwarg)
diff --git a/mealie/routes/backup_routes.py b/mealie/routes/backup_routes.py
index 17aa937d2..8c186a737 100644
--- a/mealie/routes/backup_routes.py
+++ b/mealie/routes/backup_routes.py
@@ -18,14 +18,11 @@ router = APIRouter(prefix="/api/backups", tags=["Backups"])
def available_imports():
"""Returns a list of avaiable .zip files for import into Mealie."""
imports = []
- templates = []
for archive in BACKUP_DIR.glob("*.zip"):
backup = LocalBackup(name=archive.name, date=archive.stat().st_ctime)
imports.append(backup)
- for template in TEMPLATE_DIR.glob("*.*"):
- templates.append(template.name)
-
+ templates = [template.name for template in TEMPLATE_DIR.glob("*.*")]
imports.sort(key=operator.attrgetter("date"), reverse=True)
return Imports(imports=imports, templates=templates)
@@ -40,6 +37,7 @@ def export_database(data: BackupJob, session: Session = Depends(generate_session
templates=data.templates,
export_recipes=data.options.recipes,
export_settings=data.options.settings,
+ export_pages=data.options.pages,
export_themes=data.options.themes,
export_users=data.options.users,
export_groups=data.options.groups,
@@ -82,11 +80,12 @@ async def upload_nextcloud_zipfile(file_name: str):
def import_database(file_name: str, import_data: ImportJob, session: Session = Depends(generate_session)):
""" Import a database backup file generated from Mealie. """
- imported = imports.import_database(
+ return imports.import_database(
session=session,
archive=import_data.name,
import_recipes=import_data.recipes,
import_settings=import_data.settings,
+ import_pages=import_data.pages,
import_themes=import_data.themes,
import_users=import_data.users,
import_groups=import_data.groups,
@@ -94,8 +93,6 @@ def import_database(file_name: str, import_data: ImportJob, session: Session = D
rebase=import_data.rebase,
)
- return imported
-
@router.delete("/{file_name}/delete", status_code=200)
def delete_backup(file_name: str):
diff --git a/mealie/routes/users/__init__ copy.py b/mealie/routes/site_settings/__init__.py
similarity index 100%
rename from mealie/routes/users/__init__ copy.py
rename to mealie/routes/site_settings/__init__.py
diff --git a/mealie/routes/site_settings/all_settings.py b/mealie/routes/site_settings/all_settings.py
new file mode 100644
index 000000000..09da2eda2
--- /dev/null
+++ b/mealie/routes/site_settings/all_settings.py
@@ -0,0 +1,7 @@
+from fastapi import APIRouter
+from mealie.routes.site_settings import custom_pages, site_settings
+
+router = APIRouter()
+
+router.include_router(custom_pages.router)
+router.include_router(site_settings.router)
diff --git a/mealie/routes/site_settings/custom_pages.py b/mealie/routes/site_settings/custom_pages.py
new file mode 100644
index 000000000..33bd27598
--- /dev/null
+++ b/mealie/routes/site_settings/custom_pages.py
@@ -0,0 +1,75 @@
+from typing import Union
+
+from fastapi import APIRouter, Depends
+from mealie.db.database import db
+from mealie.db.db_setup import generate_session
+from mealie.routes.deps import get_current_user
+from mealie.schema.settings import CustomPageBase, CustomPageOut
+from mealie.schema.snackbar import SnackResponse
+from mealie.schema.user import UserInDB
+from sqlalchemy.orm.session import Session
+
+router = APIRouter(prefix="/api/site-settings/custom-pages", tags=["Settings"])
+
+
+@router.get("")
+def get_custom_pages(session: Session = Depends(generate_session)):
+ """ Returns the sites custom pages """
+
+ return db.custom_pages.get_all(session)
+
+
+@router.post("")
+async def create_new_page(
+ new_page: CustomPageBase,
+ session: Session = Depends(generate_session),
+ current_user: UserInDB = Depends(get_current_user),
+):
+ """ Creates a new Custom Page """
+
+ db.custom_pages.create(session, new_page.dict())
+
+ return SnackResponse.success("New Page Created")
+
+
+@router.put("")
+async def update_multiple_pages(
+ pages: list[CustomPageOut],
+ session: Session = Depends(generate_session),
+ current_user: UserInDB = Depends(get_current_user),
+):
+ """ Update multiple custom pages """
+ for page in pages:
+ db.custom_pages.update(session, page.id, page.dict())
+ return SnackResponse.success("Pages Updated")
+
+
+@router.get("/{id}")
+async def get_single_page(
+ id: Union[int, str],
+ session: Session = Depends(generate_session),
+):
+ """ Removes a custom page from the database """
+ if isinstance(id, int):
+ return db.custom_pages.get(session, id)
+ elif isinstance(id, str):
+ return db.custom_pages.get(session, id, "slug")
+
+
+@router.put("/{id}")
+async def update_single_age(data: CustomPageOut, id: int, session: Session = Depends(generate_session)):
+ """ Removes a custom page from the database """
+
+ return db.custom_pages.update(session, id, data.dict())
+
+
+@router.delete("/{id}")
+async def delete_custom_page(
+ id: int,
+ session: Session = Depends(generate_session),
+ current_user: UserInDB = Depends(get_current_user),
+):
+ """ Removes a custom page from the database """
+
+ db.custom_pages.delete(session, id)
+ return
diff --git a/mealie/routes/setting_routes.py b/mealie/routes/site_settings/site_settings.py
similarity index 95%
rename from mealie/routes/setting_routes.py
rename to mealie/routes/site_settings/site_settings.py
index 81cd18d46..4f7bf9772 100644
--- a/mealie/routes/setting_routes.py
+++ b/mealie/routes/site_settings/site_settings.py
@@ -16,9 +16,7 @@ router = APIRouter(prefix="/api/site-settings", tags=["Settings"])
def get_main_settings(session: Session = Depends(generate_session)):
""" Returns basic site settings """
- data = db.settings.get(session, 1)
-
- return data
+ return db.settings.get(session, 1)
@router.put("")
diff --git a/mealie/schema/backup.py b/mealie/schema/backup.py
index 12c0f3b3e..9b7b735c0 100644
--- a/mealie/schema/backup.py
+++ b/mealie/schema/backup.py
@@ -7,6 +7,7 @@ from pydantic import BaseModel
class BackupOptions(BaseModel):
recipes: bool = True
settings: bool = True
+ pages: bool = True
themes: bool = True
groups: bool = True
users: bool = True
diff --git a/mealie/schema/recipe.py b/mealie/schema/recipe.py
index ad343be18..54a83d534 100644
--- a/mealie/schema/recipe.py
+++ b/mealie/schema/recipe.py
@@ -101,11 +101,10 @@ class Recipe(BaseModel):
name: str = values["name"]
calc_slug: str = slugify(name)
- if slug == calc_slug:
- return slug
- else:
+ if slug != calc_slug:
slug = calc_slug
- return slug
+
+ return slug
class AllRecipeRequest(BaseModel):
diff --git a/mealie/schema/restore.py b/mealie/schema/restore.py
index f57fdd3fa..badb590d8 100644
--- a/mealie/schema/restore.py
+++ b/mealie/schema/restore.py
@@ -27,3 +27,7 @@ class GroupImport(ImportBase):
class UserImport(ImportBase):
pass
+
+
+class CustomPageImport(ImportBase):
+ pass
diff --git a/mealie/schema/settings.py b/mealie/schema/settings.py
index 0e97aa8c9..9f147d16a 100644
--- a/mealie/schema/settings.py
+++ b/mealie/schema/settings.py
@@ -1,8 +1,9 @@
from typing import Optional
from fastapi_camelcase import CamelModel
-
from mealie.schema.category import CategoryBase
+from pydantic import validator
+from slugify import slugify
class SiteSettings(CamelModel):
@@ -25,3 +26,30 @@ class SiteSettings(CamelModel):
],
}
}
+
+
+class CustomPageBase(CamelModel):
+ name: str
+ slug: Optional[str]
+ position: int
+ categories: list[CategoryBase] = []
+
+ class Config:
+ orm_mode = True
+
+ @validator("slug", always=True, pre=True)
+ def validate_slug(slug: str, values):
+ name: str = values["name"]
+ calc_slug: str = slugify(name)
+
+ if slug != calc_slug:
+ slug = calc_slug
+
+ return slug
+
+
+class CustomPageOut(CustomPageBase):
+ id: int
+
+ class Config:
+ orm_mode = True
diff --git a/mealie/services/backups/exports.py b/mealie/services/backups/exports.py
index 8d0e5ba04..1753e1140 100644
--- a/mealie/services/backups/exports.py
+++ b/mealie/services/backups/exports.py
@@ -4,11 +4,11 @@ from datetime import datetime
from pathlib import Path
from typing import Union
+from fastapi.logger import logger
+from jinja2 import Template
from mealie.core.config import BACKUP_DIR, IMG_DIR, TEMP_DIR, TEMPLATE_DIR
from mealie.db.database import db
from mealie.db.db_setup import create_session
-from fastapi.logger import logger
-from jinja2 import Template
from pydantic.main import BaseModel
@@ -101,6 +101,7 @@ def backup_all(
templates=None,
export_recipes=True,
export_settings=True,
+ export_pages=True,
export_themes=True,
export_users=True,
export_groups=True,
@@ -125,6 +126,10 @@ def backup_all(
all_settings = db.settings.get_all(session)
db_export.export_items(all_settings, "settings")
+ if export_pages:
+ all_pages = db.custom_pages.get_all(session)
+ db_export.export_items(all_pages, "pages")
+
if export_themes:
all_themes = db.themes.get_all(session)
db_export.export_items(all_themes, "themes")
@@ -136,10 +141,7 @@ def auto_backup_job():
for backup in BACKUP_DIR.glob("Auto*.zip"):
backup.unlink()
- templates = []
- for template in TEMPLATE_DIR.iterdir():
- templates.append(template)
-
+ templates = [template for template in TEMPLATE_DIR.iterdir()]
session = create_session()
backup_all(session=session, tag="Auto", templates=templates)
logger.info("Auto Backup Called")
diff --git a/mealie/services/backups/imports.py b/mealie/services/backups/imports.py
index ca99e8480..97ff3f1ed 100644
--- a/mealie/services/backups/imports.py
+++ b/mealie/services/backups/imports.py
@@ -7,8 +7,8 @@ from typing import Callable, List
from mealie.core.config import BACKUP_DIR, IMG_DIR, TEMP_DIR
from mealie.db.database import db
from mealie.schema.recipe import Recipe
-from mealie.schema.restore import GroupImport, RecipeImport, SettingsImport, ThemeImport, UserImport
-from mealie.schema.settings import SiteSettings
+from mealie.schema.restore import CustomPageImport, GroupImport, RecipeImport, SettingsImport, ThemeImport, UserImport
+from mealie.schema.settings import CustomPageOut, SiteSettings
from mealie.schema.theme import SiteTheme
from mealie.schema.user import UpdateGroup, UserInDB
from pydantic.main import BaseModel
@@ -42,7 +42,6 @@ class ImportDatabase:
with zipfile.ZipFile(self.archive, "r") as zip_ref:
zip_ref.extractall(self.import_dir)
- pass
else:
raise Exception("Import file does not exist")
@@ -95,9 +94,7 @@ class ImportDatabase:
try:
if "" in recipe_dict["categories"]:
- recipe_dict["categories"] = [
- cat for cat in recipe_dict["categories"] if cat != ""
- ]
+ recipe_dict["categories"] = [cat for cat in recipe_dict["categories"] if cat != ""]
except:
pass
@@ -149,6 +146,19 @@ class ImportDatabase:
return [import_status]
+ def import_pages(self):
+ pages_file = self.import_dir.joinpath("pages", "pages.json")
+ pages = ImportDatabase.read_models_file(pages_file, CustomPageOut)
+
+ page_imports = []
+ for page in pages:
+ import_stats = self.import_model(
+ db_table=db.custom_pages, model=page, return_model=CustomPageImport, name_attr="name", search_key="slug"
+ )
+ page_imports.append(import_stats)
+
+ return page_imports
+
def import_groups(self):
groups_file = self.import_dir.joinpath("groups", "groups.json")
groups = ImportDatabase.read_models_file(groups_file, UpdateGroup)
@@ -273,6 +283,7 @@ def import_database(
archive,
import_recipes=True,
import_settings=True,
+ import_pages=True,
import_themes=True,
import_users=True,
import_groups=True,
@@ -293,6 +304,10 @@ def import_database(
if import_themes:
theme_report = import_session.import_themes()
+ if import_pages:
+ print("IMport Pages")
+ page_report = import_session.import_pages()
+
group_report = []
if import_groups:
group_report = import_session.import_groups()
@@ -307,6 +322,7 @@ def import_database(
"recipeImports": recipe_report,
"settingsImports": settings_report,
"themeImports": theme_report,
+ "pageImports": page_report,
"groupImports": group_report,
"userImports": user_report,
}
diff --git a/tests/conftest.py b/tests/conftest.py
index 0dfd75af0..913f3e16c 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -3,7 +3,7 @@ import json
import requests
from fastapi.testclient import TestClient
from mealie.app import app
-from mealie.core.config import SQLITE_DIR
+from mealie.core.config import DEFAULT_PASSWORD, SQLITE_DIR
from mealie.db.db_setup import generate_session, sql_global_init
from mealie.db.init_db import init_db
from pytest import fixture
@@ -44,7 +44,7 @@ def test_image():
@fixture(scope="session")
def token(api_client: requests):
- form_data = {"username": "changeme@email.com", "password": "MyPassword"}
+ form_data = {"username": "changeme@email.com", "password": DEFAULT_PASSWORD}
response = api_client.post(TOKEN_URL, form_data)
token = json.loads(response.text).get("access_token")