add base for meal-plan categories

This commit is contained in:
hay-kot 2021-02-13 12:10:09 -09:00
commit d9b9b36747
20 changed files with 133 additions and 31 deletions

View file

@ -11,8 +11,6 @@
</span> </span>
</v-card-title> </v-card-title>
<v-divider></v-divider> <v-divider></v-divider>
<HomePageSettings />
<v-divider></v-divider>
<v-card-text> <v-card-text>
<h2 class="mt-1 mb-4">{{ $t("settings.language") }}</h2> <h2 class="mt-1 mb-4">{{ $t("settings.language") }}</h2>
<v-row> <v-row>
@ -30,6 +28,8 @@
</v-row> </v-row>
</v-card-text> </v-card-text>
<v-divider></v-divider> <v-divider></v-divider>
<HomePageSettings />
<v-divider></v-divider>
</v-card> </v-card>
</template> </template>
@ -59,6 +59,9 @@ export default {
this.langOptions = this.$store.getters.getAllLangs; this.langOptions = this.$store.getters.getAllLangs;
this.selectedLang = this.$store.getters.getActiveLang; this.selectedLang = this.$store.getters.getActiveLang;
}, },
removeCategory(index) {
this.value.categories.splice(index, 1);
},
}, },
}; };
</script> </script>

View file

