mirror of
https://github.com/hay-kot/mealie.git
synced 2025-08-21 22:13:31 -07:00
Merge 68dadf7cde
into 6f0183cc4b
This commit is contained in:
commit
7783efe244
5 changed files with 49 additions and 6 deletions
|
@ -46,3 +46,11 @@ class UserLockedOut(Exception): ...
|
||||||
|
|
||||||
|
|
||||||
class MissingClaimException(Exception): ...
|
class MissingClaimException(Exception): ...
|
||||||
|
|
||||||
|
|
||||||
|
class IncompleteData(Exception):
|
||||||
|
"""
|
||||||
|
This exception is raised when a user sends incomplete data to the API
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
|
@ -84,6 +84,13 @@ class RecipeController(BaseRecipeController):
|
||||||
elif thrownType == exceptions.NoEntryFound:
|
elif thrownType == exceptions.NoEntryFound:
|
||||||
self.logger.error("No Entry Found on recipe controller action")
|
self.logger.error("No Entry Found on recipe controller action")
|
||||||
raise HTTPException(status_code=404, detail=ErrorResponse.respond(message="No Entry Found"))
|
raise HTTPException(status_code=404, detail=ErrorResponse.respond(message="No Entry Found"))
|
||||||
|
elif thrownType == exceptions.IncompleteData:
|
||||||
|
missing = ex.args[-1]
|
||||||
|
self.logger.error("Incomplete data provided to API route:", missing)
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail=ErrorResponse.respond(message=f"Field '{missing}' missing on the body of this API request"),
|
||||||
|
)
|
||||||
elif thrownType == sqlalchemy.exc.IntegrityError:
|
elif thrownType == sqlalchemy.exc.IntegrityError:
|
||||||
self.logger.error("SQL Integrity Error on recipe controller action")
|
self.logger.error("SQL Integrity Error on recipe controller action")
|
||||||
raise HTTPException(status_code=400, detail=ErrorResponse.respond(message="Recipe already exists"))
|
raise HTTPException(status_code=400, detail=ErrorResponse.respond(message="Recipe already exists"))
|
||||||
|
@ -360,7 +367,13 @@ class RecipeController(BaseRecipeController):
|
||||||
|
|
||||||
@router.put("/{slug}")
|
@router.put("/{slug}")
|
||||||
def update_one(self, slug: str, data: Recipe):
|
def update_one(self, slug: str, data: Recipe):
|
||||||
"""Updates a recipe by existing slug and data."""
|
"""
|
||||||
|
Updates a recipe by existing slug and data.
|
||||||
|
|
||||||
|
Requires all the fields of the recipe to be passed in the body
|
||||||
|
|
||||||
|
If you wish to update parts of the recipe only, use a PATCH request to this route
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
recipe = self.service.update_one(slug, data)
|
recipe = self.service.update_one(slug, data)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
@ -36,6 +36,15 @@ from mealie.services.scraper import cleaner
|
||||||
|
|
||||||
from .template_service import TemplateService
|
from .template_service import TemplateService
|
||||||
|
|
||||||
|
MANDATORY_FIELDS = [
|
||||||
|
"name",
|
||||||
|
"user_id",
|
||||||
|
"household_id",
|
||||||
|
"group_id",
|
||||||
|
"recipe_ingredient",
|
||||||
|
"recipe_instructions",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class RecipeServiceBase(BaseService):
|
class RecipeServiceBase(BaseService):
|
||||||
def __init__(self, repos: AllRepositories, user: PrivateUser, household: HouseholdInDB, translator: Translator):
|
def __init__(self, repos: AllRepositories, user: PrivateUser, household: HouseholdInDB, translator: Translator):
|
||||||
|
@ -144,6 +153,7 @@ class RecipeService(RecipeServiceBase):
|
||||||
if not additional_attrs.get("recipe_instructions"):
|
if not additional_attrs.get("recipe_instructions"):
|
||||||
additional_attrs["recipe_instructions"] = [RecipeStep(text=self.t("recipe.recipe-defaults.step-text"))]
|
additional_attrs["recipe_instructions"] = [RecipeStep(text=self.t("recipe.recipe-defaults.step-text"))]
|
||||||
|
|
||||||
|
assert all(f in additional_attrs for f in MANDATORY_FIELDS)
|
||||||
return Recipe(**additional_attrs)
|
return Recipe(**additional_attrs)
|
||||||
|
|
||||||
def get_one(self, slug_or_id: str | UUID) -> Recipe:
|
def get_one(self, slug_or_id: str | UUID) -> Recipe:
|
||||||
|
@ -369,7 +379,7 @@ class RecipeService(RecipeServiceBase):
|
||||||
|
|
||||||
return new_recipe
|
return new_recipe
|
||||||
|
|
||||||
def _pre_update_check(self, slug_or_id: str | UUID, new_data: Recipe) -> Recipe:
|
def _pre_update_check(self, slug_or_id: str | UUID, new_data: Recipe, strict: bool) -> Recipe:
|
||||||
"""
|
"""
|
||||||
gets the recipe from the database and performs a check to see if the user can update the recipe.
|
gets the recipe from the database and performs a check to see if the user can update the recipe.
|
||||||
If the user can't update the recipe, an exception is raised.
|
If the user can't update the recipe, an exception is raised.
|
||||||
|
@ -382,6 +392,7 @@ class RecipeService(RecipeServiceBase):
|
||||||
Args:
|
Args:
|
||||||
slug_or_id (str | UUID): recipe slug or id
|
slug_or_id (str | UUID): recipe slug or id
|
||||||
new_data (Recipe): the new recipe data
|
new_data (Recipe): the new recipe data
|
||||||
|
strict: if the data provided must contain ALL fields, or can be incomplete
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
exceptions.PermissionDenied (403)
|
exceptions.PermissionDenied (403)
|
||||||
|
@ -389,6 +400,11 @@ class RecipeService(RecipeServiceBase):
|
||||||
|
|
||||||
recipe = self.get_one(slug_or_id)
|
recipe = self.get_one(slug_or_id)
|
||||||
|
|
||||||
|
if strict:
|
||||||
|
for field in new_data.__class__.model_fields:
|
||||||
|
if field in MANDATORY_FIELDS and getattr(new_data, field) is None:
|
||||||
|
raise exceptions.IncompleteData("Incomplete recipe", field)
|
||||||
|
|
||||||
if recipe is None or recipe.settings is None:
|
if recipe is None or recipe.settings is None:
|
||||||
raise exceptions.NoEntryFound("Recipe not found.")
|
raise exceptions.NoEntryFound("Recipe not found.")
|
||||||
|
|
||||||
|
@ -402,7 +418,7 @@ class RecipeService(RecipeServiceBase):
|
||||||
return recipe
|
return recipe
|
||||||
|
|
||||||
def update_one(self, slug_or_id: str | UUID, update_data: Recipe) -> Recipe:
|
def update_one(self, slug_or_id: str | UUID, update_data: Recipe) -> Recipe:
|
||||||
recipe = self._pre_update_check(slug_or_id, update_data)
|
recipe = self._pre_update_check(slug_or_id, update_data, True)
|
||||||
|
|
||||||
new_data = self.group_recipes.update(recipe.slug, update_data)
|
new_data = self.group_recipes.update(recipe.slug, update_data)
|
||||||
self.check_assets(new_data, recipe.slug)
|
self.check_assets(new_data, recipe.slug)
|
||||||
|
@ -419,7 +435,7 @@ class RecipeService(RecipeServiceBase):
|
||||||
return self.group_recipes.update_image(slug, extension)
|
return self.group_recipes.update_image(slug, extension)
|
||||||
|
|
||||||
def patch_one(self, slug_or_id: str | UUID, patch_data: Recipe) -> Recipe:
|
def patch_one(self, slug_or_id: str | UUID, patch_data: Recipe) -> Recipe:
|
||||||
recipe: Recipe = self._pre_update_check(slug_or_id, patch_data)
|
recipe: Recipe = self._pre_update_check(slug_or_id, patch_data, False)
|
||||||
|
|
||||||
new_data = self.group_recipes.patch(recipe.slug, patch_data.model_dump(exclude_unset=True))
|
new_data = self.group_recipes.patch(recipe.slug, patch_data.model_dump(exclude_unset=True))
|
||||||
|
|
||||||
|
|
|
@ -109,6 +109,7 @@ def test_organizer_update(
|
||||||
item[key] = update_data[key]
|
item[key] = update_data[key]
|
||||||
|
|
||||||
response = api_client.put(route.item(item_id), json=item, headers=unique_user.token)
|
response = api_client.put(route.item(item_id), json=item, headers=unique_user.token)
|
||||||
|
print(response)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
response = api_client.get(route.item(item_id), headers=unique_user.token)
|
response = api_client.get(route.item(item_id), headers=unique_user.token)
|
||||||
|
@ -175,7 +176,7 @@ def test_organizer_association(
|
||||||
]
|
]
|
||||||
|
|
||||||
# Update Recipe
|
# Update Recipe
|
||||||
response = api_client.put(api_routes.recipes_slug(slug), json=as_json, headers=unique_user.token)
|
response = api_client.patch(api_routes.recipes_slug(slug), json=as_json, headers=unique_user.token)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
# Get Recipe Data
|
# Get Recipe Data
|
||||||
|
@ -223,7 +224,7 @@ def test_organizer_get_by_slug(
|
||||||
{"id": item["id"], "group_id": unique_user.group_id, "name": item["name"], "slug": item["slug"]}
|
{"id": item["id"], "group_id": unique_user.group_id, "name": item["name"], "slug": item["slug"]}
|
||||||
]
|
]
|
||||||
|
|
||||||
response = api_client.put(api_routes.recipes_slug(slug), json=as_json, headers=unique_user.token)
|
response = api_client.patch(api_routes.recipes_slug(slug), json=as_json, headers=unique_user.token)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
# Get Organizer by Slug
|
# Get Organizer by Slug
|
||||||
|
|
|
@ -470,6 +470,11 @@ def test_read_update(
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert json.loads(response.text).get("slug") == recipe_data.expected_slug
|
assert json.loads(response.text).get("slug") == recipe_data.expected_slug
|
||||||
|
|
||||||
|
recipe_incomplete = recipe.copy()
|
||||||
|
del recipe_incomplete["group_id"]
|
||||||
|
response = api_client.put(recipe_url, json=utils.jsonify(recipe_incomplete), headers=unique_user.token)
|
||||||
|
assert response.status_code == 400
|
||||||
|
|
||||||
response = api_client.get(recipe_url, headers=unique_user.token)
|
response = api_client.get(recipe_url, headers=unique_user.token)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
recipe = json.loads(response.text)
|
recipe = json.loads(response.text)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue