refactor recipe schema/model

This commit is contained in:
hay-kot 2021-03-07 11:27:12 -09:00
commit 9a5fc25cb4
26 changed files with 221 additions and 396 deletions

View file

@ -7,6 +7,7 @@
<UploadBtn <UploadBtn
class="mt-1" class="mt-1"
:url="`/api/migrations/${folder}/upload`" :url="`/api/migrations/${folder}/upload`"
fileName="archive"
@uploaded="$emit('refresh')" @uploaded="$emit('refresh')"
/> />
</span> </span>

View file

@ -1,5 +1,5 @@
<template> <template>
<div v-if="items[0]"> <div v-if="items && items.length > 0">
<h2 class="mt-4">{{ title }}</h2> <h2 class="mt-4">{{ title }}</h2>
<v-chip <v-chip
class="ma-1" class="ma-1"

View file

@ -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 schema.user import GroupInDB, UserInDB
from sqlalchemy.orm.session import Session from sqlalchemy.orm.session import Session
@ -15,7 +18,8 @@ class _Recipes(BaseDocument):
def __init__(self) -> None: def __init__(self) -> None:
self.primary_key = "slug" self.primary_key = "slug"
self.sql_model: RecipeModel = RecipeModel 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: 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)
@ -43,7 +47,8 @@ class _Meals(BaseDocument):
def __init__(self) -> None: def __init__(self) -> None:
self.primary_key = "uid" self.primary_key = "uid"
self.sql_model = MealPlanModel self.sql_model = MealPlanModel
self.orm_mode = False self.orm_mode = True
self.schema = MealPlanInDB
class _Settings(BaseDocument): class _Settings(BaseDocument):
@ -70,10 +75,9 @@ class _Users(BaseDocument):
def update_password(self, session, id, password: str): def update_password(self, session, id, password: str):
entry = self._query_one(session=session, match_value=id) entry = self._query_one(session=session, match_value=id)
entry.update_password(password) entry.update_password(password)
return_data = entry.dict()
session.commit() session.commit()
return return_data return self.schema.from_orm(entry)
class _Groups(BaseDocument): class _Groups(BaseDocument):
@ -88,7 +92,8 @@ class _SignUps(BaseDocument):
def __init__(self) -> None: def __init__(self) -> None:
self.primary_key = "token" self.primary_key = "token"
self.sql_model = SignUp self.sql_model = SignUp
self.orm_mode = False self.orm_mode = True
self.schema = SignUpOut
class Database: class Database:

View file

@ -122,7 +122,7 @@ class BaseDocument:
return self.schema.from_orm(result[0]) return self.schema.from_orm(result[0])
except IndexError: except IndexError:
return None 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] db_entries = [x.dict() for x in result]

View file

@ -13,32 +13,16 @@ class Meal(SqlAlchemyBase):
slug = sa.Column(sa.String) slug = sa.Column(sa.String)
name = sa.Column(sa.String) name = sa.Column(sa.String)
date = sa.Column(sa.Date) date = sa.Column(sa.Date)
dateText = sa.Column(sa.String)
image = sa.Column(sa.String) image = sa.Column(sa.String)
description = sa.Column(sa.String) description = sa.Column(sa.String)
def __init__( def __init__(self, slug, name, date, image, description, session=None) -> None:
self, slug, name, date, dateText, image, description, session=None
) -> None:
self.slug = slug self.slug = slug
self.name = name self.name = name
self.date = date self.date = date
self.dateText = dateText
self.image = image self.image = image
self.description = description 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): class MealPlanModel(SqlAlchemyBase, BaseMixins):
__tablename__ = "mealplan" __tablename__ = "mealplan"
@ -58,13 +42,3 @@ class MealPlanModel(SqlAlchemyBase, BaseMixins):
MealPlanModel._sql_remove_list(session, [Meal], uid) MealPlanModel._sql_remove_list(session, [Meal], uid)
self.__init__(startDate, endDate, meals) 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

View file

