mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-04-17 10:25:34 -04:00
feat: Allow Cookbooks To Share Names (#4186)
This commit is contained in:
66
mealie/repos/repository_cookbooks.py
Normal file
66
mealie/repos/repository_cookbooks.py
Normal file
@@ -0,0 +1,66 @@
|
||||
import re
|
||||
from collections.abc import Iterable
|
||||
|
||||
from fastapi import HTTPException, status
|
||||
from pydantic import UUID4
|
||||
from slugify import slugify
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
from mealie.db.models.household.cookbook import CookBook
|
||||
from mealie.repos.repository_generic import HouseholdRepositoryGeneric
|
||||
from mealie.schema.cookbook.cookbook import ReadCookBook, SaveCookBook
|
||||
from mealie.schema.response.responses import ErrorResponse
|
||||
|
||||
|
||||
class RepositoryCookbooks(HouseholdRepositoryGeneric[ReadCookBook, CookBook]):
|
||||
def create(self, data: SaveCookBook | dict) -> ReadCookBook:
|
||||
if isinstance(data, dict):
|
||||
data = SaveCookBook(**data)
|
||||
data.slug = slugify(data.name)
|
||||
|
||||
max_retries = 10
|
||||
for i in range(max_retries):
|
||||
try:
|
||||
return super().create(data)
|
||||
except IntegrityError:
|
||||
self.session.rollback()
|
||||
data.slug = slugify(f"{data.name} ({i+1})")
|
||||
|
||||
raise # raise the last IntegrityError
|
||||
|
||||
def create_many(self, data: Iterable[ReadCookBook | dict]) -> list[ReadCookBook]:
|
||||
return [self.create(entry) for entry in data]
|
||||
|
||||
def update(self, match_value: str | int | UUID4, data: SaveCookBook | dict) -> ReadCookBook:
|
||||
if isinstance(data, dict):
|
||||
data = SaveCookBook(**data)
|
||||
|
||||
new_slug = slugify(data.name)
|
||||
if not (data.slug and re.match(f"^({new_slug})(-\d+)?$", data.slug)):
|
||||
data.slug = new_slug
|
||||
|
||||
max_retries = 10
|
||||
for i in range(max_retries):
|
||||
try:
|
||||
return super().update(match_value, data)
|
||||
except IntegrityError:
|
||||
self.session.rollback()
|
||||
data.slug = slugify(f"{data.name} ({i+1})")
|
||||
|
||||
raise # raise the last IntegrityError
|
||||
|
||||
def update_many(self, data: Iterable[ReadCookBook | dict]) -> list[ReadCookBook]:
|
||||
return [self.update(entry.id if isinstance(entry, ReadCookBook) else entry["id"], entry) for entry in data]
|
||||
|
||||
def patch(self, match_value: str | int | UUID4, data: SaveCookBook | dict) -> ReadCookBook:
|
||||
cookbook = self.get_one(match_value)
|
||||
if not cookbook:
|
||||
raise HTTPException(
|
||||
status.HTTP_404_NOT_FOUND,
|
||||
detail=ErrorResponse.respond(message="Not found."),
|
||||
)
|
||||
cookbook_data = cookbook.model_dump()
|
||||
|
||||
if not isinstance(data, dict):
|
||||
data = data.model_dump()
|
||||
return self.update(match_value, cookbook_data | data)
|
||||
@@ -35,6 +35,7 @@ from mealie.db.models.recipe.tool import Tool
|
||||
from mealie.db.models.users import LongLiveToken, User
|
||||
from mealie.db.models.users.password_reset import PasswordResetModel
|
||||
from mealie.db.models.users.user_to_recipe import UserToRecipe
|
||||
from mealie.repos.repository_cookbooks import RepositoryCookbooks
|
||||
from mealie.repos.repository_foods import RepositoryFood
|
||||
from mealie.repos.repository_household import RepositoryHousehold
|
||||
from mealie.repos.repository_meal_plan_rules import RepositoryMealPlanRules
|
||||
@@ -231,8 +232,8 @@ class AllRepositories:
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def cookbooks(self) -> HouseholdRepositoryGeneric[ReadCookBook, CookBook]:
|
||||
return HouseholdRepositoryGeneric(
|
||||
def cookbooks(self) -> RepositoryCookbooks:
|
||||
return RepositoryCookbooks(
|
||||
self.session, PK_ID, CookBook, ReadCookBook, group_id=self.group_id, household_id=self.household_id
|
||||
)
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
from typing import Annotated
|
||||
|
||||
from pydantic import UUID4, ConfigDict, Field, field_validator
|
||||
from pydantic_core.core_schema import ValidationInfo
|
||||
from slugify import slugify
|
||||
from sqlalchemy.orm import joinedload
|
||||
from sqlalchemy.orm.interfaces import LoaderOption
|
||||
|
||||
@@ -31,16 +29,6 @@ class CreateCookBook(MealieModel):
|
||||
def validate_public(public: bool | None) -> bool:
|
||||
return False if public is None else public
|
||||
|
||||
@field_validator("slug", mode="before")
|
||||
def validate_slug(slug: str, info: ValidationInfo):
|
||||
name: str = info.data["name"]
|
||||
calc_slug: str = slugify(name)
|
||||
|
||||
if slug != calc_slug:
|
||||
slug = calc_slug
|
||||
|
||||
return slug
|
||||
|
||||
|
||||
class SaveCookBook(CreateCookBook):
|
||||
group_id: UUID4
|
||||
|
||||
Reference in New Issue
Block a user