mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-02-09 17:33:12 -05:00
Refactor/conver to controllers (#923)
* add dependency injection for get_repositories * convert events api to controller * update generic typing * add abstract controllers * update test naming * migrate admin services to controllers * add additional admin route tests * remove print * add public shared dependencies * add types * fix typo * add static variables for recipe json keys * add coverage gutters config * update controller routers * add generic success response * add category/tag/tool tests * add token refresh test * add coverage utilities * covert comments to controller * add todo * add helper properties * delete old service * update test notes * add unit test for pretty_stats * remove dead code from post_webhooks * update group routes to use controllers * add additional group test coverage * abstract common permission checks * convert ingredient parser to controller * update recipe crud to use controller * remove dead-code * add class lifespan tracker for debugging * convert bulk export to controller * migrate tools router to controller * update recipe share to controller * move customer router to _base * ignore prints in flake8 * convert units and foods to new controllers * migrate user routes to controllers * centralize error handling * fix invalid ref * reorder fields * update routers to share common handling * update tests * remove prints * fix cookbooks delete * fix cookbook get * add controller for mealplanner * cover report routes to controller * remove __future__ imports * remove dead code * remove all base_http children and remove dead code
This commit is contained in:
@@ -1,40 +0,0 @@
|
||||
from mealie.schema.recipe import Recipe
|
||||
from mealie.schema.recipe.recipe_ingredient import RecipeIngredient
|
||||
from mealie.schema.recipe.recipe_step import RecipeStep
|
||||
from mealie.schema.user.user import PrivateUser
|
||||
|
||||
step_text = """Recipe steps as well as other fields in the recipe page support markdown syntax.
|
||||
|
||||
**Add a link**
|
||||
|
||||
[My Link](https://beta.mealie.io)
|
||||
|
||||
**Imbed an image**
|
||||
|
||||
Use the `height="100"` or `width="100"` attributes to set the size of the image.
|
||||
|
||||
<img height="100" src="https://images.unsplash.com/photo-1567620905732-2d1ec7ab7445?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=960&q=80"></img>
|
||||
|
||||
"""
|
||||
|
||||
ingredient_note = "1 Cup Flour"
|
||||
|
||||
|
||||
def recipe_creation_factory(user: PrivateUser, name: str, additional_attrs: dict = None) -> Recipe:
|
||||
"""
|
||||
The main creation point for recipes. The factor method returns an instance of the
|
||||
Recipe Schema class with the appropriate defaults set. Recipes shoudld not be created
|
||||
else-where to avoid conflicts.
|
||||
"""
|
||||
additional_attrs = additional_attrs or {}
|
||||
additional_attrs["name"] = name
|
||||
additional_attrs["user_id"] = user.id
|
||||
additional_attrs["group_id"] = user.group_id
|
||||
|
||||
if not additional_attrs.get("recipe_ingredient"):
|
||||
additional_attrs["recipe_ingredient"] = [RecipeIngredient(note=ingredient_note)]
|
||||
|
||||
if not additional_attrs.get("recipe_instructions"):
|
||||
additional_attrs["recipe_instructions"] = [RecipeStep(text=step_text)]
|
||||
|
||||
return Recipe(**additional_attrs)
|
||||
@@ -1,33 +1,29 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from mealie.core.root_logger import get_logger
|
||||
from mealie.repos.repository_factory import AllRepositories
|
||||
from mealie.schema.group.group_exports import GroupDataExport
|
||||
from mealie.schema.recipe import CategoryBase, Recipe
|
||||
from mealie.schema.recipe import CategoryBase
|
||||
from mealie.schema.recipe.recipe_category import TagBase
|
||||
from mealie.services._base_http_service.http_services import UserHttpService
|
||||
from mealie.services.events import create_recipe_event
|
||||
from mealie.schema.user.user import GroupInDB, PrivateUser
|
||||
from mealie.services._base_service import BaseService
|
||||
from mealie.services.exporter import Exporter, RecipeExporter
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class RecipeBulkActions(UserHttpService[int, Recipe]):
|
||||
event_func = create_recipe_event
|
||||
_restrict_by_group = True
|
||||
|
||||
def populate_item(self, _: int) -> Recipe:
|
||||
return
|
||||
class RecipeBulkActionsService(BaseService):
|
||||
def __init__(self, repos: AllRepositories, user: PrivateUser, group: GroupInDB):
|
||||
self.repos = repos
|
||||
self.user = user
|
||||
self.group = group
|
||||
super().__init__()
|
||||
|
||||
def export_recipes(self, temp_path: Path, slugs: list[str]) -> None:
|
||||
recipe_exporter = RecipeExporter(self.db, self.group_id, slugs)
|
||||
exporter = Exporter(self.group_id, temp_path, [recipe_exporter])
|
||||
recipe_exporter = RecipeExporter(self.repos, self.group.id, slugs)
|
||||
exporter = Exporter(self.group.id, temp_path, [recipe_exporter])
|
||||
|
||||
exporter.run(self.db)
|
||||
exporter.run(self.repos)
|
||||
|
||||
def get_exports(self) -> list[GroupDataExport]:
|
||||
return self.db.group_exports.multi_query({"group_id": self.group_id})
|
||||
return self.repos.group_exports.multi_query({"group_id": self.group.id})
|
||||
|
||||
def purge_exports(self) -> int:
|
||||
all_exports = self.get_exports()
|
||||
@@ -36,13 +32,13 @@ class RecipeBulkActions(UserHttpService[int, Recipe]):
|
||||
for export in all_exports:
|
||||
try:
|
||||
Path(export.path).unlink(missing_ok=True)
|
||||
self.db.group_exports.delete(export.id)
|
||||
self.repos.group_exports.delete(export.id)
|
||||
exports_deleted += 1
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to delete export {export.id}")
|
||||
logger.error(e)
|
||||
self.logger.error(f"Failed to delete export {export.id}")
|
||||
self.logger.error(e)
|
||||
|
||||
group = self.db.groups.get_one(self.group_id)
|
||||
group = self.repos.groups.get_one(self.group.id)
|
||||
|
||||
for match in group.directory.glob("**/export/*zip"):
|
||||
if match.is_file():
|
||||
@@ -53,38 +49,38 @@ class RecipeBulkActions(UserHttpService[int, Recipe]):
|
||||
|
||||
def assign_tags(self, recipes: list[str], tags: list[TagBase]) -> None:
|
||||
for slug in recipes:
|
||||
recipe = self.db.recipes.get_one(slug)
|
||||
recipe = self.repos.recipes.get_one(slug)
|
||||
|
||||
if recipe is None:
|
||||
logger.error(f"Failed to tag recipe {slug}, no recipe found")
|
||||
self.logger.error(f"Failed to tag recipe {slug}, no recipe found")
|
||||
|
||||
recipe.tags += tags
|
||||
|
||||
try:
|
||||
self.db.recipes.update(slug, recipe)
|
||||
self.repos.recipes.update(slug, recipe)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to tag recipe {slug}")
|
||||
logger.error(e)
|
||||
self.logger.error(f"Failed to tag recipe {slug}")
|
||||
self.logger.error(e)
|
||||
|
||||
def assign_categories(self, recipes: list[str], categories: list[CategoryBase]) -> None:
|
||||
for slug in recipes:
|
||||
recipe = self.db.recipes.get_one(slug)
|
||||
recipe = self.repos.recipes.get_one(slug)
|
||||
|
||||
if recipe is None:
|
||||
logger.error(f"Failed to categorize recipe {slug}, no recipe found")
|
||||
self.logger.error(f"Failed to categorize recipe {slug}, no recipe found")
|
||||
|
||||
recipe.recipe_category += categories
|
||||
|
||||
try:
|
||||
self.db.recipes.update(slug, recipe)
|
||||
self.repos.recipes.update(slug, recipe)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to categorize recipe {slug}")
|
||||
logger.error(e)
|
||||
self.logger.error(f"Failed to categorize recipe {slug}")
|
||||
self.logger.error(e)
|
||||
|
||||
def delete_recipes(self, recipes: list[str]) -> None:
|
||||
for slug in recipes:
|
||||
try:
|
||||
self.db.recipes.delete(slug)
|
||||
self.repos.recipes.delete(slug)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to delete recipe {slug}")
|
||||
logger.error(e)
|
||||
self.logger.error(f"Failed to delete recipe {slug}")
|
||||
self.logger.error(e)
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import cached_property
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import HTTPException
|
||||
|
||||
from mealie.schema.recipe.recipe_comments import (
|
||||
RecipeCommentCreate,
|
||||
RecipeCommentOut,
|
||||
RecipeCommentSave,
|
||||
RecipeCommentUpdate,
|
||||
)
|
||||
from mealie.services._base_http_service.crud_http_mixins import CrudHttpMixins
|
||||
from mealie.services._base_http_service.http_services import UserHttpService
|
||||
from mealie.services.events import create_recipe_event
|
||||
|
||||
|
||||
class RecipeCommentsService(
|
||||
CrudHttpMixins[RecipeCommentOut, RecipeCommentCreate, RecipeCommentCreate],
|
||||
UserHttpService[UUID, RecipeCommentOut],
|
||||
):
|
||||
event_func = create_recipe_event
|
||||
_restrict_by_group = False
|
||||
_schema = RecipeCommentOut
|
||||
|
||||
@cached_property
|
||||
def repo(self):
|
||||
return self.db.comments
|
||||
|
||||
def _check_comment_belongs_to_user(self) -> None:
|
||||
if self.item.user_id != self.user.id and not self.user.admin:
|
||||
raise HTTPException(detail="Comment does not belong to user")
|
||||
|
||||
def populate_item(self, id: UUID) -> RecipeCommentOut:
|
||||
self.item = self.repo.get_one(id)
|
||||
return self.item
|
||||
|
||||
def get_all(self) -> list[RecipeCommentOut]:
|
||||
return self.repo.get_all()
|
||||
|
||||
def create_one(self, data: RecipeCommentCreate) -> RecipeCommentOut:
|
||||
save_data = RecipeCommentSave(text=data.text, user_id=self.user.id, recipe_id=data.recipe_id)
|
||||
return self._create_one(save_data)
|
||||
|
||||
def update_one(self, data: RecipeCommentUpdate, item_id: UUID = None) -> RecipeCommentOut:
|
||||
self._check_comment_belongs_to_user()
|
||||
return self._update_one(data, item_id)
|
||||
|
||||
def delete_one(self, item_id: UUID = None) -> RecipeCommentOut:
|
||||
self._check_comment_belongs_to_user()
|
||||
return self._delete_one(item_id)
|
||||
@@ -1,37 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import cached_property
|
||||
|
||||
from mealie.schema.recipe.recipe_ingredient import CreateIngredientFood, IngredientFood
|
||||
from mealie.services._base_http_service.crud_http_mixins import CrudHttpMixins
|
||||
from mealie.services._base_http_service.http_services import UserHttpService
|
||||
from mealie.services.events import create_recipe_event
|
||||
|
||||
|
||||
class RecipeFoodService(
|
||||
CrudHttpMixins[IngredientFood, CreateIngredientFood, CreateIngredientFood],
|
||||
UserHttpService[int, IngredientFood],
|
||||
):
|
||||
event_func = create_recipe_event
|
||||
_restrict_by_group = False
|
||||
_schema = IngredientFood
|
||||
|
||||
@cached_property
|
||||
def repo(self):
|
||||
return self.db.ingredient_foods
|
||||
|
||||
def populate_item(self, id: int) -> IngredientFood:
|
||||
self.item = self.repo.get_one(id)
|
||||
return self.item
|
||||
|
||||
def get_all(self) -> list[IngredientFood]:
|
||||
return self.repo.get_all()
|
||||
|
||||
def create_one(self, data: CreateIngredientFood) -> IngredientFood:
|
||||
return self._create_one(data)
|
||||
|
||||
def update_one(self, data: IngredientFood, item_id: int = None) -> IngredientFood:
|
||||
return self._update_one(data, item_id)
|
||||
|
||||
def delete_one(self, id: int = None) -> IngredientFood:
|
||||
return self._delete_one(id)
|
||||
@@ -1,111 +1,123 @@
|
||||
import json
|
||||
import shutil
|
||||
from functools import cached_property
|
||||
from pathlib import Path
|
||||
from shutil import copytree, rmtree
|
||||
from typing import Union
|
||||
from zipfile import ZipFile
|
||||
|
||||
from fastapi import Depends, HTTPException, UploadFile, status
|
||||
from sqlalchemy import exc
|
||||
from fastapi import UploadFile
|
||||
|
||||
from mealie.core.dependencies.grouped import UserDeps
|
||||
from mealie.core.root_logger import get_logger
|
||||
from mealie.repos.repository_recipes import RepositoryRecipes
|
||||
from mealie.schema.recipe.recipe import CreateRecipe, Recipe, RecipeSummary
|
||||
from mealie.core import exceptions
|
||||
from mealie.repos.repository_factory import AllRepositories
|
||||
from mealie.schema.recipe.recipe import CreateRecipe, Recipe
|
||||
from mealie.schema.recipe.recipe_ingredient import RecipeIngredient
|
||||
from mealie.schema.recipe.recipe_settings import RecipeSettings
|
||||
from mealie.services._base_http_service.crud_http_mixins import CrudHttpMixins
|
||||
from mealie.services._base_http_service.http_services import UserHttpService
|
||||
from mealie.services.events import create_recipe_event
|
||||
from mealie.schema.recipe.recipe_step import RecipeStep
|
||||
from mealie.schema.user.user import GroupInDB, PrivateUser
|
||||
from mealie.services._base_service import BaseService
|
||||
from mealie.services.image.image import write_image
|
||||
from mealie.services.recipe.mixins import recipe_creation_factory
|
||||
|
||||
from .template_service import TemplateService
|
||||
|
||||
logger = get_logger(module=__name__)
|
||||
step_text = """Recipe steps as well as other fields in the recipe page support markdown syntax.
|
||||
|
||||
**Add a link**
|
||||
|
||||
[My Link](https://beta.mealie.io)
|
||||
|
||||
**Imbed an image**
|
||||
|
||||
Use the `height="100"` or `width="100"` attributes to set the size of the image.
|
||||
|
||||
<img height="100" src="https://images.unsplash.com/photo-1567620905732-2d1ec7ab7445?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=960&q=80"></img>
|
||||
|
||||
"""
|
||||
|
||||
ingredient_note = "1 Cup Flour"
|
||||
|
||||
|
||||
class RecipeService(CrudHttpMixins[CreateRecipe, Recipe, Recipe], UserHttpService[str, Recipe]):
|
||||
"""
|
||||
Class Methods:
|
||||
`read_existing`: Reads an existing recipe from the database.
|
||||
`write_existing`: Updates an existing recipe in the database.
|
||||
`base`: Requires write permissions, but doesn't perform recipe checks
|
||||
"""
|
||||
class RecipeService(BaseService):
|
||||
def __init__(self, repos: AllRepositories, user: PrivateUser, group: GroupInDB):
|
||||
self.repos = repos
|
||||
self.user = user
|
||||
self.group = group
|
||||
super().__init__()
|
||||
|
||||
event_func = create_recipe_event
|
||||
def _get_recipe(self, slug: str) -> Recipe:
|
||||
recipe = self.repos.recipes.by_group(self.group.id).get_one(slug)
|
||||
if recipe is None:
|
||||
raise exceptions.NoEntryFound("Recipe not found.")
|
||||
return recipe
|
||||
|
||||
@cached_property
|
||||
def exception_key(self) -> dict:
|
||||
return {exc.IntegrityError: self.t("recipe.unique-name-error")}
|
||||
def can_update(self, recipe: Recipe) -> bool:
|
||||
return recipe.settings.locked is False or self.user.id == recipe.user_id
|
||||
|
||||
@cached_property
|
||||
def repo(self) -> RepositoryRecipes:
|
||||
return self.db.recipes.by_group(self.group_id)
|
||||
def can_lock_unlock(self, recipe: Recipe) -> bool:
|
||||
return recipe.user_id == self.user.id
|
||||
|
||||
@classmethod
|
||||
def write_existing(cls, slug: str, deps: UserDeps = Depends()):
|
||||
return super().write_existing(slug, deps)
|
||||
def check_assets(self, recipe: Recipe, original_slug: str) -> None:
|
||||
"""Checks if the recipe slug has changed, and if so moves the assets to a new file with the new slug."""
|
||||
if original_slug != recipe.slug:
|
||||
current_dir = self.directories.RECIPE_DATA_DIR.joinpath(original_slug)
|
||||
|
||||
@classmethod
|
||||
def read_existing(cls, slug: str, deps: UserDeps = Depends()):
|
||||
return super().write_existing(slug, deps)
|
||||
try:
|
||||
copytree(current_dir, recipe.directory, dirs_exist_ok=True)
|
||||
self.logger.info(f"Renaming Recipe Directory: {original_slug} -> {recipe.slug}")
|
||||
except FileNotFoundError:
|
||||
self.logger.error(f"Recipe Directory not Found: {original_slug}")
|
||||
|
||||
def assert_existing(self, slug: str):
|
||||
self.populate_item(slug)
|
||||
if not self.item:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND)
|
||||
all_asset_files = [x.file_name for x in recipe.assets]
|
||||
|
||||
if not self.item.settings.public and not self.user:
|
||||
raise HTTPException(status.HTTP_403_FORBIDDEN)
|
||||
for file in recipe.asset_dir.iterdir():
|
||||
file: Path
|
||||
if file.is_dir():
|
||||
continue
|
||||
if file.name not in all_asset_files:
|
||||
file.unlink()
|
||||
|
||||
def can_update(self) -> bool:
|
||||
if self.item.settings.locked and self.user.id != self.item.user_id:
|
||||
raise HTTPException(status.HTTP_403_FORBIDDEN)
|
||||
def delete_assets(self, recipe: Recipe) -> None:
|
||||
recipe_dir = recipe.directory
|
||||
rmtree(recipe_dir, ignore_errors=True)
|
||||
self.logger.info(f"Recipe Directory Removed: {recipe.slug}")
|
||||
|
||||
return True
|
||||
@staticmethod
|
||||
def _recipe_creation_factory(user: PrivateUser, name: str, additional_attrs: dict = None) -> Recipe:
|
||||
"""
|
||||
The main creation point for recipes. The factor method returns an instance of the
|
||||
Recipe Schema class with the appropriate defaults set. Recipes shoudld not be created
|
||||
else-where to avoid conflicts.
|
||||
"""
|
||||
additional_attrs = additional_attrs or {}
|
||||
additional_attrs["name"] = name
|
||||
additional_attrs["user_id"] = user.id
|
||||
additional_attrs["group_id"] = user.group_id
|
||||
|
||||
def get_all(self, start=0, limit=None, load_foods=False) -> list[RecipeSummary]:
|
||||
items = self.db.recipes.summary(self.user.group_id, start=start, limit=limit, load_foods=load_foods)
|
||||
if not additional_attrs.get("recipe_ingredient"):
|
||||
additional_attrs["recipe_ingredient"] = [RecipeIngredient(note=ingredient_note)]
|
||||
|
||||
new_items = []
|
||||
if not additional_attrs.get("recipe_instructions"):
|
||||
additional_attrs["recipe_instructions"] = [RecipeStep(text=step_text)]
|
||||
|
||||
for item in items:
|
||||
# Pydantic/FastAPI can't seem to serialize the ingredient field on thier own.
|
||||
new_item = item.__dict__
|
||||
|
||||
if load_foods:
|
||||
new_item["recipe_ingredient"] = [x.__dict__ for x in item.recipe_ingredient]
|
||||
|
||||
new_items.append(new_item)
|
||||
|
||||
return [RecipeSummary.construct(**x) for x in new_items]
|
||||
return Recipe(**additional_attrs)
|
||||
|
||||
def create_one(self, create_data: Union[Recipe, CreateRecipe]) -> Recipe:
|
||||
group = self.db.groups.get(self.group_id, "id")
|
||||
|
||||
create_data: Recipe = recipe_creation_factory(
|
||||
create_data: Recipe = self._recipe_creation_factory(
|
||||
self.user,
|
||||
name=create_data.name,
|
||||
additional_attrs=create_data.dict(),
|
||||
)
|
||||
|
||||
create_data.settings = RecipeSettings(
|
||||
public=group.preferences.recipe_public,
|
||||
show_nutrition=group.preferences.recipe_show_nutrition,
|
||||
show_assets=group.preferences.recipe_show_assets,
|
||||
landscape_view=group.preferences.recipe_landscape_view,
|
||||
disable_comments=group.preferences.recipe_disable_comments,
|
||||
disable_amount=group.preferences.recipe_disable_amount,
|
||||
public=self.group.preferences.recipe_public,
|
||||
show_nutrition=self.group.preferences.recipe_show_nutrition,
|
||||
show_assets=self.group.preferences.recipe_show_assets,
|
||||
landscape_view=self.group.preferences.recipe_landscape_view,
|
||||
disable_comments=self.group.preferences.recipe_disable_comments,
|
||||
disable_amount=self.group.preferences.recipe_disable_amount,
|
||||
)
|
||||
|
||||
self._create_one(create_data, self.t("generic.server-error"), self.exception_key)
|
||||
|
||||
self._create_event(
|
||||
"Recipe Created",
|
||||
f"'{self.item.name}' by {self.user.username} \n {self.settings.BASE_URL}/recipe/{self.item.slug}",
|
||||
)
|
||||
return self.item
|
||||
return self.repos.recipes.create(create_data)
|
||||
|
||||
def create_from_zip(self, archive: UploadFile, temp_path: Path) -> Recipe:
|
||||
"""
|
||||
@@ -127,69 +139,46 @@ class RecipeService(CrudHttpMixins[CreateRecipe, Recipe, Recipe], UserHttpServic
|
||||
with myzip.open(file) as myfile:
|
||||
recipe_image = myfile.read()
|
||||
|
||||
self.create_one(Recipe(**recipe_dict))
|
||||
recipe = self.create_one(Recipe(**recipe_dict))
|
||||
|
||||
if self.item:
|
||||
write_image(self.item.slug, recipe_image, "webp")
|
||||
if recipe:
|
||||
write_image(recipe.slug, recipe_image, "webp")
|
||||
|
||||
return self.item
|
||||
return recipe
|
||||
|
||||
def update_one(self, update_data: Recipe) -> Recipe:
|
||||
self.can_update()
|
||||
def _pre_update_check(self, slug: str, new_data: Recipe) -> Recipe:
|
||||
recipe = self._get_recipe(slug)
|
||||
if not self.can_update(recipe):
|
||||
raise exceptions.PermissionDenied("You do not have permission to edit this recipe.")
|
||||
if recipe.settings.locked != new_data.settings.locked and not self.can_lock_unlock(recipe):
|
||||
raise exceptions.PermissionDenied("You do not have permission to lock/unlock this recipe.")
|
||||
|
||||
if self.item.settings.locked != update_data.settings.locked and self.item.user_id != self.user.id:
|
||||
raise HTTPException(status.HTTP_403_FORBIDDEN)
|
||||
return recipe
|
||||
|
||||
original_slug = self.item.slug
|
||||
self._update_one(update_data, original_slug)
|
||||
def update_one(self, slug: str, update_data: Recipe) -> Recipe:
|
||||
recipe = self._pre_update_check(slug, update_data)
|
||||
new_data = self.repos.recipes.update(slug, update_data)
|
||||
self.check_assets(new_data, recipe.slug)
|
||||
return new_data
|
||||
|
||||
self.check_assets(original_slug)
|
||||
return self.item
|
||||
def patch_one(self, slug: str, patch_data: Recipe) -> Recipe:
|
||||
recipe = self._pre_update_check(slug, patch_data)
|
||||
recipe = self.repos.recipes.by_group(self.group.id).get_one(slug)
|
||||
new_data = self.repos.recipes.patch(recipe.slug, patch_data)
|
||||
|
||||
def patch_one(self, patch_data: Recipe) -> Recipe:
|
||||
self.can_update()
|
||||
self.check_assets(new_data, recipe.slug)
|
||||
return new_data
|
||||
|
||||
original_slug = self.item.slug
|
||||
self._patch_one(patch_data, original_slug)
|
||||
|
||||
self.check_assets(original_slug)
|
||||
return self.item
|
||||
|
||||
def delete_one(self) -> Recipe:
|
||||
self.can_update()
|
||||
self._delete_one(self.item.slug)
|
||||
self.delete_assets()
|
||||
self._create_event("Recipe Delete", f"'{self.item.name}' deleted by {self.user.full_name}")
|
||||
return self.item
|
||||
|
||||
def check_assets(self, original_slug: str) -> None:
|
||||
"""Checks if the recipe slug has changed, and if so moves the assets to a new file with the new slug."""
|
||||
if original_slug != self.item.slug:
|
||||
current_dir = self.app_dirs.RECIPE_DATA_DIR.joinpath(original_slug)
|
||||
|
||||
try:
|
||||
copytree(current_dir, self.item.directory, dirs_exist_ok=True)
|
||||
logger.info(f"Renaming Recipe Directory: {original_slug} -> {self.item.slug}")
|
||||
except FileNotFoundError:
|
||||
logger.error(f"Recipe Directory not Found: {original_slug}")
|
||||
|
||||
all_asset_files = [x.file_name for x in self.item.assets]
|
||||
|
||||
for file in self.item.asset_dir.iterdir():
|
||||
file: Path
|
||||
if file.is_dir():
|
||||
continue
|
||||
if file.name not in all_asset_files:
|
||||
file.unlink()
|
||||
|
||||
def delete_assets(self) -> None:
|
||||
recipe_dir = self.item.directory
|
||||
rmtree(recipe_dir, ignore_errors=True)
|
||||
logger.info(f"Recipe Directory Removed: {self.item.slug}")
|
||||
def delete_one(self, slug) -> Recipe:
|
||||
recipe = self._get_recipe(slug)
|
||||
self.can_update(recipe)
|
||||
data = self.repos.recipes.delete(slug)
|
||||
self.delete_assets(data)
|
||||
return data
|
||||
|
||||
# =================================================================
|
||||
# Recipe Template Methods
|
||||
|
||||
def render_template(self, temp_dir: Path, template: str = None) -> Path:
|
||||
def render_template(self, recipe: Recipe, temp_dir: Path, template: str = None) -> Path:
|
||||
t_service = TemplateService(temp_dir)
|
||||
return t_service.render(self.item, template)
|
||||
return t_service.render(recipe, template)
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import cached_property
|
||||
|
||||
from mealie.schema.recipe import RecipeTool, RecipeToolCreate
|
||||
from mealie.services._base_http_service.crud_http_mixins import CrudHttpMixins
|
||||
from mealie.services._base_http_service.http_services import UserHttpService
|
||||
from mealie.services.events import create_recipe_event
|
||||
|
||||
|
||||
class RecipeToolService(
|
||||
CrudHttpMixins[RecipeTool, RecipeToolCreate, RecipeToolCreate],
|
||||
UserHttpService[int, RecipeTool],
|
||||
):
|
||||
event_func = create_recipe_event
|
||||
_restrict_by_group = False
|
||||
_schema = RecipeTool
|
||||
|
||||
@cached_property
|
||||
def repo(self):
|
||||
return self.db.tools
|
||||
|
||||
def populate_item(self, id: int) -> RecipeTool:
|
||||
self.item = self.repo.get_one(id)
|
||||
return self.item
|
||||
|
||||
def get_all(self) -> list[RecipeTool]:
|
||||
return self.repo.get_all()
|
||||
|
||||
def create_one(self, data: RecipeToolCreate) -> RecipeTool:
|
||||
return self._create_one(data)
|
||||
|
||||
def update_one(self, data: RecipeTool, item_id: int = None) -> RecipeTool:
|
||||
return self._update_one(data, item_id)
|
||||
|
||||
def delete_one(self, id: int = None) -> RecipeTool:
|
||||
return self._delete_one(id)
|
||||
@@ -1,37 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import cached_property
|
||||
|
||||
from mealie.schema.recipe.recipe_ingredient import CreateIngredientUnit, IngredientUnit
|
||||
from mealie.services._base_http_service.crud_http_mixins import CrudHttpMixins
|
||||
from mealie.services._base_http_service.http_services import UserHttpService
|
||||
from mealie.services.events import create_recipe_event
|
||||
|
||||
|
||||
class RecipeUnitService(
|
||||
CrudHttpMixins[IngredientUnit, CreateIngredientUnit, CreateIngredientUnit],
|
||||
UserHttpService[int, IngredientUnit],
|
||||
):
|
||||
event_func = create_recipe_event
|
||||
_restrict_by_group = False
|
||||
_schema = IngredientUnit
|
||||
|
||||
@cached_property
|
||||
def repo(self):
|
||||
return self.db.ingredient_units
|
||||
|
||||
def populate_item(self, id: int) -> IngredientUnit:
|
||||
self.item = self.repo.get_one(id)
|
||||
return self.item
|
||||
|
||||
def get_all(self) -> list[IngredientUnit]:
|
||||
return self.repo.get_all()
|
||||
|
||||
def create_one(self, data: CreateIngredientUnit) -> IngredientUnit:
|
||||
return self._create_one(data)
|
||||
|
||||
def update_one(self, data: IngredientUnit, item_id: int = None) -> IngredientUnit:
|
||||
return self._update_one(data, item_id)
|
||||
|
||||
def delete_one(self, id: int = None) -> IngredientUnit:
|
||||
return self._delete_one(id)
|
||||
@@ -32,7 +32,7 @@ class TemplateService(BaseService):
|
||||
Returns a list of all templates available to render.
|
||||
"""
|
||||
return {
|
||||
TemplateType.jinja2.value: [x.name for x in self.app_dirs.TEMPLATE_DIR.iterdir() if x.is_file()],
|
||||
TemplateType.jinja2.value: [x.name for x in self.directories.TEMPLATE_DIR.iterdir() if x.is_file()],
|
||||
TemplateType.json.value: ["raw"],
|
||||
TemplateType.zip.value: ["zip"],
|
||||
}
|
||||
@@ -98,7 +98,7 @@ class TemplateService(BaseService):
|
||||
"""
|
||||
self.__check_temp(self._render_jinja2)
|
||||
|
||||
j2_template: Path = self.app_dirs.TEMPLATE_DIR / j2_template
|
||||
j2_template: Path = self.directories.TEMPLATE_DIR / j2_template
|
||||
|
||||
if not j2_template.is_file():
|
||||
raise FileNotFoundError(f"Template '{j2_template}' not found.")
|
||||
|
||||
Reference in New Issue
Block a user