feat: Remove "Is Food" and "Disable Amounts" Flags (#5684)

Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
This commit is contained in:
Michael Genson
2025-07-31 10:36:24 -05:00
committed by GitHub
parent efc0d31724
commit 245ca5fe3b
49 changed files with 173 additions and 364 deletions

View File

@@ -0,0 +1,45 @@
"""empty migration to fix food flag data
Revision ID: d7b3ce6fa31a
Revises: 7cf3054cbbcc
Create Date: 2025-07-11 20:17:10.543280
"""
from textwrap import dedent
from alembic import op
# revision identifiers, used by Alembic.
revision = "d7b3ce6fa31a"
down_revision: str | None = "7cf3054cbbcc"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def is_postgres():
return op.get_context().dialect.name == "postgresql"
def upgrade():
# Update recipes with disable_amount=True: set ingredient quantities of 1 to 0
op.execute(
dedent(
f"""
UPDATE recipes_ingredients
SET quantity = 0
WHERE quantity = 1
AND recipe_id IN (
SELECT r.id
FROM recipes r
JOIN recipe_settings rs ON r.id = rs.recipe_id
WHERE rs.disable_amount = {"true" if is_postgres() else "1"}
)
"""
)
)
def downgrade():
pass

View File

@@ -31,6 +31,8 @@ class HouseholdPreferencesModel(SqlAlchemyBase, BaseMixins):
recipe_show_assets: Mapped[bool | None] = mapped_column(sa.Boolean, default=False)
recipe_landscape_view: Mapped[bool | None] = mapped_column(sa.Boolean, default=False)
recipe_disable_comments: Mapped[bool | None] = mapped_column(sa.Boolean, default=False)
# Deprecated
recipe_disable_amount: Mapped[bool | None] = mapped_column(sa.Boolean, default=True)
@auto_init()

View File

@@ -65,7 +65,6 @@ class ShoppingListItem(SqlAlchemyBase, BaseMixins):
quantity: Mapped[float | None] = mapped_column(Float, default=1)
note: Mapped[str | None] = mapped_column(String)
is_food: Mapped[bool | None] = mapped_column(Boolean, default=False)
extras: Mapped[list[ShoppingListItemExtras]] = orm.relationship(
"ShoppingListItemExtras", cascade="all, delete-orphan"
)
@@ -88,6 +87,9 @@ class ShoppingListItem(SqlAlchemyBase, BaseMixins):
)
model_config = ConfigDict(exclude={"label", "food", "unit"})
# Deprecated
is_food: Mapped[bool | None] = mapped_column(Boolean, default=False)
@api_extras
@auto_init()
def __init__(self, **_) -> None:

View File

