mirror of
https://github.com/hay-kot/mealie.git
synced 2025-08-22 14:33:33 -07:00
refactor recipe schema/model
This commit is contained in:
parent
db61ac8a31
commit
9a5fc25cb4
26 changed files with 221 additions and 396 deletions
|
@ -7,6 +7,7 @@
|
|||
<UploadBtn
|
||||
class="mt-1"
|
||||
:url="`/api/migrations/${folder}/upload`"
|
||||
fileName="archive"
|
||||
@uploaded="$emit('refresh')"
|
||||
/>
|
||||
</span>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div v-if="items[0]">
|
||||
<div v-if="items && items.length > 0">
|
||||
<h2 class="mt-4">{{ title }}</h2>
|
||||
<v-chip
|
||||
class="ma-1"
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
from schema.meal import MealPlanInDB
|
||||
from schema.recipe import Recipe
|
||||
from schema.sign_up import SignUpOut
|
||||
from schema.user import GroupInDB, UserInDB
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
|
@ -15,7 +18,8 @@ class _Recipes(BaseDocument):
|
|||
def __init__(self) -> None:
|
||||
self.primary_key = "slug"
|
||||
self.sql_model: RecipeModel = RecipeModel
|
||||
self.orm_mode = False
|
||||
self.orm_mode = True
|
||||
self.schema: Recipe = Recipe
|
||||
|
||||
def update_image(self, session: Session, slug: str, extension: str = None) -> str:
|
||||
entry: RecipeModel = self._query_one(session, match_value=slug)
|
||||
|
@ -43,7 +47,8 @@ class _Meals(BaseDocument):
|
|||
def __init__(self) -> None:
|
||||
self.primary_key = "uid"
|
||||
self.sql_model = MealPlanModel
|
||||
self.orm_mode = False
|
||||
self.orm_mode = True
|
||||
self.schema = MealPlanInDB
|
||||
|
||||
|
||||
class _Settings(BaseDocument):
|
||||
|
@ -70,10 +75,9 @@ class _Users(BaseDocument):
|
|||
def update_password(self, session, id, password: str):
|
||||
entry = self._query_one(session=session, match_value=id)
|
||||
entry.update_password(password)
|
||||
return_data = entry.dict()
|
||||
session.commit()
|
||||
|
||||
return return_data
|
||||
return self.schema.from_orm(entry)
|
||||
|
||||
|
||||
class _Groups(BaseDocument):
|
||||
|
@ -88,7 +92,8 @@ class _SignUps(BaseDocument):
|
|||
def __init__(self) -> None:
|
||||
self.primary_key = "token"
|
||||
self.sql_model = SignUp
|
||||
self.orm_mode = False
|
||||
self.orm_mode = True
|
||||
self.schema = SignUpOut
|
||||
|
||||
|
||||
class Database:
|
||||
|
|
|
@ -122,7 +122,7 @@ class BaseDocument:
|
|||
return self.schema.from_orm(result[0])
|
||||
except IndexError:
|
||||
return None
|
||||
return [self.schema(x) for x in result]
|
||||
return [self.schema.from_orm(x) for x in result]
|
||||
|
||||
db_entries = [x.dict() for x in result]
|
||||
|
||||
|
|
|
@ -13,32 +13,16 @@ class Meal(SqlAlchemyBase):
|
|||
slug = sa.Column(sa.String)
|
||||
name = sa.Column(sa.String)
|
||||
date = sa.Column(sa.Date)
|
||||
dateText = sa.Column(sa.String)
|
||||
image = sa.Column(sa.String)
|
||||
description = sa.Column(sa.String)
|
||||
|
||||
def __init__(
|
||||
self, slug, name, date, dateText, image, description, session=None
|
||||
) -> None:
|
||||
def __init__(self, slug, name, date, image, description, session=None) -> None:
|
||||
self.slug = slug
|
||||
self.name = name
|
||||
self.date = date
|
||||
self.dateText = dateText
|
||||
self.image = image
|
||||
self.description = description
|
||||
|
||||
def dict(self) -> dict:
|
||||
data = {
|
||||
"slug": self.slug,
|
||||
"name": self.name,
|
||||
"date": self.date,
|
||||
"dateText": self.dateText,
|
||||
"image": self.image,
|
||||
"description": self.description,
|
||||
}
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class MealPlanModel(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "mealplan"
|
||||
|
@ -58,13 +42,3 @@ class MealPlanModel(SqlAlchemyBase, BaseMixins):
|
|||
MealPlanModel._sql_remove_list(session, [Meal], uid)
|
||||
|
||||
self.__init__(startDate, endDate, meals)
|
||||
|
||||
def dict(self) -> dict:
|
||||
data = {
|
||||
"uid": self.uid,
|
||||
"startDate": self.startDate,
|
||||
"endDate": self.endDate,
|
||||
"meals": [meal.dict() for meal in self.meals],
|
||||
}
|
||||
|
||||
return data
|
||||
|
|
|
@ -183,29 +183,29 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
|
|||
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),
|
||||
}
|
||||
# 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
|
||||
# return data
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from db.models.model_base import BaseMixins, SqlAlchemyBase
|
||||
from sqlalchemy import Column, Integer, String, Boolean
|
||||
from sqlalchemy import Boolean, Column, Integer, String
|
||||
|
||||
|
||||
class SignUp(SqlAlchemyBase, BaseMixins):
|
||||
|
@ -19,11 +19,3 @@ class SignUp(SqlAlchemyBase, BaseMixins):
|
|||
self.token = token
|
||||
self.name = name
|
||||
self.admin = admin
|
||||
|
||||
def dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"token": self.token,
|
||||
"admin": self.admin
|
||||
}
|
||||
|
|
|
@ -5,11 +5,11 @@ from core.config import BACKUP_DIR, TEMPLATE_DIR
|
|||
from db.db_setup import generate_session
|
||||
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
|
||||
from schema.backup import BackupJob, ImportJob, Imports, LocalBackup
|
||||
from schema.snackbar import SnackResponse
|
||||
from services.backups.exports import backup_all
|
||||
from services.backups.imports import ImportDatabase
|
||||
from sqlalchemy.orm.session import Session
|
||||
from starlette.responses import FileResponse
|
||||
from schema.snackbar import SnackResponse
|
||||
|
||||
router = APIRouter(prefix="/api/backups", tags=["Backups"])
|
||||
|
||||
|
|
|
@ -1,20 +1,22 @@
|
|||
from datetime import date
|
||||
from typing import List
|
||||
|
||||
from db.database import db
|
||||
from db.db_setup import generate_session
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi import APIRouter, Depends
|
||||
from schema.meal import MealPlanBase, MealPlanInDB
|
||||
from schema.snackbar import SnackResponse
|
||||
from services.meal_services import MealPlan
|
||||
from services.meal_services import get_todays_meal, process_meals
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
router = APIRouter(prefix="/api/meal-plans", tags=["Meal Plan"])
|
||||
|
||||
|
||||
@router.get("/all", response_model=List[MealPlan])
|
||||
@router.get("/all", response_model=List[MealPlanInDB])
|
||||
def get_all_meals(session: Session = Depends(generate_session)):
|
||||
""" Returns a list of all available Meal Plan """
|
||||
|
||||
return MealPlan.get_all(session)
|
||||
return db.meals.get_all(session)
|
||||
|
||||
|
||||
@router.get("/{id}/shopping-list")
|
||||
|
@ -22,7 +24,8 @@ def get_shopping_list(id: str, session: Session = Depends(generate_session)):
|
|||
|
||||
#! Refactor into Single Database Call
|
||||
mealplan = db.meals.get(session, id)
|
||||
slugs = [x.get("slug") for x in mealplan.get("meals")]
|
||||
mealplan: MealPlanInDB
|
||||
slugs = [x.slug for x in mealplan.meals]
|
||||
recipes = [db.recipes.get(session, x) for x in slugs]
|
||||
ingredients = [
|
||||
{"name": x.get("name"), "recipeIngredient": x.get("recipeIngredient")}
|
||||
|
@ -34,28 +37,29 @@ def get_shopping_list(id: str, session: Session = Depends(generate_session)):
|
|||
|
||||
|
||||
@router.post("/create")
|
||||
def set_meal_plan(data: MealPlan, session: Session = Depends(generate_session)):
|
||||
def create_meal_plan(data: MealPlanBase, session: Session = Depends(generate_session)):
|
||||
""" Creates a meal plan database entry """
|
||||
data.process_meals(session)
|
||||
data.save_to_db(session)
|
||||
processed_plan = process_meals(session, data)
|
||||
db.meals.create(session, processed_plan.dict())
|
||||
|
||||
return SnackResponse.success("Mealplan Created")
|
||||
|
||||
|
||||
@router.get("/this-week", response_model=MealPlan)
|
||||
@router.get("/this-week", response_model=MealPlanInDB)
|
||||
def get_this_week(session: Session = Depends(generate_session)):
|
||||
""" Returns the meal plan data for this week """
|
||||
|
||||
return MealPlan.this_week(session)
|
||||
return db.meals.get_all(session, limit=1, order_by="startDate")
|
||||
|
||||
|
||||
@router.put("/{plan_id}")
|
||||
def update_meal_plan(
|
||||
plan_id: str, meal_plan: MealPlan, session: Session = Depends(generate_session)
|
||||
plan_id: str, meal_plan: MealPlanBase, session: Session = Depends(generate_session)
|
||||
):
|
||||
""" Updates a meal plan based off ID """
|
||||
meal_plan.process_meals(session)
|
||||
meal_plan.update(session, plan_id)
|
||||
processed_plan = process_meals(session, meal_plan)
|
||||
processed_plan = MealPlanInDB(uid=plan_id, **processed_plan.dict())
|
||||
db.meals.update(session, plan_id, processed_plan.dict())
|
||||
|
||||
return SnackResponse.info("Mealplan Updated")
|
||||
|
||||
|
@ -64,7 +68,7 @@ def update_meal_plan(
|
|||
def delete_meal_plan(plan_id, session: Session = Depends(generate_session)):
|
||||
""" Removes a meal plan from the database """
|
||||
|
||||
MealPlan.delete(session, plan_id)
|
||||
db.meals.delete(session, plan_id)
|
||||
|
||||
return SnackResponse.error("Mealplan Deleted")
|
||||
|
||||
|
@ -76,4 +80,4 @@ def get_today(session: Session = Depends(generate_session)):
|
|||
If no meal is scheduled nothing is returned
|
||||
"""
|
||||
|
||||
return MealPlan.today(session)
|
||||
return get_todays_meal(session)
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
from db.database import db
|
||||
from db.db_setup import generate_session
|
||||
from fastapi import APIRouter, Depends, File, Form, HTTPException
|
||||
from fastapi.logger import logger
|
||||
from fastapi.responses import FileResponse
|
||||
from schema.recipe import RecipeURLIn
|
||||
from schema.recipe import Recipe, RecipeURLIn
|
||||
from schema.snackbar import SnackResponse
|
||||
from services.image_services import read_image, write_image
|
||||
from services.recipe_services import Recipe
|
||||
from services.scraper.scraper import create_from_url
|
||||
from sqlalchemy.orm.session import Session
|
||||
from schema.snackbar import SnackResponse
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/api/recipes",
|
||||
|
@ -16,49 +16,47 @@ router = APIRouter(
|
|||
|
||||
|
||||
@router.post("/create", status_code=201, response_model=str)
|
||||
def create_from_json(data: Recipe, db: Session = Depends(generate_session)) -> str:
|
||||
def create_from_json(data: Recipe, session: Session = Depends(generate_session)) -> str:
|
||||
""" Takes in a JSON string and loads data into the database as a new entry"""
|
||||
new_recipe_slug = data.save_to_db(db)
|
||||
recipe: Recipe = db.recipes.create(session, data.dict())
|
||||
|
||||
return new_recipe_slug
|
||||
return recipe.slug
|
||||
|
||||
|
||||
@router.post("/create-url", status_code=201, response_model=str)
|
||||
def parse_recipe_url(url: RecipeURLIn, db: Session = Depends(generate_session)):
|
||||
def parse_recipe_url(url: RecipeURLIn, session: Session = Depends(generate_session)):
|
||||
""" Takes in a URL and attempts to scrape data and load it into the database """
|
||||
|
||||
recipe = create_from_url(url.url)
|
||||
|
||||
recipe.save_to_db(db)
|
||||
recipe: Recipe = db.recipes.create(session, recipe.dict())
|
||||
|
||||
return recipe.slug
|
||||
|
||||
|
||||
@router.get("/{recipe_slug}", response_model=Recipe)
|
||||
def get_recipe(recipe_slug: str, db: Session = Depends(generate_session)):
|
||||
def get_recipe(recipe_slug: str, session: Session = Depends(generate_session)):
|
||||
""" Takes in a recipe slug, returns all data for a recipe """
|
||||
recipe = Recipe.get_by_slug(db, recipe_slug)
|
||||
|
||||
return recipe
|
||||
return db.recipes.get(session, recipe_slug)
|
||||
|
||||
|
||||
@router.put("/{recipe_slug}")
|
||||
def update_recipe(
|
||||
recipe_slug: str, data: Recipe, db: Session = Depends(generate_session)
|
||||
recipe_slug: str, data: Recipe, session: Session = Depends(generate_session)
|
||||
):
|
||||
""" Updates a recipe by existing slug and data. """
|
||||
|
||||
new_slug = data.update(db, recipe_slug)
|
||||
recipe: Recipe = db.recipes.update(session, recipe_slug, data.dict())
|
||||
|
||||
return new_slug
|
||||
return recipe.slug
|
||||
|
||||
|
||||
@router.delete("/{recipe_slug}")
|
||||
def delete_recipe(recipe_slug: str, db: Session = Depends(generate_session)):
|
||||
def delete_recipe(recipe_slug: str, session: Session = Depends(generate_session)):
|
||||
""" Deletes a recipe by slug """
|
||||
|
||||
try:
|
||||
Recipe.delete(db, recipe_slug)
|
||||
db.recipes.delete(session, recipe_slug)
|
||||
except:
|
||||
raise HTTPException(
|
||||
status_code=404, detail=SnackResponse.error("Unable to Delete Recipe")
|
||||
|
@ -86,6 +84,6 @@ def update_recipe_image(
|
|||
):
|
||||
""" Removes an existing image and replaces it with the incoming file. """
|
||||
response = write_image(recipe_slug, image, extension)
|
||||
Recipe.update_image(session, recipe_slug, extension)
|
||||
db.recipes.update_image(session, recipe_slug, extension)
|
||||
|
||||
return response
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from typing import List, Optional
|
||||
|
||||
from pydantic.main import BaseModel
|
||||
from services.recipe_services import Recipe
|
||||
from schema.recipe import Recipe
|
||||
|
||||
|
||||
class RecipeCategoryResponse(BaseModel):
|
||||
|
|
|
@ -1,29 +1,47 @@
|
|||
from datetime import date
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, validator
|
||||
|
||||
|
||||
class Meal(BaseModel):
|
||||
slug: Optional[str]
|
||||
class MealIn(BaseModel):
|
||||
name: Optional[str]
|
||||
date: date
|
||||
dateText: str
|
||||
slug: Optional[str]
|
||||
date: Optional[date]
|
||||
|
||||
|
||||
class MealOut(MealIn):
|
||||
image: Optional[str]
|
||||
description: Optional[str]
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
class MealPlanBase(BaseModel):
|
||||
startDate: date
|
||||
endDate: date
|
||||
meals: List[MealIn]
|
||||
|
||||
@validator("endDate")
|
||||
def endDate_after_startDate(cls, v, values, **kwargs):
|
||||
if "startDate" in values and v < values["startDate"]:
|
||||
raise ValueError("EndDate should be greater than StartDate")
|
||||
return v
|
||||
|
||||
class MealPlanProcessed(MealPlanBase):
|
||||
meals: list[MealOut]
|
||||
|
||||
class MealPlanInDB(MealPlanProcessed):
|
||||
uid: str
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
class MealData(BaseModel):
|
||||
name: Optional[str]
|
||||
slug: str
|
||||
dateText: str
|
||||
|
||||
|
||||
class MealPlan(BaseModel):
|
||||
uid: Optional[str]
|
||||
startDate: date
|
||||
endDate: date
|
||||
meals: List[Meal]
|
||||
|
||||
class Config:
|
||||
schema_extra = {
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import datetime
|
||||
from typing import Any, List, Optional
|
||||
|
||||
from pydantic import BaseModel, validator
|
||||
from db.models.recipe.ingredient import RecipeIngredient
|
||||
from db.models.recipe.recipe import RecipeModel
|
||||
from pydantic import BaseModel, Schema, validator
|
||||
from pydantic.utils import GetterDict
|
||||
from slugify import slugify
|
||||
|
||||
|
||||
|
@ -9,10 +12,16 @@ class RecipeNote(BaseModel):
|
|||
title: str
|
||||
text: str
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
class RecipeStep(BaseModel):
|
||||
text: str
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
class Nutrition(BaseModel):
|
||||
calories: Optional[str]
|
||||
|
@ -22,6 +31,9 @@ class Nutrition(BaseModel):
|
|||
sodiumContent: Optional[str]
|
||||
sugarContent: Optional[str]
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
class Recipe(BaseModel):
|
||||
# Standard Schema
|
||||
|
@ -30,8 +42,8 @@ class Recipe(BaseModel):
|
|||
image: Optional[Any]
|
||||
recipeYield: Optional[str]
|
||||
recipeCategory: Optional[List[str]] = []
|
||||
recipeIngredient: Optional[list]
|
||||
recipeInstructions: Optional[list]
|
||||
recipeIngredient: Optional[list[str]]
|
||||
recipeInstructions: Optional[list[RecipeStep]]
|
||||
nutrition: Optional[Nutrition]
|
||||
|
||||
totalTime: Optional[str] = None
|
||||
|
@ -48,6 +60,18 @@ class Recipe(BaseModel):
|
|||
extras: Optional[dict] = {}
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
@classmethod
|
||||
def getter_dict(cls, name_orm: RecipeModel):
|
||||
print(name_orm.recipeIngredient)
|
||||
return {
|
||||
**GetterDict(name_orm),
|
||||
"recipeIngredient": [x.ingredient for x in name_orm.recipeIngredient],
|
||||
"recipeCategory": [x.name for x in name_orm.recipeCategory],
|
||||
"tags": [x.name for x in name_orm.tags]
|
||||
}
|
||||
|
||||
schema_extra = {
|
||||
"example": {
|
||||
"name": "Chicken and Rice With Leeks and Salsa Verde",
|
||||
|
|
|
@ -12,3 +12,6 @@ class SignUpToken(SignUpIn):
|
|||
|
||||
class SignUpOut(SignUpToken):
|
||||
id: int
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
|
|
@ -8,8 +8,7 @@ from db.database import db
|
|||
from db.db_setup import create_session
|
||||
from fastapi.logger import logger
|
||||
from jinja2 import Template
|
||||
from services.meal_services import MealPlan
|
||||
from services.recipe_services import Recipe
|
||||
from schema.recipe import Recipe
|
||||
|
||||
|
||||
class ExportDatabase:
|
||||
|
@ -57,15 +56,16 @@ class ExportDatabase:
|
|||
dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def export_recipes(self):
|
||||
all_recipes = Recipe.get_all(self.session)
|
||||
all_recipes = db.recipes.get_all(self.session)
|
||||
|
||||
for recipe in all_recipes:
|
||||
recipe: Recipe
|
||||
logger.info(f"Backing Up Recipes: {recipe}")
|
||||
|
||||
filename = recipe.get("slug") + ".json"
|
||||
filename = recipe.slug + ".json"
|
||||
file_path = self.recipe_dir.joinpath(filename)
|
||||
|
||||
ExportDatabase._write_json_file(recipe, file_path)
|
||||
ExportDatabase._write_json_file(recipe.dict(), file_path)
|
||||
|
||||
if self.templates:
|
||||
self._export_template(recipe)
|
||||
|
@ -101,7 +101,7 @@ class ExportDatabase:
|
|||
|
||||
def export_meals(self):
|
||||
#! Problem Parseing Datetime Objects... May come back to this
|
||||
meal_plans = MealPlan.get_all(self.session)
|
||||
meal_plans = db.meals.get_all(self.session)
|
||||
if meal_plans:
|
||||
meal_plans = [x.dict() for x in meal_plans]
|
||||
|
||||
|
|
|
@ -6,10 +6,11 @@ from typing import List
|
|||
|
||||
from core.config import BACKUP_DIR, IMG_DIR, TEMP_DIR
|
||||
from db.database import db
|
||||
from db.db_setup import create_session
|
||||
from fastapi.logger import logger
|
||||
from schema.recipe import Recipe
|
||||
from schema.restore import RecipeImport, SettingsImport, ThemeImport
|
||||
from schema.theme import SiteTheme
|
||||
from services.recipe_services import Recipe
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
|
||||
|
@ -75,6 +76,7 @@ class ImportDatabase:
|
|||
}
|
||||
|
||||
def import_recipes(self):
|
||||
session = create_session()
|
||||
recipe_dir: Path = self.import_dir.joinpath("recipes")
|
||||
|
||||
imports = []
|
||||
|
@ -88,10 +90,9 @@ class ImportDatabase:
|
|||
if recipe_dict.get("categories", False):
|
||||
recipe_dict["recipeCategory"] = recipe_dict.get("categories")
|
||||
del recipe_dict["categories"]
|
||||
print(recipe_dict)
|
||||
|
||||
recipe_obj = Recipe(**recipe_dict)
|
||||
recipe_obj.save_to_db(self.session)
|
||||
db.recipes.create(session, recipe_obj.dict())
|
||||
import_status = RecipeImport(
|
||||
name=recipe_obj.name, slug=recipe_obj.slug, status=True
|
||||
)
|
||||
|
|
|
@ -1,111 +1,43 @@
|
|||
from datetime import date, timedelta
|
||||
from typing import List, Optional
|
||||
|
||||
from db.database import db
|
||||
from pydantic import BaseModel, validator
|
||||
from schema.meal import MealIn, MealOut, MealPlanBase, MealPlanProcessed
|
||||
from schema.recipe import Recipe
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from services.recipe_services import Recipe
|
||||
|
||||
def process_meals(session: Session, meal_plan_base: MealPlanBase) -> MealPlanProcessed:
|
||||
meals = []
|
||||
for x, meal in enumerate(meal_plan_base.meals):
|
||||
meal: MealIn
|
||||
try:
|
||||
recipe: Recipe = db.recipes.get(session, meal.slug)
|
||||
|
||||
meal_data = MealOut(
|
||||
slug=recipe.slug,
|
||||
name=recipe.name,
|
||||
date=meal_plan_base.startDate + timedelta(days=x),
|
||||
image=recipe.image,
|
||||
description=recipe.description,
|
||||
)
|
||||
|
||||
except:
|
||||
|
||||
meal_data = MealOut(
|
||||
date=meal_plan_base.startDate + timedelta(days=x),
|
||||
)
|
||||
|
||||
meals.append(meal_data)
|
||||
|
||||
return MealPlanProcessed(
|
||||
meals=meals, startDate=meal_plan_base.startDate, endDate=meal_plan_base.endDate
|
||||
)
|
||||
|
||||
|
||||
class Meal(BaseModel):
|
||||
slug: Optional[str]
|
||||
name: Optional[str]
|
||||
date: date
|
||||
dateText: str
|
||||
image: Optional[str]
|
||||
description: Optional[str]
|
||||
def get_todays_meal(session):
|
||||
meal_plan = db.meals.get_all(session, limit=1, order_by="startDate")
|
||||
|
||||
|
||||
class MealData(BaseModel):
|
||||
name: Optional[str]
|
||||
slug: str
|
||||
dateText: str
|
||||
|
||||
|
||||
class MealPlan(BaseModel):
|
||||
uid: Optional[str]
|
||||
startDate: date
|
||||
endDate: date
|
||||
meals: List[Meal]
|
||||
|
||||
class Config:
|
||||
schema_extra = {
|
||||
"example": {
|
||||
"startDate": date.today(),
|
||||
"endDate": date.today(),
|
||||
"meals": [
|
||||
{"slug": "Packed Mac and Cheese", "date": date.today()},
|
||||
{"slug": "Eggs and Toast", "date": date.today()},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
@validator('endDate')
|
||||
def endDate_after_startDate(cls, v, values, **kwargs):
|
||||
if 'startDate' in values and v < values['startDate']:
|
||||
raise ValueError('EndDate should be greater than StartDate')
|
||||
return v
|
||||
|
||||
def process_meals(self, session: Session):
|
||||
meals = []
|
||||
for x, meal in enumerate(self.meals):
|
||||
|
||||
try:
|
||||
recipe = Recipe.get_by_slug(session, meal.slug)
|
||||
|
||||
meal_data = {
|
||||
"slug": recipe.slug,
|
||||
"name": recipe.name,
|
||||
"date": self.startDate + timedelta(days=x),
|
||||
"dateText": meal.dateText,
|
||||
"image": recipe.image,
|
||||
"description": recipe.description,
|
||||
}
|
||||
except:
|
||||
meal_data = {
|
||||
"date": self.startDate + timedelta(days=x),
|
||||
"dateText": meal.dateText,
|
||||
}
|
||||
|
||||
meals.append(Meal(**meal_data))
|
||||
|
||||
self.meals = meals
|
||||
|
||||
def save_to_db(self, session: Session):
|
||||
db.meals.create(session, self.dict())
|
||||
|
||||
@staticmethod
|
||||
def get_all(session: Session) -> List:
|
||||
|
||||
all_meals = [
|
||||
MealPlan(**x) for x in db.meals.get_all(session, order_by="startDate")
|
||||
]
|
||||
|
||||
return all_meals
|
||||
|
||||
def update(self, session, uid):
|
||||
db.meals.update(session, uid, self.dict())
|
||||
|
||||
@staticmethod
|
||||
def delete(session, uid):
|
||||
db.meals.delete(session, uid)
|
||||
|
||||
@staticmethod
|
||||
def today(session: Session) -> str:
|
||||
""" Returns the meal slug for Today """
|
||||
meal_plan = db.meals.get_all(session, limit=1, order_by="startDate")
|
||||
|
||||
meal_docs = [Meal(**meal) for meal in meal_plan["meals"]]
|
||||
|
||||
for meal in meal_docs:
|
||||
if meal.date == date.today():
|
||||
return meal.slug
|
||||
|
||||
return "No Meal Today"
|
||||
|
||||
@staticmethod
|
||||
def this_week(session: Session):
|
||||
meal_plan = db.meals.get_all(session, limit=1, order_by="startDate")
|
||||
|
||||
return meal_plan
|
||||
for meal in meal_plan:
|
||||
meal: MealOut
|
||||
if meal.date == date.today():
|
||||
return meal.slug
|
||||
|
|
|
@ -3,7 +3,8 @@ from pathlib import Path
|
|||
|
||||
import yaml
|
||||
from core.config import IMG_DIR, TEMP_DIR
|
||||
from services.recipe_services import Recipe
|
||||
from db.database import db
|
||||
from schema.recipe import Recipe
|
||||
from sqlalchemy.orm.session import Session
|
||||
from utils.unzip import unpack_zip
|
||||
|
||||
|
@ -49,32 +50,42 @@ def read_chowdown_file(recipe_file: Path) -> Recipe:
|
|||
"tags": recipe_data.get("tags").split(","),
|
||||
}
|
||||
|
||||
new_recipe = Recipe(**reformat_data)
|
||||
print(reformat_data)
|
||||
|
||||
reformated_list = []
|
||||
for instruction in new_recipe.recipeInstructions:
|
||||
for instruction in reformat_data["recipeInstructions"]:
|
||||
reformated_list.append({"text": instruction})
|
||||
reformat_data["recipeInstructions"] = reformated_list
|
||||
|
||||
new_recipe.recipeInstructions = reformated_list
|
||||
|
||||
return new_recipe
|
||||
return Recipe(**reformat_data)
|
||||
|
||||
|
||||
def chowdown_migrate(session: Session, zip_file: Path):
|
||||
temp_dir = unpack_zip(zip_file)
|
||||
|
||||
temp_dir = unpack_zip(zip_file)
|
||||
print(temp_dir.name)
|
||||
|
||||
path = Path(temp_dir.name)
|
||||
for p in path.iterdir():
|
||||
print("ItterDir", p)
|
||||
for p in p.iterdir():
|
||||
print("Sub Itter", p)
|
||||
with temp_dir as dir:
|
||||
image_dir = TEMP_DIR.joinpath(dir, zip_file.stem, "images")
|
||||
recipe_dir = TEMP_DIR.joinpath(dir, zip_file.stem, "_recipes")
|
||||
chow_dir = next(Path(dir).iterdir())
|
||||
image_dir = TEMP_DIR.joinpath(chow_dir, "images")
|
||||
recipe_dir = TEMP_DIR.joinpath(chow_dir, "_recipes")
|
||||
|
||||
print(image_dir.exists())
|
||||
print(recipe_dir.exists())
|
||||
|
||||
failed_recipes = []
|
||||
successful_recipes = []
|
||||
for recipe in recipe_dir.glob("*.md"):
|
||||
try:
|
||||
new_recipe = read_chowdown_file(recipe)
|
||||
new_recipe.save_to_db(session)
|
||||
successful_recipes.append(recipe.stem)
|
||||
except:
|
||||
db.recipes.create(session, new_recipe.dict())
|
||||
successful_recipes.append(new_recipe.name)
|
||||
except Exception as inst:
|
||||
failed_recipes.append(recipe.stem)
|
||||
|
||||
failed_images = []
|
||||
|
@ -82,7 +93,8 @@ def chowdown_migrate(session: Session, zip_file: Path):
|
|||
try:
|
||||
if not image.stem in failed_recipes:
|
||||
shutil.copy(image, IMG_DIR.joinpath(image.name))
|
||||
except:
|
||||
except Exception as inst:
|
||||
print(inst)
|
||||
failed_images.append(image.name)
|
||||
|
||||
report = {"successful": successful_recipes, "failed": failed_recipes}
|
||||
|
|
|
@ -5,9 +5,10 @@ import zipfile
|
|||
from pathlib import Path
|
||||
|
||||
from core.config import IMG_DIR, MIGRATION_DIR, TEMP_DIR
|
||||
from services.recipe_services import Recipe
|
||||
from schema.recipe import Recipe
|
||||
from services.scraper.cleaner import Cleaner
|
||||
from core.config import IMG_DIR, TEMP_DIR
|
||||
from db.database import db
|
||||
|
||||
|
||||
def process_selection(selection: Path) -> Path:
|
||||
|
@ -77,7 +78,8 @@ def migrate(session, selection: str):
|
|||
|
||||
try:
|
||||
recipe = import_recipes(dir)
|
||||
recipe.save_to_db(session)
|
||||
db.recipes.create(session, recipe.dict())
|
||||
|
||||
successful_imports.append(recipe.name)
|
||||
except:
|
||||
logging.error(f"Failed Nextcloud Import: {dir.name}")
|
||||
|
|
|
@ -1,141 +0,0 @@
|
|||
import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, List, Optional
|
||||
|
||||
from db.database import db
|
||||
from pydantic import BaseModel, validator
|
||||
from slugify import slugify
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from services.image_services import delete_image
|
||||
|
||||
|
||||
class RecipeNote(BaseModel):
|
||||
title: str
|
||||
text: str
|
||||
|
||||
|
||||
class RecipeStep(BaseModel):
|
||||
text: str
|
||||
|
||||
|
||||
class Nutrition(BaseModel):
|
||||
calories: Optional[str]
|
||||
fatContent: Optional[str]
|
||||
fiberContent: Optional[str]
|
||||
proteinContent: Optional[str]
|
||||
sodiumContent: Optional[str]
|
||||
sugarContent: Optional[str]
|
||||
|
||||
|
||||
class Recipe(BaseModel):
|
||||
# Standard Schema
|
||||
name: str
|
||||
description: Optional[str]
|
||||
image: Optional[Any]
|
||||
nutrition: Optional[Nutrition]
|
||||
recipeYield: Optional[str]
|
||||
recipeCategory: Optional[List[str]] = []
|
||||
recipeIngredient: Optional[list]
|
||||
recipeInstructions: Optional[list]
|
||||
tool: Optional[list[str]]
|
||||
|
||||
totalTime: Optional[str] = None
|
||||
prepTime: Optional[str] = None
|
||||
performTime: Optional[str] = None
|
||||
|
||||
# Mealie Specific
|
||||
slug: Optional[str] = ""
|
||||
tags: Optional[List[str]] = []
|
||||
dateAdded: Optional[datetime.date]
|
||||
notes: Optional[List[RecipeNote]] = []
|
||||
rating: Optional[int] = 0
|
||||
orgURL: Optional[str] = ""
|
||||
extras: Optional[dict] = {}
|
||||
|
||||
class Config:
|
||||
schema_extra = {
|
||||
"example": {
|
||||
"name": "Chicken and Rice With Leeks and Salsa Verde",
|
||||
"description": "This one-skillet dinner gets deep oniony flavor from lots of leeks cooked down to jammy tenderness.",
|
||||
"image": "chicken-and-rice-with-leeks-and-salsa-verde.jpg",
|
||||
"recipeYield": "4 Servings",
|
||||
"recipeIngredient": [
|
||||
"1 1/2 lb. skinless, boneless chicken thighs (4-8 depending on size)",
|
||||
"Kosher salt, freshly ground pepper",
|
||||
"3 Tbsp. unsalted butter, divided",
|
||||
],
|
||||
"recipeInstructions": [
|
||||
{
|
||||
"text": "Season chicken with salt and pepper.",
|
||||
},
|
||||
],
|
||||
"slug": "chicken-and-rice-with-leeks-and-salsa-verde",
|
||||
"tags": ["favorite", "yummy!"],
|
||||
"recipeCategory": ["Dinner", "Pasta"],
|
||||
"notes": [{"title": "Watch Out!", "text": "Prep the day before!"}],
|
||||
"orgURL": "https://www.bonappetit.com/recipe/chicken-and-rice-with-leeks-and-salsa-verde",
|
||||
"rating": 3,
|
||||
"extras": {"message": "Don't forget to defrost the chicken!"},
|
||||
}
|
||||
}
|
||||
|
||||
@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:
|
||||
return slug
|
||||
else:
|
||||
slug = calc_slug
|
||||
return slug
|
||||
|
||||
@classmethod
|
||||
def get_by_slug(cls, session, slug: str):
|
||||
""" Returns a Recipe Object by Slug """
|
||||
|
||||
document = db.recipes.get(session, slug, "slug")
|
||||
|
||||
return cls(**document)
|
||||
|
||||
def save_to_db(self, session) -> str:
|
||||
recipe_dict = self.dict()
|
||||
|
||||
try:
|
||||
extension = Path(recipe_dict["image"]).suffix
|
||||
recipe_dict["image"] = recipe_dict.get("slug") + extension
|
||||
except:
|
||||
recipe_dict["image"] = "no image"
|
||||
|
||||
recipe_doc = db.recipes.create(session, recipe_dict)
|
||||
recipe = Recipe(**recipe_doc)
|
||||
|
||||
return recipe.slug
|
||||
|
||||
@staticmethod
|
||||
def delete(session: Session, recipe_slug: str) -> str:
|
||||
""" Removes the recipe from the database by slug """
|
||||
delete_image(recipe_slug)
|
||||
db.recipes.delete(session, recipe_slug)
|
||||
return "Document Deleted"
|
||||
|
||||
def update(self, session: Session, recipe_slug: str):
|
||||
""" Updates the recipe from the database by slug"""
|
||||
updated_slug = db.recipes.update(session, recipe_slug, self.dict())
|
||||
return updated_slug.get("slug")
|
||||
|
||||
@staticmethod
|
||||
def update_image(session: Session, slug: str, extension: str = None) -> str:
|
||||
"""A helper function to pass the new image name and extension
|
||||
into the database.
|
||||
|
||||
Args:
|
||||
slug (str): The current recipe slug
|
||||
extension (str): the file extension of the new image
|
||||
"""
|
||||
return db.recipes.update_image(session, slug, extension)
|
||||
|
||||
@staticmethod
|
||||
def get_all(session: Session):
|
||||
return db.recipes.get_all(session)
|
|
@ -48,6 +48,8 @@ class Cleaner:
|
|||
recipe_data["slug"] = slugify(recipe_data.get("name"))
|
||||
recipe_data["orgURL"] = url
|
||||
|
||||
print(recipe_data["recipeIngredient"])
|
||||
|
||||
return recipe_data
|
||||
|
||||
@staticmethod
|
||||
|
|
|
@ -6,7 +6,7 @@ import scrape_schema_recipe
|
|||
from core.config import DEBUG_DIR
|
||||
from fastapi.logger import logger
|
||||
from services.image_services import scrape_image
|
||||
from services.recipe_services import Recipe
|
||||
from schema.recipe import Recipe
|
||||
from services.scraper import open_graph
|
||||
from services.scraper.cleaner import Cleaner
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
from pathlib import Path
|
||||
from core.config import TEMP_DIR
|
||||
|
||||
import pytest
|
||||
from core.config import TEMP_DIR
|
||||
from schema.recipe import Recipe
|
||||
from services.image_services import IMG_DIR
|
||||
from services.migrations.nextcloud import (
|
||||
cleanup,
|
||||
|
@ -9,7 +10,6 @@ from services.migrations.nextcloud import (
|
|||
prep,
|
||||
process_selection,
|
||||
)
|
||||
from services.recipe_services import Recipe
|
||||
from tests.test_config import TEST_NEXTCLOUD_DIR
|
||||
|
||||
CWD = Path(__file__).parent
|
||||
|
|
|
@ -19,12 +19,10 @@ def get_meal_plan_template(first=None, second=None):
|
|||
{
|
||||
"slug": first,
|
||||
"date": "2021-1-17",
|
||||
"dateText": "Monday, January 18, 2021",
|
||||
},
|
||||
{
|
||||
"slug": second,
|
||||
"date": "2021-1-18",
|
||||
"dateText": "Tueday, January 19, 2021",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
from pathlib import Path
|
||||
from core.config import TEMP_DIR
|
||||
|
||||
import pytest
|
||||
from core.config import TEMP_DIR
|
||||
from schema.recipe import Recipe
|
||||
from services.image_services import IMG_DIR
|
||||
from services.migrations.nextcloud import (
|
||||
cleanup,
|
||||
|
@ -9,7 +10,6 @@ from services.migrations.nextcloud import (
|
|||
prep,
|
||||
process_selection,
|
||||
)
|
||||
from services.recipe_services import Recipe
|
||||
from tests.test_config import TEST_NEXTCLOUD_DIR
|
||||
|
||||
CWD = Path(__file__).parent
|
||||
|
|
|
@ -4,8 +4,7 @@ import requests
|
|||
from db.database import db
|
||||
from db.db_setup import create_session
|
||||
from schema.settings import SiteSettings
|
||||
from services.meal_services import MealPlan
|
||||
from services.recipe_services import Recipe
|
||||
from services.meal_services import get_todays_meal
|
||||
|
||||
|
||||
def post_webhooks():
|
||||
|
@ -14,7 +13,8 @@ def post_webhooks():
|
|||
all_settings = SiteSettings(**all_settings)
|
||||
|
||||
if all_settings.webhooks.enabled:
|
||||
todays_meal = Recipe.get_by_slug(MealPlan.today()).dict()
|
||||
today_slug = get_todays_meal(session)
|
||||
todays_meal = db.recipes.get(session, today_slug)
|
||||
urls = all_settings.webhooks.webhookURLs
|
||||
|
||||
for url in urls:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue