mirror of
https://github.com/hay-kot/mealie.git
synced 2025-08-22 06:23:34 -07:00
recipe assets first pass
This commit is contained in:
parent
3b02553aa2
commit
4f2b6ebdb4
7 changed files with 81 additions and 21 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -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/*
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
52
mealie/routes/recipe/recipe_assets.py
Normal file
52
mealie/routes/recipe/recipe_assets.py
Normal 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")
|
|
@ -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),
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue