recipe assets first pass

This commit is contained in:
hay-kot 2021-04-28 18:02:22 -08:00
commit 4f2b6ebdb4
7 changed files with 81 additions and 21 deletions

1
.gitignore vendored
View file

@ -159,3 +159,4 @@ scratch.py
dev/data/backups/dev_sample_data*.zip dev/data/backups/dev_sample_data*.zip
dev/data/backups/dev_sample_data*.zip dev/data/backups/dev_sample_data*.zip
!dev/data/backups/test*.zip !dev/data/backups/test*.zip
dev/data/recipes/*

View file

@ -8,7 +8,7 @@ from mealie.core.config import APP_VERSION, settings
from mealie.routes import backup_routes, debug_routes, migration_routes, theme_routes, utility_routes from mealie.routes import backup_routes, debug_routes, migration_routes, theme_routes, utility_routes
from mealie.routes.groups import groups from mealie.routes.groups import groups
from mealie.routes.mealplans import mealplans from mealie.routes.mealplans import mealplans
from mealie.routes.recipe import all_recipe_routes, category_routes, recipe_crud_routes, tag_routes from mealie.routes.recipe import all_recipe_routes, category_routes, recipe_assets, recipe_crud_routes, tag_routes
from mealie.routes.site_settings import all_settings from mealie.routes.site_settings import all_settings
from mealie.routes.users import users from mealie.routes.users import users
@ -37,6 +37,7 @@ def api_routers():
app.include_router(category_routes.router) app.include_router(category_routes.router)
app.include_router(tag_routes.router) app.include_router(tag_routes.router)
app.include_router(recipe_crud_routes.router) app.include_router(recipe_crud_routes.router)
app.include_router(recipe_assets.router)
# Meal Routes # Meal Routes
app.include_router(mealplans.router) app.include_router(mealplans.router)
# Settings Routes # Settings Routes

View file

@ -8,13 +8,15 @@ class RecipeAsset(SqlAlchemyBase):
parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id")) parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id"))
name = sa.Column(sa.String) name = sa.Column(sa.String)
icon = sa.Column(sa.String) icon = sa.Column(sa.String)
file_name = sa.Column(sa.String)
def __init__( def __init__(
self, self,
name=None, name=None,
icon=None, icon=None,
file_name=None,
) -> None: ) -> None:
print("Asset Saved", name)
self.name = name self.name = name
self.file_name = file_name
self.icon = icon self.icon = icon

View file

@ -1,6 +1,5 @@
import datetime import datetime
from datetime import date from datetime import date
from typing import List
import sqlalchemy as sa import sqlalchemy as sa
import sqlalchemy.orm as orm import sqlalchemy.orm as orm
@ -33,17 +32,18 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
cookTime = sa.Column(sa.String) cookTime = sa.Column(sa.String)
recipeYield = sa.Column(sa.String) recipeYield = sa.Column(sa.String)
recipeCuisine = sa.Column(sa.String) recipeCuisine = sa.Column(sa.String)
tools: List[Tool] = orm.relationship("Tool", cascade="all, delete-orphan") tools: list[Tool] = orm.relationship("Tool", cascade="all, delete-orphan")
assets: list[RecipeAsset] = orm.relationship("RecipeAsset", cascade="all, delete-orphan")
nutrition: Nutrition = orm.relationship("Nutrition", uselist=False, cascade="all, delete-orphan") nutrition: Nutrition = orm.relationship("Nutrition", uselist=False, cascade="all, delete-orphan")
recipeCategory: List = orm.relationship("Category", secondary=recipes2categories, back_populates="recipes") recipeCategory: list = orm.relationship("Category", secondary=recipes2categories, back_populates="recipes")
recipeIngredient: List[RecipeIngredient] = orm.relationship( recipeIngredient: list[RecipeIngredient] = orm.relationship(
"RecipeIngredient", "RecipeIngredient",
cascade="all, delete-orphan", cascade="all, delete-orphan",
order_by="RecipeIngredient.position", order_by="RecipeIngredient.position",
collection_class=ordering_list("position"), collection_class=ordering_list("position"),
) )
recipeInstructions: List[RecipeInstruction] = orm.relationship( recipeInstructions: list[RecipeInstruction] = orm.relationship(
"RecipeInstruction", "RecipeInstruction",
cascade="all, delete-orphan", cascade="all, delete-orphan",
order_by="RecipeInstruction.position", order_by="RecipeInstruction.position",
@ -52,12 +52,12 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
# Mealie Specific # Mealie Specific
slug = sa.Column(sa.String, index=True, unique=True) slug = sa.Column(sa.String, index=True, unique=True)
tags: List[Tag] = orm.relationship("Tag", secondary=recipes2tags, back_populates="recipes") tags: list[Tag] = orm.relationship("Tag", secondary=recipes2tags, back_populates="recipes")
dateAdded = sa.Column(sa.Date, default=date.today) dateAdded = sa.Column(sa.Date, default=date.today)
notes: List[Note] = orm.relationship("Note", cascade="all, delete-orphan") notes: list[Note] = orm.relationship("Note", cascade="all, delete-orphan")
rating = sa.Column(sa.Integer) rating = sa.Column(sa.Integer)
orgURL = sa.Column(sa.String) orgURL = sa.Column(sa.String)
extras: List[ApiExtras] = orm.relationship("ApiExtras", cascade="all, delete-orphan") extras: list[ApiExtras] = orm.relationship("ApiExtras", cascade="all, delete-orphan")
@validates("name") @validates("name")
def validate_name(self, key, name): def validate_name(self, key, name):
@ -71,19 +71,19 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
description: str = None, description: str = None,
image: str = None, image: str = None,
recipeYield: str = None, recipeYield: str = None,
recipeIngredient: List[str] = None, recipeIngredient: list[str] = None,
recipeInstructions: List[dict] = None, recipeInstructions: list[dict] = None,
recipeCuisine: str = None, recipeCuisine: str = None,
totalTime: str = None, totalTime: str = None,
prepTime: str = None, prepTime: str = None,
nutrition: dict = None, nutrition: dict = None,
tools: list[str] = [], tools: list[str] = None,
performTime: str = None, performTime: str = None,
slug: str = None, slug: str = None,
recipeCategory: List[str] = None, recipeCategory: list[str] = None,
tags: List[str] = None, tags: list[str] = None,
dateAdded: datetime.date = None, dateAdded: datetime.date = None,
notes: List[dict] = None, notes: list[dict] = None,
rating: int = None, rating: int = None,
orgURL: str = None, orgURL: str = None,
extras: dict = None, extras: dict = None,
@ -101,7 +101,7 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
self.recipeYield = recipeYield self.recipeYield = recipeYield
self.recipeIngredient = [RecipeIngredient(ingredient=ingr) for ingr in recipeIngredient] self.recipeIngredient = [RecipeIngredient(ingredient=ingr) for ingr in recipeIngredient]
self.assets = [RecipeAsset(name=a.get("name"), icon=a.get("icon")) for a in assets] self.assets = [RecipeAsset(**a) for a in assets]
self.recipeInstructions = [ self.recipeInstructions = [
RecipeInstruction(text=instruc.get("text"), title=instruc.get("title"), type=instruc.get("@type", None)) RecipeInstruction(text=instruc.get("text"), title=instruc.get("title"), type=instruc.get("@type", None))
for instruc in recipeInstructions for instruc in recipeInstructions

View file

@ -0,0 +1,52 @@
import shutil
from fastapi import APIRouter, Depends, File, Form
from fastapi.datastructures import UploadFile
from fastapi.routing import run_endpoint_function
from mealie.core.config import app_dirs
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.recipe import Recipe, RecipeAsset
from mealie.schema.snackbar import SnackResponse
from slugify import slugify
from sqlalchemy.orm.session import Session
from starlette.responses import FileResponse
router = APIRouter(prefix="/api/recipes", tags=["Recipe Assets"])
@router.get("/{recipe_slug}/asset")
async def get_recipe_asset(recipe_slug, file_name: str):
""" Returns a recipe asset """
file = app_dirs.RECIPE_DATA_DIR.joinpath(recipe_slug, file_name)
return FileResponse(file)
@router.post("/{recipe_slug}/asset", response_model=RecipeAsset)
def upload_recipe_asset(
recipe_slug: str,
name: str = Form(...),
icon: str = Form(...),
extension: str = Form(...),
file: UploadFile = File(...),
session: Session = Depends(generate_session),
current_user=Depends(get_current_user),
):
""" Upload a file to store as a recipe asset """
file_name = slugify(name) + "." + extension
asset_in = RecipeAsset(name=name, icon=icon, file_name=file_name)
dest = app_dirs.RECIPE_DATA_DIR.joinpath(recipe_slug, file_name)
dest.parent.mkdir(exist_ok=True, parents=True)
with dest.open("wb") as buffer:
shutil.copyfileobj(file.file, buffer)
if dest.is_file():
recipe: Recipe = db.recipes.get(session, recipe_slug)
recipe.assets.append(asset_in)
db.recipes.update(session, recipe_slug, recipe.dict())
return asset_in
else:
return SnackResponse.error("Failure uploading file")

View file

@ -57,6 +57,7 @@ def update_recipe(
""" Updates a recipe by existing slug and data. """ """ Updates a recipe by existing slug and data. """
recipe: Recipe = db.recipes.update(session, recipe_slug, data.dict()) recipe: Recipe = db.recipes.update(session, recipe_slug, data.dict())
print(recipe.assets)
if recipe_slug != recipe.slug: if recipe_slug != recipe.slug:
rename_image(original_slug=recipe_slug, new_slug=recipe.slug) rename_image(original_slug=recipe_slug, new_slug=recipe.slug)
@ -65,7 +66,7 @@ def update_recipe(
@router.patch("/{recipe_slug}") @router.patch("/{recipe_slug}")
def update_recipe( def patch_recipe(
recipe_slug: str, recipe_slug: str,
data: dict, data: dict,
session: Session = Depends(generate_session), session: Session = Depends(generate_session),

View file

@ -1,6 +1,7 @@
import datetime import datetime
from typing import Any, List, Optional from typing import Any, List, Optional
from fastapi_camelcase import CamelModel
from mealie.db.models.recipe.recipe import RecipeModel from mealie.db.models.recipe.recipe import RecipeModel
from pydantic import BaseModel, validator from pydantic import BaseModel, validator
from pydantic.utils import GetterDict from pydantic.utils import GetterDict
@ -15,16 +16,18 @@ class RecipeNote(BaseModel):
orm_mode = True orm_mode = True
class RecipeStep(BaseModel): class RecipeStep(CamelModel):
title: Optional[str] = "" title: Optional[str] = ""
text: str text: str
class Config: class Config:
orm_mode = True orm_mode = True
class RecipeAsset(BaseModel):
class RecipeAsset(CamelModel):
name: str name: str
icon: str icon: str
file_name: Optional[str]
class Config: class Config:
orm_mode = True orm_mode = True