@ -1,9 +1,45 @@
<template> <template>
<v-card> <v-card>
<v-card-title class="headline"> <v-card-title class="headline">
{{ $t("settings.webhooks.meal-planner-webhooks") }} {{ $t("meal-plan.meal-planner") }}
</v-card-title> </v-card-title>
<v-divider></v-divider>
<v-card-text> <v-card-text>
<h2 class="mt-1">{{ $t("recipe.categories") }}</h2>
<v-row>
<v-col sm="12" md="6">
<v-select
v-model="planCategories"
:items="categories"
item-text="name"
item-value="name"
label="Allowed Categories"
multiple
chips
hint="Only recipes with these categories will be used in Meal Plans"
persistent-hint
>
<template v-slot:selection="data">
<v-chip
:input-value="data.selected"
close
@click:close="removeCategory(data.index)"
color="secondary"
dark
>
{{ data.item.name }}
</v-chip>
</template>
</v-select>
</v-col>
</v-row>
</v-card-text>
<v-divider> </v-divider>
<v-card-text>
<h2 class="mt-1 mb-4">
{{ $t("settings.webhooks.meal-planner-webhooks") }}
</h2>
<p> <p>
{{ {{
$t( $t(
@ -68,21 +104,29 @@ export default {
webhooks: [], webhooks: [],
enabled: false, enabled: false,
time: "", time: "",
planCategories: [],
}; };
}, },
mounted() { mounted() {
this.getSiteSettings(); this.getSiteSettings();
}, },
computed: {
categories() {
return this.$store.getters.getCategories;
},
},
methods: { methods: {
saveTime(value) { saveTime(value) {
this.time = value; this.time = value;
}, },
async getSiteSettings() { async getSiteSettings() {
let settings = await api.settings.requestAll(); let settings = await api.settings.requestAll();
console.log(settings);
this.webhooks = settings.webhooks.webhookURLs; this.webhooks = settings.webhooks.webhookURLs;
this.name = settings.name; this.name = settings.name;
this.time = settings.webhooks.webhookTime; this.time = settings.webhooks.webhookTime;
this.enabled = settings.webhooks.enabled; this.enabled = settings.webhooks.enabled;
this.planCategories = settings.planCategories;
}, },
addWebhook() { addWebhook() {
this.webhooks.push(" "); this.webhooks.push(" ");
@ -93,6 +137,7 @@ export default {
saveWebhooks() { saveWebhooks() {
const body = { const body = {
name: this.name, name: this.name,
planCategories: this.planCategories,
webhooks: { webhooks: {
webhookURLs: this.webhooks, webhookURLs: this.webhooks,
webhookTime: this.time, webhookTime: this.time,
@ -104,6 +149,9 @@ export default {
testWebhooks() { testWebhooks() {
api.settings.testWebhooks(); api.settings.testWebhooks();
}, },
removeCategory(index) {
this.planCategories.splice(index, 1);
},
}, },
}; };
</script> </script>

View file

@ -45,6 +45,7 @@
}, },
"meal-plan": { "meal-plan": {
"dinner-this-week": "Dinner This Week", "dinner-this-week": "Dinner This Week",
"meal-planner": "Meal Planner",
"dinner-today": "Dinner Today", "dinner-today": "Dinner Today",
"planner": "Planner", "planner": "Planner",
"edit-meal-plan": "Edit Meal Plan", "edit-meal-plan": "Edit Meal Plan",

View file

@ -16,7 +16,7 @@
<General /> <General />
<Theme class="mt-2" /> <Theme class="mt-2" />
<Backup class="mt-2" /> <Backup class="mt-2" />
<Webhooks class="mt-2" /> <MealPlanner class="mt-2" />
<Migration class="mt-2" /> <Migration class="mt-2" />
<p class="text-center my-2"> <p class="text-center my-2">
{{ $t("settings.current") }} {{ $t("settings.current") }}
@ -41,7 +41,7 @@
<script> <script>
import Backup from "../components/Settings/Backup"; import Backup from "../components/Settings/Backup";
import General from "../components/Settings/General"; import General from "../components/Settings/General";
import Webhooks from "../components/Settings/Webhook"; import MealPlanner from "../components/Settings/MealPlanner";
import Theme from "../components/Settings/Theme"; import Theme from "../components/Settings/Theme";
import Migration from "../components/Settings/Migration"; import Migration from "../components/Settings/Migration";
import api from "@/api"; import api from "@/api";
@ -50,7 +50,7 @@ import axios from "axios";
export default { export default {
components: { components: {
Backup, Backup,
Webhooks, MealPlanner,
Theme, Theme,
Migration, Migration,
General, General,

View file

@ -29,6 +29,7 @@ const store = new Vuex.Store({
// All Recipe Data Store // All Recipe Data Store
recentRecipes: [], recentRecipes: [],
allRecipes: [], allRecipes: [],
mealPlanCategories: [],
}, },
mutations: { mutations: {
@ -44,6 +45,11 @@ const store = new Vuex.Store({
setRecentRecipes(state, payload) { setRecentRecipes(state, payload) {
state.recentRecipes = payload; state.recentRecipes = payload;
}, },
setMealPlanCategories(state, payload) {
console.log(payload);
state.mealPlanCategories = payload;
},
}, },
actions: { actions: {
@ -69,6 +75,7 @@ const store = new Vuex.Store({
getSnackType: state => state.snackType, getSnackType: state => state.snackType,
getRecentRecipes: state => state.recentRecipes, getRecentRecipes: state => state.recentRecipes,
getMealPlanCategories: state => state.mealPlanCategories,
}, },
}); });

View file

@ -17,7 +17,7 @@ dotenv.load_dotenv(ENV)
# General # General
APP_VERSION = "v0.2.1" APP_VERSION = "v0.2.1"
DB_VERSION = "v0.2.0" DB_VERSION = "v0.2.1"
PRODUCTION = os.environ.get("ENV") PRODUCTION = os.environ.get("ENV")
PORT = int(os.getenv("mealie_port", 9000)) PORT = int(os.getenv("mealie_port", 9000))
API = os.getenv("api_docs", True) API = os.getenv("api_docs", True)

View file

@ -15,7 +15,7 @@ from db.sql.theme_models import SiteThemeModel
class _Recipes(BaseDocument): class _Recipes(BaseDocument):
def __init__(self) -> None: def __init__(self) -> None:
self.primary_key = "slug" self.primary_key = "slug"
self.sql_model = RecipeModel self.sql_model: RecipeModel = RecipeModel
def update_image(self, session: Session, slug: str, extension: str = None) -> str: def update_image(self, session: Session, slug: str, extension: str = None) -> str:
entry: RecipeModel = self._query_one(session, match_value=slug) entry: RecipeModel = self._query_one(session, match_value=slug)
@ -48,15 +48,6 @@ class _Settings(BaseDocument):
self.primary_key = "name" self.primary_key = "name"
self.sql_model = SiteSettingsModel self.sql_model = SiteSettingsModel
def create(self, session: Session, main: dict, webhooks: dict) -> str:
new_settings = self.sql_model(main.get("name"), webhooks)
session.add(new_settings)
return_data = new_settings.dict()
session.commit()
return return_data
class _Themes(BaseDocument): class _Themes(BaseDocument):
def __init__(self) -> None: def __init__(self) -> None:

View file

@ -85,6 +85,13 @@ class Category(SqlAlchemyBase):
"recipes": [x.dict() for x in self.recipes], "recipes": [x.dict() for x in self.recipes],
} }
def dict_no_recipes(self):
return {
"id": self.id,
"slug": self.slug,
"name": self.name,
}
class Tag(SqlAlchemyBase): class Tag(SqlAlchemyBase):
__tablename__ = "tags" __tablename__ = "tags"

View file

@ -1,27 +1,54 @@
import sqlalchemy as sa import sqlalchemy as sa
import sqlalchemy.orm as orm import sqlalchemy.orm as orm
from db.sql.model_base import BaseMixins, SqlAlchemyBase from db.sql.model_base import BaseMixins, SqlAlchemyBase
from db.sql.recipe_models import Category
class SiteSettingsModel(SqlAlchemyBase): class SiteSettingsModel(SqlAlchemyBase, BaseMixins):
__tablename__ = "site_settings" __tablename__ = "site_settings"
name = sa.Column(sa.String, primary_key=True) name = sa.Column(sa.String, primary_key=True)
planCategories = orm.relationship(
"MealCategory", uselist=True, cascade="all, delete"
)
webhooks = orm.relationship("WebHookModel", uselist=False, cascade="all, delete") webhooks = orm.relationship("WebHookModel", uselist=False, cascade="all, delete")
def __init__(self, name: str = None, webhooks: dict = None, session=None) -> None: def __init__(
self, name: str = None, webhooks: dict = None, planCategories=[], session=None
) -> None:
self.name = name self.name = name
self.planCategories = [MealCategory(cat) for cat in planCategories]
self.webhooks = WebHookModel(**webhooks) self.webhooks = WebHookModel(**webhooks)
def update(self, session, name, webhooks: dict) -> dict: def update(self, session, name, webhooks: dict, planCategories=[]) -> dict:
self._sql_remove_list(session, [MealCategory], self.name)
self.name = name self.name = name
self.planCategories = [MealCategory(x) for x in planCategories]
self.webhooks.update(session=session, **webhooks) self.webhooks.update(session=session, **webhooks)
return return
def dict(self): def dict(self):
data = {"name": self.name, "webhooks": self.webhooks.dict()} data = {
"name": self.name,
"planCategories": [cat.to_str() for cat in self.planCategories],
"webhooks": self.webhooks.dict(),
}
return data return data
class MealCategory(SqlAlchemyBase):
__tablename__ = "meal_plan_categories"
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.String)
parent_id = sa.Column(sa.Integer, sa.ForeignKey("site_settings.name"))
def __init__(self, name) -> None:
self.name = name
def to_str(self):
return self.name
class WebHookModel(SqlAlchemyBase, BaseMixins): class WebHookModel(SqlAlchemyBase, BaseMixins):
__tablename__ = "webhook_settings" __tablename__ = "webhook_settings"
id = sa.Column(sa.Integer, primary_key=True) id = sa.Column(sa.Integer, primary_key=True)

View file

View file

@ -1,4 +1,4 @@
from typing import List from typing import List, Optional
from pydantic.main import BaseModel from pydantic.main import BaseModel
from services.recipe_services import Recipe from services.recipe_services import Recipe
@ -8,7 +8,7 @@ class RecipeCategoryResponse(BaseModel):
id: int id: int
name: str name: str
slug: str slug: str
recipes: List[Recipe] recipes: Optional[List[Recipe]]
class Config: class Config:
schema_extra = {"example": {"id": 1, "name": "dinner", "recipes": [{}]}} schema_extra = {"example": {"id": 1, "name": "dinner", "recipes": [{}]}}

View file

@ -3,6 +3,7 @@ from typing import List, Optional
from pydantic import BaseModel from pydantic import BaseModel
class Webhooks(BaseModel): class Webhooks(BaseModel):
webhookTime: str = "00:00" webhookTime: str = "00:00"
webhookURLs: Optional[List[str]] = [] webhookURLs: Optional[List[str]] = []
@ -11,12 +12,14 @@ class Webhooks(BaseModel):
class SiteSettings(BaseModel): class SiteSettings(BaseModel):
name: str = "main" name: str = "main"
planCategories: list[str] = []
webhooks: Webhooks webhooks: Webhooks
class Config: class Config:
schema_extra = { schema_extra = {
"example": { "example": {
"name": "main", "name": "main",
"planCategories": ["dinner", "lunch"],
"webhooks": { "webhooks": {
"webhookTime": "00:00", "webhookTime": "00:00",
"webhookURLs": ["https://mywebhookurl.com/webhook"], "webhookURLs": ["https://mywebhookurl.com/webhook"],

View file

@ -69,3 +69,15 @@ def get_all_recipes_post(
""" """
return db.recipes.get_all_limit_columns(session, body.properties, body.limit) return db.recipes.get_all_limit_columns(session, body.properties, body.limit)
@router.post("/api/category")
async def filter_by_category(
categories: list, session: Session = Depends(generate_session)
):
""" pass a list of categories and get a list of recipes associated with those categories """
#! This should be refactored into a single database call, but I couldn't figure it out
in_category = [db.categories.get(session, cat) for cat in categories]
in_category = [cat.get("recipes") for cat in in_category]
in_category = [item for sublist in in_category for item in sublist]
return in_category

View file

@ -3,7 +3,6 @@ from db.db_setup import generate_session
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends
from models.category_models import RecipeCategoryResponse from models.category_models import RecipeCategoryResponse
from sqlalchemy.orm.session import Session from sqlalchemy.orm.session import Session
from utils.snackbar import SnackResponse from utils.snackbar import SnackResponse
router = APIRouter( router = APIRouter(
@ -26,13 +25,14 @@ def get_all_recipes_by_category(
return db.categories.get(session, category) return db.categories.get(session, category)
@router.delete("/{category}") @router.delete("/{category}")
async def delete_recipe_category( async def delete_recipe_category(
category: str, session: Session = Depends(generate_session) category: str, session: Session = Depends(generate_session)
): ):
""" Removes a recipe category from the database. Deleting a """Removes a recipe category from the database. Deleting a
category does not impact a recipe. The category will be removed category does not impact a recipe. The category will be removed
from any recipes that contain it """ from any recipes that contain it"""
db.categories.delete(session, category) db.categories.delete(session, category)

View file

@ -19,13 +19,14 @@ def get_main_settings(session: Session = Depends(generate_session)):
except: except:
default_settings_init(session) default_settings_init(session)
data = db.settings.get(session, "main") data = db.settings.get(session, "main")
print(data)
return data return data
@router.put("") @router.put("")
def update_settings(data: SiteSettings, session: Session = Depends(generate_session)): def update_settings(data: SiteSettings, session: Session = Depends(generate_session)):
""" Returns Site Settings """ """ Returns Site Settings """
print("Categories", data.planCategories)
db.settings.update(session, "main", data.dict()) db.settings.update(session, "main", data.dict())
return SnackResponse.success("Settings Updated") return SnackResponse.success("Settings Updated")

View file

@ -3,6 +3,7 @@ from pathlib import Path
import requests import requests
from app_config import IMG_DIR from app_config import IMG_DIR
from utils.logger import logger
def read_image(recipe_slug: str) -> Path: def read_image(recipe_slug: str) -> Path:
@ -48,6 +49,7 @@ def scrape_image(image_url: str, slug: str) -> Path:
try: try:
r = requests.get(image_url, stream=True) r = requests.get(image_url, stream=True)
except: except:
logger.exception("Fatal Image Request Exception")
return None return None
if r.status_code == 200: if r.status_code == 200:

View file

@ -1,5 +1,4 @@
from datetime import date, timedelta from datetime import date, timedelta
from pathlib import Path
from typing import List, Optional from typing import List, Optional
from db.database import db from db.database import db

View file

@ -1,7 +1,8 @@
from db.database import db from db.database import db
from db.db_setup import create_session, sql_exists from db.db_setup import create_session
from models.settings_models import SiteSettings, Webhooks from models.settings_models import SiteSettings, Webhooks
from sqlalchemy.orm.session import Session from sqlalchemy.orm.session import Session
from utils.logger import logger
def default_settings_init(session: Session = None): def default_settings_init(session: Session = None):
@ -10,7 +11,7 @@ def default_settings_init(session: Session = None):
try: try:
webhooks = Webhooks() webhooks = Webhooks()
default_entry = SiteSettings(name="main", webhooks=webhooks) default_entry = SiteSettings(name="main", webhooks=webhooks)
document = db.settings.create(session, default_entry.dict(), webhooks.dict()) document = db.settings.create(session, default_entry.dict())
logger.info(f"Created Site Settings: \n {document}")
except: except:
pass pass