@@ -13,10 +13,12 @@ class RecipeSettings(SqlAlchemyBase):
show_nutrition: Mapped[bool | None] = mapped_column(sa.Boolean)
show_assets: Mapped[bool | None] = mapped_column(sa.Boolean)
landscape_view: Mapped[bool | None] = mapped_column(sa.Boolean)
disable_amount: Mapped[bool | None] = mapped_column(sa.Boolean, default=True)
disable_comments: Mapped[bool | None] = mapped_column(sa.Boolean, default=False)
locked: Mapped[bool | None] = mapped_column(sa.Boolean, default=False)
# Deprecated
disable_amount: Mapped[bool | None] = mapped_column(sa.Boolean, default=True)
def __init__(
self,
public=True,

View File

@@ -440,7 +440,6 @@ class RepositoryRecipes(HouseholdRepositoryGeneric[Recipe, RecipeModel]):
)
q = (
q.join(settings_alias, self.model.settings)
.filter(settings_alias.disable_amount == False) # noqa: E712 - required for SQLAlchemy comparison
.outerjoin(unmatched_foods_query, self.model.id == unmatched_foods_query.c.recipe_id)
.outerjoin(total_user_foods_query, self.model.id == total_user_foods_query.c.recipe_id)
.filter(

View File

@@ -101,22 +101,18 @@ def content_with_meta(group_slug: str, recipe: Recipe) -> str:
image_url = "https://raw.githubusercontent.com/mealie-recipes/mealie/9571816ac4eed5beacfc0abf6c03eff1427fd0eb/frontend/static/icons/android-chrome-512x512.png"
ingredients: list[str] = []
if recipe.settings.disable_amount: # type: ignore
ingredients = [escape(i.note) for i in recipe.recipe_ingredient if i.note]
for ing in recipe.recipe_ingredient:
s = ""
if ing.quantity:
s += f"{ing.quantity} "
if ing.unit:
s += f"{ing.unit.name} "
if ing.food:
s += f"{ing.food.name} "
if ing.note:
s += f"{ing.note}"
else:
for ing in recipe.recipe_ingredient:
s = ""
if ing.quantity:
s += f"{ing.quantity} "
if ing.unit:
s += f"{ing.unit.name} "
if ing.food:
s += f"{ing.food.name} "
if ing.note:
s += f"{ing.note}"
ingredients.append(escape(s))
ingredients.append(escape(s))
nutrition: dict[str, str | None] = recipe.nutrition.model_dump(by_alias=True) if recipe.nutrition else {}
for k, v in nutrition.items():

View File

@@ -66,7 +66,6 @@ class ShoppingListItemBase(RecipeIngredientBase):
label_id: UUID4 | None = None
unit_id: UUID4 | None = None
is_food: bool = False
extras: dict | None = {}
@field_validator("extras", mode="before")

View File

@@ -18,7 +18,6 @@ class UpdateHouseholdPreferences(MealieModel):
recipe_show_assets: bool = False
recipe_landscape_view: bool = False
recipe_disable_comments: bool = False
recipe_disable_amount: bool = True
class CreateHouseholdPreferences(UpdateHouseholdPreferences): ...

View File

@@ -6,7 +6,7 @@ from pathlib import Path
from typing import Annotated, Any, ClassVar
from uuid import uuid4
from pydantic import UUID4, BaseModel, ConfigDict, Field, field_validator, model_validator
from pydantic import UUID4, BaseModel, ConfigDict, Field, field_validator
from pydantic_core.core_schema import ValidationInfo
from slugify import slugify
from sqlalchemy import Select, desc, func, or_, select, text
@@ -228,18 +228,6 @@ class Recipe(RecipeSummary):
model_config = ConfigDict(from_attributes=True)
@model_validator(mode="after")
def calculate_missing_food_flags_and_format_display(self):
disable_amount = self.settings.disable_amount if self.settings else True
for ingredient in self.recipe_ingredient:
ingredient.disable_amount = disable_amount
ingredient.is_food = not ingredient.disable_amount
# recalculate the display property, since it depends on the disable_amount flag
ingredient.display = ingredient._format_display()
return self
@field_validator("slug", mode="before")
def validate_slug(slug: str, info: ValidationInfo):
if not info.data.get("name"):

View File

@@ -152,13 +152,11 @@ class IngredientUnit(CreateIngredientUnit):
class RecipeIngredientBase(MealieModel):
quantity: NoneFloat = 1
quantity: NoneFloat = 0
unit: IngredientUnit | CreateIngredientUnit | None = None
food: IngredientFood | CreateIngredientFood | None = None
note: str | None = ""
is_food: bool | None = None
disable_amount: bool | None = None
display: str = ""
"""
How the ingredient should be displayed
@@ -166,20 +164,6 @@ class RecipeIngredientBase(MealieModel):
Automatically calculated after the object is created, unless overwritten
"""
@model_validator(mode="after")
def calculate_missing_food_flags(self):
# calculate missing is_food and disable_amount values
# we can't do this in a validator since they depend on each other
if self.is_food is None and self.disable_amount is not None:
self.is_food = not self.disable_amount
elif self.disable_amount is None and self.is_food is not None:
self.disable_amount = not self.is_food
elif self.is_food is None and self.disable_amount is None:
self.is_food = bool(self.food)
self.disable_amount = not self.is_food
return self
@model_validator(mode="after")
def format_display(self):
if not self.display:
@@ -266,28 +250,17 @@ class RecipeIngredientBase(MealieModel):
def _format_display(self) -> str:
components = []
use_food = True
if self.is_food is False:
use_food = False
elif self.disable_amount is True:
use_food = False
# ingredients with no food come across with a qty of 1, which looks weird
# e.g. "1 2 tbsp of olive oil"
if self.quantity and (use_food or self.quantity != 1):
if self.quantity:
components.append(self._format_quantity_for_display())
if not use_food:
components.append(self.note or "")
else:
if self.quantity and self.unit:
components.append(self._format_unit_for_display())
if self.quantity and self.unit:
components.append(self._format_unit_for_display())
if self.food:
components.append(self._format_food_for_display())
if self.food:
components.append(self._format_food_for_display())
if self.note:
components.append(self.note)
if self.note:
components.append(self.note)
return " ".join(components).strip()
@@ -299,7 +272,6 @@ class IngredientUnitPagination(PaginationBase):
class RecipeIngredient(RecipeIngredientBase):
title: str | None = None
original_text: str | None = None
disable_amount: bool = True
# Ref is used as a way to distinguish between an individual ingredient on the frontend
# It is required for the reorder and section titles to function properly because of how

View File

@@ -9,6 +9,5 @@ class RecipeSettings(MealieModel):
show_assets: bool = False
landscape_view: bool = False
disable_comments: bool = True
disable_amount: bool = True
locked: bool = False
model_config = ConfigDict(from_attributes=True)

View File

@@ -312,12 +312,10 @@ class ShoppingListService:
list_items: list[ShoppingListItemCreate] = []
for ingredient in recipe_ingredients:
if isinstance(ingredient.food, IngredientFood):
is_food = True
food_id = ingredient.food.id
label_id = ingredient.food.label_id
else:
is_food = False
food_id = None
label_id = None
@@ -329,7 +327,6 @@ class ShoppingListService:
new_item = ShoppingListItemCreate(
shopping_list_id=list_id,
is_food=is_food,
note=ingredient.note,
quantity=ingredient.quantity * scale if ingredient.quantity else 0,
food_id=food_id,

View File

@@ -177,7 +177,6 @@ class BaseMigrator(BaseService):
show_assets=self.household.preferences.recipe_show_assets,
landscape_view=self.household.preferences.recipe_landscape_view,
disable_comments=self.household.preferences.recipe_disable_comments,
disable_amount=self.household.preferences.recipe_disable_amount,
)
for recipe in validated_recipes:

View File

@@ -36,7 +36,6 @@ class BruteForceParser(ABCIngredientParser):
ingredient=RecipeIngredient(
unit=CreateIngredientUnit(name=bfi.unit),
food=CreateIngredientFood(name=bfi.food),
disable_amount=False,
quantity=bfi.amount,
note=bfi.note,
),
@@ -151,7 +150,6 @@ class NLPParser(ABCIngredientParser):
quantity=qty,
unit=CreateIngredientUnit(name=unit) if unit else None,
food=CreateIngredientFood(name=food) if food else None,
disable_amount=False,
note=note,
),
)

View File

@@ -173,7 +173,6 @@ class RecipeService(RecipeServiceBase):
show_assets=self.household.preferences.recipe_show_assets,
landscape_view=self.household.preferences.recipe_landscape_view,
disable_comments=self.household.preferences.recipe_disable_comments,
disable_amount=self.household.preferences.recipe_disable_amount,
)
else:
data.settings = RecipeSettings()

View File

@@ -74,7 +74,6 @@ class RegistrationService:
recipe_show_assets=self.registration.advanced,
recipe_landscape_view=False,
recipe_disable_comments=self.registration.advanced,
recipe_disable_amount=self.registration.advanced,
)
return HouseholdService.create_household(group_repos, household_data, household_preferences)