@ -183,29 +183,29 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
extras=extras, extras=extras,
) )
def dict(self): # def dict(self):
data = { # data = {
"name": self.name, # "name": self.name,
"description": self.description, # "description": self.description,
"image": self.image, # "image": self.image,
"recipeYield": self.recipeYield, # "recipeYield": self.recipeYield,
"recipeCuisine": self.recipeCuisine, # "recipeCuisine": self.recipeCuisine,
"recipeCategory": [x.to_str() for x in self.recipeCategory], # "recipeCategory": [x.to_str() for x in self.recipeCategory],
"recipeIngredient": [x.to_str() for x in self.recipeIngredient], # "recipeIngredient": [x.to_str() for x in self.recipeIngredient],
"recipeInstructions": [x.dict() for x in self.recipeInstructions], # "recipeInstructions": [x.dict() for x in self.recipeInstructions],
"nutrition": self.nutrition.dict(), # "nutrition": self.nutrition.dict(),
"totalTime": self.totalTime, # "totalTime": self.totalTime,
"prepTime": self.prepTime, # "prepTime": self.prepTime,
"performTime": self.performTime, # "performTime": self.performTime,
"tool": [x.str() for x in self.tool], # "tool": [x.str() for x in self.tool],
# Mealie Specific # # Mealie Specific
"slug": self.slug, # "slug": self.slug,
"tags": [x.to_str() for x in self.tags], # "tags": [x.to_str() for x in self.tags],
"dateAdded": self.dateAdded, # "dateAdded": self.dateAdded,
"notes": [x.dict() for x in self.notes], # "notes": [x.dict() for x in self.notes],
"rating": self.rating, # "rating": self.rating,
"orgURL": self.orgURL, # "orgURL": self.orgURL,
"extras": RecipeModel._flatten_dict(self.extras), # "extras": RecipeModel._flatten_dict(self.extras),
} # }
return data # return data

View file

@ -1,5 +1,5 @@
from db.models.model_base import BaseMixins, SqlAlchemyBase 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): class SignUp(SqlAlchemyBase, BaseMixins):
@ -19,11 +19,3 @@ class SignUp(SqlAlchemyBase, BaseMixins):
self.token = token self.token = token
self.name = name self.name = name
self.admin = admin self.admin = admin
def dict(self):
return {
"id": self.id,
"name": self.name,
"token": self.token,
"admin": self.admin
}

View file

@ -5,11 +5,11 @@ from core.config import BACKUP_DIR, TEMPLATE_DIR
from db.db_setup import generate_session from db.db_setup import generate_session
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
from schema.backup import BackupJob, ImportJob, Imports, LocalBackup from schema.backup import BackupJob, ImportJob, Imports, LocalBackup
from schema.snackbar import SnackResponse
from services.backups.exports import backup_all from services.backups.exports import backup_all
from services.backups.imports import ImportDatabase from services.backups.imports import ImportDatabase
from sqlalchemy.orm.session import Session from sqlalchemy.orm.session import Session
from starlette.responses import FileResponse from starlette.responses import FileResponse
from schema.snackbar import SnackResponse
router = APIRouter(prefix="/api/backups", tags=["Backups"]) router = APIRouter(prefix="/api/backups", tags=["Backups"])

View file

