forked from mealie-recipes/mealie
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Allow Cookbooks To Share Names (mealie-recipes#4186)
- Loading branch information
1 parent
c9f44d3
commit 3a07984
Showing
8 changed files
with
185 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
export interface SideBarLink { | ||
key?: string; | ||
icon: string; | ||
to?: string; | ||
href?: string; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
98 changes: 98 additions & 0 deletions
98
tests/unit_tests/repository_tests/test_cookbook_repository.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
from uuid import UUID | ||
|
||
import pytest | ||
from slugify import slugify | ||
|
||
from mealie.schema.cookbook.cookbook import SaveCookBook | ||
from tests.utils.factories import random_string | ||
from tests.utils.fixture_schemas import TestUser | ||
|
||
|
||
def cookbook_data(user: TestUser, **kwargs): | ||
data = { | ||
"name": random_string(), | ||
"group_id": UUID(user.group_id), | ||
"household_id": UUID(user.household_id), | ||
} | kwargs | ||
|
||
return SaveCookBook(**data) | ||
|
||
|
||
@pytest.mark.parametrize("use_create_many", [True, False]) | ||
def test_create_cookbook_ignores_slug(unique_user: TestUser, use_create_many: bool): | ||
bad_slug = random_string() | ||
cb_data = cookbook_data(unique_user, slug=bad_slug) | ||
if use_create_many: | ||
result = unique_user.repos.cookbooks.create_many([cb_data]) | ||
assert len(result) == 1 | ||
cb = result[0] | ||
else: | ||
cb = unique_user.repos.cookbooks.create(cb_data) | ||
assert cb.slug == slugify(cb.name) != bad_slug | ||
|
||
|
||
@pytest.mark.parametrize("use_create_many", [True, False]) | ||
def test_create_cookbook_duplicate_name(unique_user: TestUser, use_create_many: bool): | ||
cb_1_data = cookbook_data(unique_user) | ||
cb_2_data = cookbook_data(unique_user, name=cb_1_data.name) | ||
|
||
cb_1 = unique_user.repos.cookbooks.create(cb_1_data) | ||
unique_user.repos.session.commit() | ||
|
||
if use_create_many: | ||
result = unique_user.repos.cookbooks.create_many([cb_2_data]) | ||
assert len(result) == 1 | ||
cb_2 = result[0] | ||
else: | ||
cb_2 = unique_user.repos.cookbooks.create(cb_2_data) | ||
|
||
assert cb_1.id != cb_2.id | ||
assert cb_1.name == cb_2.name | ||
assert cb_1.slug != cb_2.slug | ||
|
||
|
||
@pytest.mark.parametrize("method", ["update", "update_many", "patch"]) | ||
def test_update_cookbook_updates_slug(unique_user: TestUser, method: str): | ||
cb_data = cookbook_data(unique_user) | ||
cb = unique_user.repos.cookbooks.create(cb_data) | ||
unique_user.repos.session.commit() | ||
|
||
new_name = random_string() | ||
cb.name = new_name | ||
|
||
if method == "update": | ||
cb = unique_user.repos.cookbooks.update(cb.id, cb) | ||
if method == "update_many": | ||
result = unique_user.repos.cookbooks.update_many([cb]) | ||
assert len(result) == 1 | ||
cb = result[0] | ||
else: | ||
cb = unique_user.repos.cookbooks.patch(cb.id, cb) | ||
|
||
assert cb.name == new_name | ||
assert cb.slug == slugify(new_name) | ||
|
||
|
||
@pytest.mark.parametrize("method", ["update", "update_many", "patch"]) | ||
def test_update_cookbook_duplicate_name(unique_user: TestUser, method: str): | ||
cb_1_data = cookbook_data(unique_user) | ||
cb_2_data = cookbook_data(unique_user) | ||
|
||
cb_1 = unique_user.repos.cookbooks.create(cb_1_data) | ||
unique_user.repos.session.commit() | ||
cb_2 = unique_user.repos.cookbooks.create(cb_2_data) | ||
unique_user.repos.session.commit() | ||
|
||
cb_2.name = cb_1.name | ||
if method == "update": | ||
cb_2 = unique_user.repos.cookbooks.update(cb_2.id, cb_2) | ||
if method == "update_many": | ||
result = unique_user.repos.cookbooks.update_many([cb_2]) | ||
assert len(result) == 1 | ||
cb_2 = result[0] | ||
else: | ||
cb_2 = unique_user.repos.cookbooks.patch(cb_2.id, cb_2) | ||
|
||
assert cb_1.id != cb_2.id | ||
assert cb_1.name == cb_2.name | ||
assert cb_1.slug != cb_2.slug |