feat: Allow Cookbooks To Share Names (#4186)

This commit is contained in:
Michael Genson
2024-09-15 06:42:58 -05:00
committed by GitHub
parent abe4504640
commit dbbd662e7d
8 changed files with 185 additions and 29 deletions

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

View File

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

View File

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