custom pages starter

This commit is contained in:
hay-kot 2021-03-27 17:04:59 -08:00
commit 341faace21
20 changed files with 447 additions and 81 deletions

2
.gitignore vendored
View file

@ -1,6 +1,6 @@
# Byte-compiled / optimized / DLL files
.env
__pycache__/
*__pycache__/
*.py[cod]
*$py.class
# frontend/.env.development

View file

@ -76,8 +76,6 @@ export default {
mounted() {
this.$store.dispatch("initTheme");
this.$store.dispatch("requestRecentRecipes");
this.$store.dispatch("requestHomePageSettings");
this.$store.dispatch("requestSiteSettings");
this.$store.dispatch("refreshToken");
this.$store.dispatch("requestCurrentGroup");
this.darkModeSystemCheck();

View file

@ -0,0 +1,138 @@
<template>
<v-card flat>
<v-card-text>
<h2 class="mt-1 mb-1 ">
Custom Pages
<span>
<v-btn color="success" small class="ml-3">
New
</v-btn>
</span>
</h2>
<v-row class="mt-1">
<v-col
:sm="6"
:md="6"
:lg="4"
:xl="3"
v-for="item in customPages"
:key="item + item.id"
>
<v-card>
<v-card-title class="headline">{{ item.name }}</v-card-title>
<v-divider></v-divider>
<v-card-text>
Card Position: {{ item.position }}
<div>
<v-chip
v-for="cat in item.categories"
:key="cat.slug + cat.id"
class="my-2 mr-2"
label
small
color="accent lighten-1"
>
{{ cat.name }}
</v-chip>
</div>
</v-card-text>
<v-card-actions>
<v-btn text small color="error">
Delete
</v-btn>
<v-spacer> </v-spacer>
<v-btn small text color="success">
Edit
</v-btn>
</v-card-actions>
</v-card>
</v-col>
</v-row>
</v-card-text>
</v-card>
</template>
<script>
export default {
data() {
return {
customPages: [
{
id: 0,
name: "My Page Name",
slug: "my-page-name",
position: 0,
categories: [
{
id: 2,
slug: "brownie",
name: "brownie",
},
{
id: 3,
slug: "dessert",
name: "dessert",
},
{
id: 4,
slug: "drink",
name: "Drink",
},
],
},
{
id: 3,
name: "My Page Name 1",
slug: "my-page-name",
position: 1,
categories: [
{
id: 2,
slug: "brownie",
name: "brownie",
},
{
id: 3,
slug: "dessert",
name: "dessert",
},
{
id: 4,
slug: "drink",
name: "Drink",
},
],
},
{
id: 2,
name: "My Page Name 2",
slug: "my-page-name",
position: 2,
categories: [
{
id: 2,
slug: "brownie",
name: "brownie",
},
{
id: 3,
slug: "dessert",
name: "dessert",
},
{
id: 4,
slug: "drink",
name: "Drink",
},
],
},
],
};
},
};
</script>
<style lang="scss" scoped>
</style>

View file

@ -1,9 +1,9 @@
<template>
<div class="mt-n5">
<div class="mt-n5" v-if="recipes">
<v-card flat class="transparent" height="60px">
<v-card-text>
<v-row>
<v-col>
<v-row v-if="title != null">
<v-col >
<v-btn-toggle group>
<v-btn text :to="`/recipes/${title.toLowerCase()}`">
{{ title.toUpperCase() }}
@ -15,15 +15,21 @@
<v-menu offset-y v-if="sortable">
<template v-slot:activator="{ on, attrs }">
<v-btn-toggle group>
<v-btn text v-bind="attrs" v-on="on">{{$t('general.sort')}}</v-btn>
<v-btn text v-bind="attrs" v-on="on">{{
$t("general.sort")
}}</v-btn>
</v-btn-toggle>
</template>
<v-list>
<v-list-item @click="$emit('sort-recent')">
<v-list-item-title>{{$t('general.recent')}}</v-list-item-title>
<v-list-item-title>{{
$t("general.recent")
}}</v-list-item-title>
</v-list-item>
<v-list-item @click="$emit('sort')">
<v-list-item-title>{{$t('general.sort-alphabetically')}}</v-list-item-title>
<v-list-item-title>{{
$t("general.sort-alphabetically")
}}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
@ -31,44 +37,45 @@
</v-row>
</v-card-text>
</v-card>
<v-row v-if="!viewScale">
<v-col
:sm="6"
:md="6"
:lg="4"
:xl="3"
v-for="recipe in recipes.slice(0, cardLimit)"
:key="recipe.name"
>
<RecipeCard
:name="recipe.name"
:description="recipe.description"
:slug="recipe.slug"
:rating="recipe.rating"
:image="recipe.image"
/>
</v-col>
</v-row>
<v-row v-else dense>
<v-col
cols="12"
sm="12"
md="6"
lg="4"
xl="3"
v-for="recipe in recipes.slice(0, cardLimit)"
:key="recipe.name"
>
<MobileRecipeCard
:name="recipe.name"
:description="recipe.description"
:slug="recipe.slug"
:rating="recipe.rating"
:image="recipe.image"
/>
</v-col>
</v-row>
<div v-if="recipes">
<v-row v-if="!viewScale">
<v-col
:sm="6"
:md="6"
:lg="4"
:xl="3"
v-for="recipe in recipes.slice(0, cardLimit)"
:key="recipe.name"
>
<RecipeCard
:name="recipe.name"
:description="recipe.description"
:slug="recipe.slug"
:rating="recipe.rating"
:image="recipe.image"
/>
</v-col>
</v-row>
<v-row v-else dense>
<v-col
cols="12"
sm="12"
md="6"
lg="4"
xl="3"
v-for="recipe in recipes.slice(0, cardLimit)"
:key="recipe.name"
>
<MobileRecipeCard
:name="recipe.name"
:description="recipe.description"
:slug="recipe.slug"
:rating="recipe.rating"
:image="recipe.image"
/>
</v-col>
</v-row>
</div>
</div>
</template>
@ -84,12 +91,19 @@ export default {
sortable: {
default: false,
},
title: String,
title: {
default: null
},
recipes: Array,
cardLimit: {
default: 6,
default: 999,
},
},
watch: {
recipes(val) {
console.log(val)
}
},
computed: {
viewScale() {
switch (this.$vuetify.breakpoint.name) {

View file

@ -14,15 +14,19 @@
<v-divider></v-divider>
<HomePageSettings />
<v-divider></v-divider>
<CustomPageCreator />
<v-divider></v-divider>
</v-card>
</template>
<script>
import HomePageSettings from "@/components/Admin/General/HomePageSettings";
import CustomPageCreator from "@/components/Admin/General/CustomPageCreator";
export default {
components: {
HomePageSettings,
CustomPageCreator,
},
data() {
return {

View file

@ -2,10 +2,10 @@
<v-container>
<CategorySidebar />
<CardSection
v-if="showRecent"
v-if="siteSettings.showRecent"
:title="$t('page.recent')"
:recipes="recentRecipes"
:card-limit="showLimit"
:card-limit="siteSettings.cardsPerSection"
/>
<CardSection
:sortable="true"
@ -13,7 +13,7 @@
:key="section.name + section.position"
:title="section.name"
:recipes="section.recipes"
:card-limit="showLimit"
:card-limit="siteSettings.cardsPerSection"
@sort="sortAZ(index)"
@sort-recent="sortRecent(index)"
/>
@ -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);
});
},

View file

@ -0,0 +1,93 @@
<template>
<v-container>
<CategorySidebar />
<v-card flat height="100%">
<v-card-title class="text-center justify-center py-6 headline">
Category Section
</v-card-title>
<div v-if="render">
<v-tabs v-model="tab" background-color="transparent" grow>
<v-tab v-for="item in categories" :key="item.slug">
{{ item.name }}
</v-tab>
</v-tabs>
<v-tabs-items v-model="tab">
<v-tab-item
v-for="(item, index) in categories"
:key="item.slug + index"
>
<CardSection class="mb-5 mx-1" :recipes="filterRecipe(item.slug)" />
</v-tab-item>
</v-tabs-items>
</div>
</v-card>
</v-container>
</template>
<script>
import CardSection from "@/components/UI/CardSection";
import CategorySidebar from "@/components/UI/CategorySidebar";
import api from "@/api";
export default {
components: {
CardSection,
CategorySidebar,
},
data() {
return {
tab: null,
render: false,
recipeStore: [],
categories: [
{
id: 2,
slug: "brownie",
name: "brownie",
},
{
id: 3,
slug: "dessert",
name: "dessert",
},
{
id: 4,
slug: "drink",
name: "Drink",
},
],
};
},
watch: {
tab(val) {
console.log(val);
},
},
async mounted() {
await this.buildPage();
this.render = true;
},
methods: {
async buildPage() {
this.categories.forEach(async element => {
let categoryRecipes = await this.getRecipeByCategory(element.slug);
this.recipeStore.push(categoryRecipes);
});
},
async getRecipeByCategory(category) {
return await api.categories.get_recipes_in_category(category);
},
filterRecipe(slug) {
const storeCategory = this.recipeStore.find(
element => element.slug === slug
);
return storeCategory ? storeCategory.recipes : [];
},
},
};
</script>
<style>
</style>

View file

@ -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: "/recipes/test-page", component: CustomPage },
{ path: "/recipes/:category", component: CategoryPage },
{ path: "/recipe/:recipe", component: ViewRecipe },
{ path: "/new/", component: NewRecipe },

View file

@ -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,
},

View file

@ -11,7 +11,7 @@ const state = {
const mutations = {
setSettings(state, payload) {
state.settings = payload;
state.siteSettings = payload;
},
};

View file

@ -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)

View file

@ -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()

View file

@ -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:

View file

@ -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=[]) -> 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)

View file

@ -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)

View file

@ -0,0 +1,52 @@
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
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.get("/{id}")
async def delete_custom_page(
id: int,
session: Session = Depends(generate_session),
):
""" Removes a custom page from the database """
return db.custom_pages.get(session, id)
@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

View file

@ -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("")

View file

@ -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):

View file

@ -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