@ -1,20 +1,22 @@
from datetime import date
from typing import List from typing import List
from db.database import db from db.database import db
from db.db_setup import generate_session 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 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 from sqlalchemy.orm.session import Session
router = APIRouter(prefix="/api/meal-plans", tags=["Meal Plan"]) 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)): def get_all_meals(session: Session = Depends(generate_session)):
""" Returns a list of all available Meal Plan """ """ Returns a list of all available Meal Plan """
return MealPlan.get_all(session) return db.meals.get_all(session)
@router.get("/{id}/shopping-list") @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 #! Refactor into Single Database Call
mealplan = db.meals.get(session, id) 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] recipes = [db.recipes.get(session, x) for x in slugs]
ingredients = [ ingredients = [
{"name": x.get("name"), "recipeIngredient": x.get("recipeIngredient")} {"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") @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 """ """ Creates a meal plan database entry """
data.process_meals(session) processed_plan = process_meals(session, data)
data.save_to_db(session) db.meals.create(session, processed_plan.dict())
return SnackResponse.success("Mealplan Created") 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)): def get_this_week(session: Session = Depends(generate_session)):
""" Returns the meal plan data for this week """ """ 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}") @router.put("/{plan_id}")
def update_meal_plan( 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 """ """ Updates a meal plan based off ID """
meal_plan.process_meals(session) processed_plan = process_meals(session, meal_plan)
meal_plan.update(session, plan_id) processed_plan = MealPlanInDB(uid=plan_id, **processed_plan.dict())
db.meals.update(session, plan_id, processed_plan.dict())
return SnackResponse.info("Mealplan Updated") return SnackResponse.info("Mealplan Updated")
@ -64,7 +68,7 @@ def update_meal_plan(
def delete_meal_plan(plan_id, session: Session = Depends(generate_session)): def delete_meal_plan(plan_id, session: Session = Depends(generate_session)):
""" Removes a meal plan from the database """ """ Removes a meal plan from the database """
MealPlan.delete(session, plan_id) db.meals.delete(session, plan_id)
return SnackResponse.error("Mealplan Deleted") 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 If no meal is scheduled nothing is returned
""" """
return MealPlan.today(session) return get_todays_meal(session)

View file

@ -1,13 +1,13 @@
from db.database import db
from db.db_setup import generate_session from db.db_setup import generate_session
from fastapi import APIRouter, Depends, File, Form, HTTPException from fastapi import APIRouter, Depends, File, Form, HTTPException
from fastapi.logger import logger from fastapi.logger import logger
from fastapi.responses import FileResponse 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.image_services import read_image, write_image
from services.recipe_services import Recipe
from services.scraper.scraper import create_from_url from services.scraper.scraper import create_from_url
from sqlalchemy.orm.session import Session from sqlalchemy.orm.session import Session
from schema.snackbar import SnackResponse
router = APIRouter( router = APIRouter(
prefix="/api/recipes", prefix="/api/recipes",
@ -16,49 +16,47 @@ router = APIRouter(
@router.post("/create", status_code=201, response_model=str) @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""" """ 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) @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 """ """ Takes in a URL and attempts to scrape data and load it into the database """
recipe = create_from_url(url.url) recipe = create_from_url(url.url)
recipe: Recipe = db.recipes.create(session, recipe.dict())
recipe.save_to_db(db)
return recipe.slug return recipe.slug
@router.get("/{recipe_slug}", response_model=Recipe) @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 """ """ 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}") @router.put("/{recipe_slug}")
def update_recipe( 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. """ """ 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}") @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 """ """ Deletes a recipe by slug """
try: try:
Recipe.delete(db, recipe_slug) db.recipes.delete(session, recipe_slug)
except: except:
raise HTTPException( raise HTTPException(
status_code=404, detail=SnackResponse.error("Unable to Delete Recipe") 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. """ """ Removes an existing image and replaces it with the incoming file. """
response = write_image(recipe_slug, image, extension) response = write_image(recipe_slug, image, extension)
Recipe.update_image(session, recipe_slug, extension) db.recipes.update_image(session, recipe_slug, extension)
return response return response

View file

@ -1,7 +1,7 @@
from typing import List, Optional from typing import List, Optional
from pydantic.main import BaseModel from pydantic.main import BaseModel
from services.recipe_services import Recipe from schema.recipe import Recipe
class RecipeCategoryResponse(BaseModel): class RecipeCategoryResponse(BaseModel):

View file

@ -1,29 +1,47 @@
from datetime import date from datetime import date
from typing import List, Optional from typing import List, Optional
from pydantic import BaseModel from pydantic import BaseModel, validator
class Meal(BaseModel): class MealIn(BaseModel):
slug: Optional[str]
name: Optional[str] name: Optional[str]
date: date slug: Optional[str]
dateText: str date: Optional[date]
class MealOut(MealIn):
image: Optional[str] image: Optional[str]
description: 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): class MealPlan(BaseModel):
uid: Optional[str] uid: Optional[str]
startDate: date
endDate: date
meals: List[Meal]
class Config: class Config:
schema_extra = { schema_extra = {

View file

@ -1,7 +1,10 @@
import datetime import datetime
from typing import Any, List, Optional 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 from slugify import slugify
@ -9,10 +12,16 @@ class RecipeNote(BaseModel):
title: str title: str
text: str text: str
class Config:
orm_mode = True
class RecipeStep(BaseModel): class RecipeStep(BaseModel):
text: str text: str
class Config:
orm_mode = True
class Nutrition(BaseModel): class Nutrition(BaseModel):
calories: Optional[str] calories: Optional[str]
@ -22,6 +31,9 @@ class Nutrition(BaseModel):
sodiumContent: Optional[str] sodiumContent: Optional[str]
sugarContent: Optional[str] sugarContent: Optional[str]
class Config:
orm_mode = True
class Recipe(BaseModel): class Recipe(BaseModel):
# Standard Schema # Standard Schema
@ -30,8 +42,8 @@ class Recipe(BaseModel):
image: Optional[Any] image: Optional[Any]
recipeYield: Optional[str] recipeYield: Optional[str]
recipeCategory: Optional[List[str]] = [] recipeCategory: Optional[List[str]] = []
recipeIngredient: Optional[list] recipeIngredient: Optional[list[str]]
recipeInstructions: Optional[list] recipeInstructions: Optional[list[RecipeStep]]
nutrition: Optional[Nutrition] nutrition: Optional[Nutrition]
totalTime: Optional[str] = None totalTime: Optional[str] = None
@ -48,6 +60,18 @@ class Recipe(BaseModel):
extras: Optional[dict] = {} extras: Optional[dict] = {}
class Config: 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 = { schema_extra = {
"example": { "example": {
"name": "Chicken and Rice With Leeks and Salsa Verde", "name": "Chicken and Rice With Leeks and Salsa Verde",

View file

@ -12,3 +12,6 @@ class SignUpToken(SignUpIn):
class SignUpOut(SignUpToken): class SignUpOut(SignUpToken):
id: int id: int
class Config:
orm_mode = True

View file

@ -8,8 +8,7 @@ from db.database import db
from db.db_setup import create_session from db.db_setup import create_session
from fastapi.logger import logger from fastapi.logger import logger
from jinja2 import Template from jinja2 import Template
from services.meal_services import MealPlan from schema.recipe import Recipe
from services.recipe_services import Recipe
class ExportDatabase: class ExportDatabase:
@ -57,15 +56,16 @@ class ExportDatabase:
dir.mkdir(parents=True, exist_ok=True) dir.mkdir(parents=True, exist_ok=True)
def export_recipes(self): def export_recipes(self):
all_recipes = Recipe.get_all(self.session) all_recipes = db.recipes.get_all(self.session)
for recipe in all_recipes: for recipe in all_recipes:
recipe: Recipe
logger.info(f"Backing Up Recipes: {recipe}") logger.info(f"Backing Up Recipes: {recipe}")
filename = recipe.get("slug") + ".json" filename = recipe.slug + ".json"
file_path = self.recipe_dir.joinpath(filename) 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: if self.templates:
self._export_template(recipe) self._export_template(recipe)
@ -101,7 +101,7 @@ class ExportDatabase:
def export_meals(self): def export_meals(self):
#! Problem Parseing Datetime Objects... May come back to this #! 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: if meal_plans:
meal_plans = [x.dict() for x in meal_plans] meal_plans = [x.dict() for x in meal_plans]

View file

@ -6,10 +6,11 @@ from typing import List
from core.config import BACKUP_DIR, IMG_DIR, TEMP_DIR from core.config import BACKUP_DIR, IMG_DIR, TEMP_DIR
from db.database import db from db.database import db
from db.db_setup import create_session
from fastapi.logger import logger from fastapi.logger import logger
from schema.recipe import Recipe
from schema.restore import RecipeImport, SettingsImport, ThemeImport from schema.restore import RecipeImport, SettingsImport, ThemeImport
from schema.theme import SiteTheme from schema.theme import SiteTheme
from services.recipe_services import Recipe
from sqlalchemy.orm.session import Session from sqlalchemy.orm.session import Session
@ -75,6 +76,7 @@ class ImportDatabase:
} }
def import_recipes(self): def import_recipes(self):
session = create_session()
recipe_dir: Path = self.import_dir.joinpath("recipes") recipe_dir: Path = self.import_dir.joinpath("recipes")
imports = [] imports = []
@ -88,10 +90,9 @@ class ImportDatabase:
if recipe_dict.get("categories", False): if recipe_dict.get("categories", False):
recipe_dict["recipeCategory"] = recipe_dict.get("categories") recipe_dict["recipeCategory"] = recipe_dict.get("categories")
del recipe_dict["categories"] del recipe_dict["categories"]
print(recipe_dict)
recipe_obj = Recipe(**recipe_dict) recipe_obj = Recipe(**recipe_dict)
recipe_obj.save_to_db(self.session) db.recipes.create(session, recipe_obj.dict())
import_status = RecipeImport( import_status = RecipeImport(
name=recipe_obj.name, slug=recipe_obj.slug, status=True name=recipe_obj.name, slug=recipe_obj.slug, status=True
) )

View file

@ -1,111 +1,43 @@
from datetime import date, timedelta from datetime import date, timedelta
from typing import List, Optional
from db.database import db 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 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): def get_todays_meal(session):
slug: Optional[str] meal_plan = db.meals.get_all(session, limit=1, order_by="startDate")
name: Optional[str]
date: date
dateText: str
image: Optional[str]
description: Optional[str]
for meal in meal_plan:
class MealData(BaseModel): meal: MealOut
name: Optional[str] if meal.date == date.today():
slug: str return meal.slug
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

View file

@ -3,7 +3,8 @@ from pathlib import Path
import yaml import yaml
from core.config import IMG_DIR, TEMP_DIR 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 sqlalchemy.orm.session import Session
from utils.unzip import unpack_zip from utils.unzip import unpack_zip
@ -49,32 +50,42 @@ def read_chowdown_file(recipe_file: Path) -> Recipe:
"tags": recipe_data.get("tags").split(","), "tags": recipe_data.get("tags").split(","),
} }
new_recipe = Recipe(**reformat_data) print(reformat_data)
reformated_list = [] reformated_list = []
for instruction in new_recipe.recipeInstructions: for instruction in reformat_data["recipeInstructions"]:
reformated_list.append({"text": instruction}) reformated_list.append({"text": instruction})
reformat_data["recipeInstructions"] = reformated_list
new_recipe.recipeInstructions = reformated_list return Recipe(**reformat_data)
return new_recipe
def chowdown_migrate(session: Session, zip_file: Path): 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: with temp_dir as dir:
image_dir = TEMP_DIR.joinpath(dir, zip_file.stem, "images") chow_dir = next(Path(dir).iterdir())
recipe_dir = TEMP_DIR.joinpath(dir, zip_file.stem, "_recipes") 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 = [] failed_recipes = []
successful_recipes = [] successful_recipes = []
for recipe in recipe_dir.glob("*.md"): for recipe in recipe_dir.glob("*.md"):
try: try:
new_recipe = read_chowdown_file(recipe) new_recipe = read_chowdown_file(recipe)
new_recipe.save_to_db(session) db.recipes.create(session, new_recipe.dict())
successful_recipes.append(recipe.stem) successful_recipes.append(new_recipe.name)
except: except Exception as inst:
failed_recipes.append(recipe.stem) failed_recipes.append(recipe.stem)
failed_images = [] failed_images = []
@ -82,7 +93,8 @@ def chowdown_migrate(session: Session, zip_file: Path):
try: try:
if not image.stem in failed_recipes: if not image.stem in failed_recipes:
shutil.copy(image, IMG_DIR.joinpath(image.name)) shutil.copy(image, IMG_DIR.joinpath(image.name))
except: except Exception as inst:
print(inst)
failed_images.append(image.name) failed_images.append(image.name)
report = {"successful": successful_recipes, "failed": failed_recipes} report = {"successful": successful_recipes, "failed": failed_recipes}

View file

@ -5,9 +5,10 @@ import zipfile
from pathlib import Path from pathlib import Path
from core.config import IMG_DIR, MIGRATION_DIR, TEMP_DIR 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 services.scraper.cleaner import Cleaner
from core.config import IMG_DIR, TEMP_DIR from core.config import IMG_DIR, TEMP_DIR
from db.database import db
def process_selection(selection: Path) -> Path: def process_selection(selection: Path) -> Path:
@ -77,7 +78,8 @@ def migrate(session, selection: str):
try: try:
recipe = import_recipes(dir) recipe = import_recipes(dir)
recipe.save_to_db(session) db.recipes.create(session, recipe.dict())
successful_imports.append(recipe.name) successful_imports.append(recipe.name)
except: except:
logging.error(f"Failed Nextcloud Import: {dir.name}") logging.error(f"Failed Nextcloud Import: {dir.name}")

View file

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

View file

@ -48,6 +48,8 @@ class Cleaner:
recipe_data["slug"] = slugify(recipe_data.get("name")) recipe_data["slug"] = slugify(recipe_data.get("name"))
recipe_data["orgURL"] = url recipe_data["orgURL"] = url
print(recipe_data["recipeIngredient"])
return recipe_data return recipe_data
@staticmethod @staticmethod

View file

@ -6,7 +6,7 @@ import scrape_schema_recipe
from core.config import DEBUG_DIR from core.config import DEBUG_DIR
from fastapi.logger import logger from fastapi.logger import logger
from services.image_services import scrape_image 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 import open_graph
from services.scraper.cleaner import Cleaner from services.scraper.cleaner import Cleaner

View file

@ -1,7 +1,8 @@
from pathlib import Path from pathlib import Path
from core.config import TEMP_DIR
import pytest import pytest
from core.config import TEMP_DIR from core.config import TEMP_DIR
from schema.recipe import Recipe
from services.image_services import IMG_DIR from services.image_services import IMG_DIR
from services.migrations.nextcloud import ( from services.migrations.nextcloud import (
cleanup, cleanup,
@ -9,7 +10,6 @@ from services.migrations.nextcloud import (
prep, prep,
process_selection, process_selection,
) )
from services.recipe_services import Recipe
from tests.test_config import TEST_NEXTCLOUD_DIR from tests.test_config import TEST_NEXTCLOUD_DIR
CWD = Path(__file__).parent CWD = Path(__file__).parent

View file

@ -19,12 +19,10 @@ def get_meal_plan_template(first=None, second=None):
{ {
"slug": first, "slug": first,
"date": "2021-1-17", "date": "2021-1-17",
"dateText": "Monday, January 18, 2021",
}, },
{ {
"slug": second, "slug": second,
"date": "2021-1-18", "date": "2021-1-18",
"dateText": "Tueday, January 19, 2021",
}, },
], ],
} }

View file

@ -1,7 +1,8 @@
from pathlib import Path from pathlib import Path
from core.config import TEMP_DIR
import pytest import pytest
from core.config import TEMP_DIR from core.config import TEMP_DIR
from schema.recipe import Recipe
from services.image_services import IMG_DIR from services.image_services import IMG_DIR
from services.migrations.nextcloud import ( from services.migrations.nextcloud import (
cleanup, cleanup,
@ -9,7 +10,6 @@ from services.migrations.nextcloud import (
prep, prep,
process_selection, process_selection,
) )
from services.recipe_services import Recipe
from tests.test_config import TEST_NEXTCLOUD_DIR from tests.test_config import TEST_NEXTCLOUD_DIR
CWD = Path(__file__).parent CWD = Path(__file__).parent

View file

@ -4,8 +4,7 @@ import requests
from db.database import db from db.database import db
from db.db_setup import create_session from db.db_setup import create_session
from schema.settings import SiteSettings from schema.settings import SiteSettings
from services.meal_services import MealPlan from services.meal_services import get_todays_meal
from services.recipe_services import Recipe
def post_webhooks(): def post_webhooks():
@ -14,7 +13,8 @@ def post_webhooks():
all_settings = SiteSettings(**all_settings) all_settings = SiteSettings(**all_settings)
if all_settings.webhooks.enabled: 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 urls = all_settings.webhooks.webhookURLs
for url in urls: for url in urls: