mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-01-06 00:51:28 -05:00
feat: plural foods and units, and aliases (#2674)
* added plural names and alias tables to foods/units
* updated models to include plural names and aliases
* updated parser to include plural and aliases
* fixed migrations
* fixed recursive models
* added plural abbreviation to migration
* updated parser and display prop
* update displays to use plurals
* fix display edgecase and remove print
* added/updated display tests
* fixed model bug and added parser tests
* added tests for aliases
* added new plural options to data management page
* removed unique constraint
* made base dialog more customizable
* added alias management to food and unit data pages
* removed unused awaits
* 🧹
This commit is contained in:
@@ -25,25 +25,44 @@ class IngredientUnitModel(SqlAlchemyBase, BaseMixins):
|
||||
group: Mapped["Group"] = orm.relationship("Group", back_populates="ingredient_units", foreign_keys=[group_id])
|
||||
|
||||
name: Mapped[str | None] = mapped_column(String)
|
||||
plural_name: Mapped[str | None] = mapped_column(String)
|
||||
description: Mapped[str | None] = mapped_column(String)
|
||||
abbreviation: Mapped[str | None] = mapped_column(String)
|
||||
plural_abbreviation: Mapped[str | None] = mapped_column(String)
|
||||
use_abbreviation: Mapped[bool | None] = mapped_column(Boolean, default=False)
|
||||
fraction: Mapped[bool | None] = mapped_column(Boolean, default=True)
|
||||
|
||||
ingredients: Mapped[list["RecipeIngredientModel"]] = orm.relationship(
|
||||
"RecipeIngredientModel", back_populates="unit"
|
||||
)
|
||||
aliases: Mapped[list["IngredientUnitAliasModel"]] = orm.relationship(
|
||||
"IngredientUnitAliasModel", back_populates="unit", cascade="all, delete, delete-orphan"
|
||||
)
|
||||
|
||||
# Automatically updated by sqlalchemy event, do not write to this manually
|
||||
name_normalized: Mapped[str | None] = mapped_column(sa.String, index=True)
|
||||
plural_name_normalized: Mapped[str | None] = mapped_column(sa.String, index=True)
|
||||
abbreviation_normalized: Mapped[str | None] = mapped_column(String, index=True)
|
||||
plural_abbreviation_normalized: Mapped[str | None] = mapped_column(String, index=True)
|
||||
|
||||
@auto_init()
|
||||
def __init__(self, session: Session, name: str | None = None, abbreviation: str | None = None, **_) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
session: Session,
|
||||
name: str | None = None,
|
||||
plural_name: str | None = None,
|
||||
abbreviation: str | None = None,
|
||||
plural_abbreviation: str | None = None,
|
||||
**_,
|
||||
) -> None:
|
||||
if name is not None:
|
||||
self.name_normalized = self.normalize(name)
|
||||
|
||||
if plural_name is not None:
|
||||
self.plural_name_normalized = self.normalize(plural_name)
|
||||
if abbreviation is not None:
|
||||
self.abbreviation = self.normalize(abbreviation)
|
||||
self.abbreviation_normalized = self.normalize(abbreviation)
|
||||
if plural_abbreviation is not None:
|
||||
self.plural_abbreviation_normalized = self.normalize(plural_abbreviation)
|
||||
|
||||
tableargs = [
|
||||
sa.UniqueConstraint("name", "group_id", name="ingredient_units_name_group_id_key"),
|
||||
@@ -52,11 +71,21 @@ class IngredientUnitModel(SqlAlchemyBase, BaseMixins):
|
||||
"name_normalized",
|
||||
unique=False,
|
||||
),
|
||||
sa.Index(
|
||||
"ix_ingredient_units_plural_name_normalized",
|
||||
"plural_name_normalized",
|
||||
unique=False,
|
||||
),
|
||||
sa.Index(
|
||||
"ix_ingredient_units_abbreviation_normalized",
|
||||
"abbreviation_normalized",
|
||||
unique=False,
|
||||
),
|
||||
sa.Index(
|
||||
"ix_ingredient_units_plural_abbreviation_normalized",
|
||||
"plural_abbreviation_normalized",
|
||||
unique=False,
|
||||
),
|
||||
]
|
||||
|
||||
if session.get_bind().name == "postgresql":
|
||||
@@ -71,6 +100,15 @@ class IngredientUnitModel(SqlAlchemyBase, BaseMixins):
|
||||
"name_normalized": "gin_trgm_ops",
|
||||
},
|
||||
),
|
||||
sa.Index(
|
||||
"ix_ingredient_units_plural_name_normalized_gin",
|
||||
"name_normalized",
|
||||
unique=False,
|
||||
postgresql_using="gin",
|
||||
postgresql_ops={
|
||||
"plural_name_normalized": "gin_trgm_ops",
|
||||
},
|
||||
),
|
||||
sa.Index(
|
||||
"ix_ingredient_units_abbreviation_normalized_gin",
|
||||
"abbreviation_normalized",
|
||||
@@ -80,6 +118,15 @@ class IngredientUnitModel(SqlAlchemyBase, BaseMixins):
|
||||
"abbreviation_normalized": "gin_trgm_ops",
|
||||
},
|
||||
),
|
||||
sa.Index(
|
||||
"ix_ingredient_units_plural_abbreviation_normalized_gin",
|
||||
"plural_abbreviation_normalized",
|
||||
unique=False,
|
||||
postgresql_using="gin",
|
||||
postgresql_ops={
|
||||
"plural_abbreviation_normalized": "gin_trgm_ops",
|
||||
},
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -95,10 +142,15 @@ class IngredientFoodModel(SqlAlchemyBase, BaseMixins):
|
||||
group: Mapped["Group"] = orm.relationship("Group", back_populates="ingredient_foods", foreign_keys=[group_id])
|
||||
|
||||
name: Mapped[str | None] = mapped_column(String)
|
||||
plural_name: Mapped[str | None] = mapped_column(String)
|
||||
description: Mapped[str | None] = mapped_column(String)
|
||||
|
||||
ingredients: Mapped[list["RecipeIngredientModel"]] = orm.relationship(
|
||||
"RecipeIngredientModel", back_populates="food"
|
||||
)
|
||||
aliases: Mapped[list["IngredientFoodAliasModel"]] = orm.relationship(
|
||||
"IngredientFoodAliasModel", back_populates="food", cascade="all, delete, delete-orphan"
|
||||
)
|
||||
extras: Mapped[list[IngredientFoodExtras]] = orm.relationship("IngredientFoodExtras", cascade="all, delete-orphan")
|
||||
|
||||
label_id: Mapped[GUID | None] = mapped_column(GUID, ForeignKey("multi_purpose_labels.id"), index=True)
|
||||
@@ -106,12 +158,15 @@ class IngredientFoodModel(SqlAlchemyBase, BaseMixins):
|
||||
|
||||
# Automatically updated by sqlalchemy event, do not write to this manually
|
||||
name_normalized: Mapped[str | None] = mapped_column(sa.String, index=True)
|
||||
plural_name_normalized: Mapped[str | None] = mapped_column(sa.String, index=True)
|
||||
|
||||
@api_extras
|
||||
@auto_init()
|
||||
def __init__(self, session: Session, name: str | None = None, **_) -> None:
|
||||
def __init__(self, session: Session, name: str | None = None, plural_name: str | None = None, **_) -> None:
|
||||
if name is not None:
|
||||
self.name_normalized = self.normalize(name)
|
||||
if plural_name is not None:
|
||||
self.plural_name_normalized = self.normalize(plural_name)
|
||||
|
||||
tableargs = [
|
||||
sa.UniqueConstraint("name", "group_id", name="ingredient_foods_name_group_id_key"),
|
||||
@@ -120,6 +175,11 @@ class IngredientFoodModel(SqlAlchemyBase, BaseMixins):
|
||||
"name_normalized",
|
||||
unique=False,
|
||||
),
|
||||
sa.Index(
|
||||
"ix_ingredient_foods_plural_name_normalized",
|
||||
"plural_name_normalized",
|
||||
unique=False,
|
||||
),
|
||||
]
|
||||
|
||||
if session.get_bind().name == "postgresql":
|
||||
@@ -133,13 +193,104 @@ class IngredientFoodModel(SqlAlchemyBase, BaseMixins):
|
||||
postgresql_ops={
|
||||
"name_normalized": "gin_trgm_ops",
|
||||
},
|
||||
)
|
||||
),
|
||||
sa.Index(
|
||||
"ix_ingredient_foods_plural_name_normalized_gin",
|
||||
"plural_name_normalized",
|
||||
unique=False,
|
||||
postgresql_using="gin",
|
||||
postgresql_ops={
|
||||
"plural_name_normalized": "gin_trgm_ops",
|
||||
},
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
self.__table_args__ = tuple(tableargs)
|
||||
|
||||
|
||||
class IngredientUnitAliasModel(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "ingredient_units_aliases"
|
||||
id: Mapped[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
|
||||
|
||||
unit_id: Mapped[GUID] = mapped_column(GUID, ForeignKey("ingredient_units.id"), primary_key=True)
|
||||
unit: Mapped["IngredientUnitModel"] = orm.relationship("IngredientUnitModel", back_populates="aliases")
|
||||
|
||||
name: Mapped[str] = mapped_column(String)
|
||||
|
||||
# Automatically updated by sqlalchemy event, do not write to this manually
|
||||
name_normalized: Mapped[str | None] = mapped_column(sa.String, index=True)
|
||||
|
||||
@auto_init()
|
||||
def __init__(self, session: Session, name: str, **_) -> None:
|
||||
self.name_normalized = self.normalize(name)
|
||||
tableargs = [
|
||||
sa.Index(
|
||||
"ix_ingredient_units_aliases_name_normalized",
|
||||
"name_normalized",
|
||||
unique=False,
|
||||
),
|
||||
]
|
||||
|
||||
if session.get_bind().name == "postgresql":
|
||||
tableargs.extend(
|
||||
[
|
||||
sa.Index(
|
||||
"ix_ingredient_units_aliases_name_normalized_gin",
|
||||
"name_normalized",
|
||||
unique=False,
|
||||
postgresql_using="gin",
|
||||
postgresql_ops={
|
||||
"name_normalized": "gin_trgm_ops",
|
||||
},
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
self.__table_args__ = tableargs
|
||||
|
||||
|
||||
class IngredientFoodAliasModel(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "ingredient_foods_aliases"
|
||||
id: Mapped[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
|
||||
|
||||
food_id: Mapped[GUID] = mapped_column(GUID, ForeignKey("ingredient_foods.id"), primary_key=True)
|
||||
food: Mapped["IngredientFoodModel"] = orm.relationship("IngredientFoodModel", back_populates="aliases")
|
||||
|
||||
name: Mapped[str] = mapped_column(String)
|
||||
|
||||
# Automatically updated by sqlalchemy event, do not write to this manually
|
||||
name_normalized: Mapped[str | None] = mapped_column(sa.String, index=True)
|
||||
|
||||
@auto_init()
|
||||
def __init__(self, session: Session, name: str, **_) -> None:
|
||||
self.name_normalized = self.normalize(name)
|
||||
tableargs = [
|
||||
sa.Index(
|
||||
"ix_ingredient_foods_aliases_name_normalized",
|
||||
"name_normalized",
|
||||
unique=False,
|
||||
),
|
||||
]
|
||||
|
||||
if session.get_bind().name == "postgresql":
|
||||
tableargs.extend(
|
||||
[
|
||||
sa.Index(
|
||||
"ix_ingredient_foods_aliases_name_normalized_gin",
|
||||
"name_normalized",
|
||||
unique=False,
|
||||
postgresql_using="gin",
|
||||
postgresql_ops={
|
||||
"name_normalized": "gin_trgm_ops",
|
||||
},
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
self.__table_args__ = tableargs
|
||||
|
||||
|
||||
class RecipeIngredientModel(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "recipes_ingredients"
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
@@ -221,6 +372,14 @@ def receive_unit_name(target: IngredientUnitModel, value: str | None, oldvalue,
|
||||
target.name_normalized = None
|
||||
|
||||
|
||||
@event.listens_for(IngredientUnitModel.plural_name, "set")
|
||||
def receive_plural_unit_name(target: IngredientUnitModel, value: str | None, oldvalue, initiator):
|
||||
if value is not None:
|
||||
target.plural_name_normalized = IngredientUnitModel.normalize(value)
|
||||
else:
|
||||
target.plural_name_normalized = None
|
||||
|
||||
|
||||
@event.listens_for(IngredientUnitModel.abbreviation, "set")
|
||||
def receive_unit_abbreviation(target: IngredientUnitModel, value: str | None, oldvalue, initiator):
|
||||
if value is not None:
|
||||
@@ -229,6 +388,14 @@ def receive_unit_abbreviation(target: IngredientUnitModel, value: str | None, ol
|
||||
target.abbreviation_normalized = None
|
||||
|
||||
|
||||
@event.listens_for(IngredientUnitModel.plural_abbreviation, "set")
|
||||
def receive_unit_plural_abbreviation(target: IngredientUnitModel, value: str | None, oldvalue, initiator):
|
||||
if value is not None:
|
||||
target.plural_abbreviation_normalized = IngredientUnitModel.normalize(value)
|
||||
else:
|
||||
target.plural_abbreviation_normalized = None
|
||||
|
||||
|
||||
@event.listens_for(IngredientFoodModel.name, "set")
|
||||
def receive_food_name(target: IngredientFoodModel, value: str | None, oldvalue, initiator):
|
||||
if value is not None:
|
||||
@@ -237,6 +404,24 @@ def receive_food_name(target: IngredientFoodModel, value: str | None, oldvalue,
|
||||
target.name_normalized = None
|
||||
|
||||
|
||||
@event.listens_for(IngredientFoodModel.plural_name, "set")
|
||||
def receive_food_plural_name(target: IngredientFoodModel, value: str | None, oldvalue, initiator):
|
||||
if value is not None:
|
||||
target.plural_name_normalized = IngredientFoodModel.normalize(value)
|
||||
else:
|
||||
target.plural_name_normalized = None
|
||||
|
||||
|
||||
@event.listens_for(IngredientUnitAliasModel.name, "set")
|
||||
def receive_unit_alias_name(target: IngredientUnitAliasModel, value: str, oldvalue, initiator):
|
||||
target.name_normalized = IngredientUnitAliasModel.normalize(value)
|
||||
|
||||
|
||||
@event.listens_for(IngredientFoodAliasModel.name, "set")
|
||||
def receive_food_alias_name(target: IngredientFoodAliasModel, value: str, oldvalue, initiator):
|
||||
target.name_normalized = IngredientFoodAliasModel.normalize(value)
|
||||
|
||||
|
||||
@event.listens_for(RecipeIngredientModel.note, "set")
|
||||
def receive_ingredient_note(target: RecipeIngredientModel, value: str | None, oldvalue, initiator):
|
||||
if value is not None:
|
||||
|
||||
@@ -47,13 +47,17 @@ from .recipe_comments import (
|
||||
from .recipe_image_types import RecipeImageTypes
|
||||
from .recipe_ingredient import (
|
||||
CreateIngredientFood,
|
||||
CreateIngredientFoodAlias,
|
||||
CreateIngredientUnit,
|
||||
CreateIngredientUnitAlias,
|
||||
IngredientConfidence,
|
||||
IngredientFood,
|
||||
IngredientFoodAlias,
|
||||
IngredientFoodPagination,
|
||||
IngredientRequest,
|
||||
IngredientsRequest,
|
||||
IngredientUnit,
|
||||
IngredientUnitAlias,
|
||||
IngredientUnitPagination,
|
||||
MergeFood,
|
||||
MergeUnit,
|
||||
@@ -88,25 +92,6 @@ __all__ = [
|
||||
"RecipeToolOut",
|
||||
"RecipeToolResponse",
|
||||
"RecipeToolSave",
|
||||
"RecipeTimelineEventCreate",
|
||||
"RecipeTimelineEventIn",
|
||||
"RecipeTimelineEventOut",
|
||||
"RecipeTimelineEventPagination",
|
||||
"RecipeTimelineEventUpdate",
|
||||
"TimelineEventImage",
|
||||
"TimelineEventType",
|
||||
"RecipeAsset",
|
||||
"RecipeSettings",
|
||||
"RecipeShareToken",
|
||||
"RecipeShareTokenCreate",
|
||||
"RecipeShareTokenSave",
|
||||
"RecipeShareTokenSummary",
|
||||
"RecipeDuplicate",
|
||||
"RecipeSlug",
|
||||
"RecipeZipTokenResponse",
|
||||
"SlugResponse",
|
||||
"UpdateImageResponse",
|
||||
"RecipeNote",
|
||||
"CategoryBase",
|
||||
"CategoryIn",
|
||||
"CategoryOut",
|
||||
@@ -117,12 +102,6 @@ __all__ = [
|
||||
"TagIn",
|
||||
"TagOut",
|
||||
"TagSave",
|
||||
"RecipeCommentCreate",
|
||||
"RecipeCommentOut",
|
||||
"RecipeCommentPagination",
|
||||
"RecipeCommentSave",
|
||||
"RecipeCommentUpdate",
|
||||
"UserBase",
|
||||
"AssignCategories",
|
||||
"AssignSettings",
|
||||
"AssignTags",
|
||||
@@ -130,28 +109,19 @@ __all__ = [
|
||||
"ExportBase",
|
||||
"ExportRecipes",
|
||||
"ExportTypes",
|
||||
"IngredientReferences",
|
||||
"RecipeStep",
|
||||
"RecipeShareToken",
|
||||
"RecipeShareTokenCreate",
|
||||
"RecipeShareTokenSave",
|
||||
"RecipeShareTokenSummary",
|
||||
"ScrapeRecipe",
|
||||
"ScrapeRecipeTest",
|
||||
"RecipeCommentCreate",
|
||||
"RecipeCommentOut",
|
||||
"RecipeCommentPagination",
|
||||
"RecipeCommentSave",
|
||||
"RecipeCommentUpdate",
|
||||
"UserBase",
|
||||
"RecipeImageTypes",
|
||||
"Nutrition",
|
||||
"CreateIngredientFood",
|
||||
"CreateIngredientUnit",
|
||||
"IngredientConfidence",
|
||||
"IngredientFood",
|
||||
"IngredientFoodPagination",
|
||||
"IngredientRequest",
|
||||
"IngredientUnit",
|
||||
"IngredientUnitPagination",
|
||||
"IngredientsRequest",
|
||||
"MergeFood",
|
||||
"MergeUnit",
|
||||
"ParsedIngredient",
|
||||
"RecipeIngredient",
|
||||
"RecipeIngredientBase",
|
||||
"RegisteredParser",
|
||||
"SaveIngredientFood",
|
||||
"SaveIngredientUnit",
|
||||
"UnitFoodBase",
|
||||
"CreateRecipe",
|
||||
"CreateRecipeBulk",
|
||||
"CreateRecipeByUrlBulk",
|
||||
@@ -165,6 +135,44 @@ __all__ = [
|
||||
"RecipeTagPagination",
|
||||
"RecipeTool",
|
||||
"RecipeToolPagination",
|
||||
"ScrapeRecipe",
|
||||
"ScrapeRecipeTest",
|
||||
"IngredientReferences",
|
||||
"RecipeStep",
|
||||
"CreateIngredientFood",
|
||||
"CreateIngredientFoodAlias",
|
||||
"CreateIngredientUnit",
|
||||
"CreateIngredientUnitAlias",
|
||||
"IngredientConfidence",
|
||||
"IngredientFood",
|
||||
"IngredientFoodAlias",
|
||||
"IngredientFoodPagination",
|
||||
"IngredientRequest",
|
||||
"IngredientUnit",
|
||||
"IngredientUnitAlias",
|
||||
"IngredientUnitPagination",
|
||||
"IngredientsRequest",
|
||||
"MergeFood",
|
||||
"MergeUnit",
|
||||
"ParsedIngredient",
|
||||
"RecipeIngredient",
|
||||
"RecipeIngredientBase",
|
||||
"RegisteredParser",
|
||||
"SaveIngredientFood",
|
||||
"SaveIngredientUnit",
|
||||
"UnitFoodBase",
|
||||
"RecipeAsset",
|
||||
"RecipeTimelineEventCreate",
|
||||
"RecipeTimelineEventIn",
|
||||
"RecipeTimelineEventOut",
|
||||
"RecipeTimelineEventPagination",
|
||||
"RecipeTimelineEventUpdate",
|
||||
"TimelineEventImage",
|
||||
"TimelineEventType",
|
||||
"RecipeDuplicate",
|
||||
"RecipeSlug",
|
||||
"RecipeZipTokenResponse",
|
||||
"SlugResponse",
|
||||
"UpdateImageResponse",
|
||||
"Nutrition",
|
||||
"RecipeSettings",
|
||||
"RecipeNote",
|
||||
]
|
||||
|
||||
@@ -33,12 +33,23 @@ def display_fraction(fraction: Fraction):
|
||||
|
||||
class UnitFoodBase(MealieModel):
|
||||
name: str
|
||||
plural_name: str | None = None
|
||||
description: str = ""
|
||||
extras: dict | None = {}
|
||||
|
||||
|
||||
class CreateIngredientFoodAlias(MealieModel):
|
||||
name: str
|
||||
|
||||
|
||||
class IngredientFoodAlias(CreateIngredientFoodAlias):
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
class CreateIngredientFood(UnitFoodBase):
|
||||
label_id: UUID4 | None = None
|
||||
aliases: list[CreateIngredientFoodAlias] = []
|
||||
|
||||
|
||||
class SaveIngredientFood(CreateIngredientFood):
|
||||
@@ -48,10 +59,12 @@ class SaveIngredientFood(CreateIngredientFood):
|
||||
class IngredientFood(CreateIngredientFood):
|
||||
id: UUID4
|
||||
label: MultiPurposeLabelSummary | None = None
|
||||
aliases: list[IngredientFoodAlias] = []
|
||||
|
||||
created_at: datetime.datetime | None
|
||||
update_at: datetime.datetime | None
|
||||
|
||||
_searchable_properties: ClassVar[list[str]] = ["name_normalized"]
|
||||
_searchable_properties: ClassVar[list[str]] = ["name_normalized", "plural_name_normalized"]
|
||||
_normalize_search: ClassVar[bool] = True
|
||||
|
||||
class Config:
|
||||
@@ -67,10 +80,21 @@ class IngredientFoodPagination(PaginationBase):
|
||||
items: list[IngredientFood]
|
||||
|
||||
|
||||
class CreateIngredientUnitAlias(MealieModel):
|
||||
name: str
|
||||
|
||||
|
||||
class IngredientUnitAlias(CreateIngredientUnitAlias):
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
class CreateIngredientUnit(UnitFoodBase):
|
||||
fraction: bool = True
|
||||
abbreviation: str = ""
|
||||
plural_abbreviation: str | None = ""
|
||||
use_abbreviation: bool = False
|
||||
aliases: list[CreateIngredientUnitAlias] = []
|
||||
|
||||
|
||||
class SaveIngredientUnit(CreateIngredientUnit):
|
||||
@@ -79,10 +103,17 @@ class SaveIngredientUnit(CreateIngredientUnit):
|
||||
|
||||
class IngredientUnit(CreateIngredientUnit):
|
||||
id: UUID4
|
||||
aliases: list[IngredientUnitAlias] = []
|
||||
|
||||
created_at: datetime.datetime | None
|
||||
update_at: datetime.datetime | None
|
||||
|
||||
_searchable_properties: ClassVar[list[str]] = ["name_normalized", "abbreviation_normalized"]
|
||||
_searchable_properties: ClassVar[list[str]] = [
|
||||
"name_normalized",
|
||||
"plural_name_normalized",
|
||||
"abbreviation_normalized",
|
||||
"plural_abbreviation_normalized",
|
||||
]
|
||||
_normalize_search: ClassVar[bool] = True
|
||||
|
||||
class Config:
|
||||
@@ -165,6 +196,36 @@ class RecipeIngredientBase(MealieModel):
|
||||
|
||||
return f"{whole_number} {display_fraction(qty)}"
|
||||
|
||||
def _format_unit_for_display(self) -> str:
|
||||
if not self.unit:
|
||||
return ""
|
||||
|
||||
use_plural = self.quantity and self.quantity > 1
|
||||
unit_val = ""
|
||||
if self.unit.use_abbreviation:
|
||||
if use_plural:
|
||||
unit_val = self.unit.plural_abbreviation or self.unit.abbreviation
|
||||
else:
|
||||
unit_val = self.unit.abbreviation
|
||||
|
||||
if not unit_val:
|
||||
if use_plural:
|
||||
unit_val = self.unit.plural_name or self.unit.name
|
||||
else:
|
||||
unit_val = self.unit.name
|
||||
|
||||
return unit_val
|
||||
|
||||
def _format_food_for_display(self) -> str:
|
||||
if not self.food:
|
||||
return ""
|
||||
|
||||
use_plural = (not self.quantity) or self.quantity > 1
|
||||
if use_plural:
|
||||
return self.food.plural_name or self.food.name
|
||||
else:
|
||||
return self.food.name
|
||||
|
||||
def _format_display(self) -> str:
|
||||
components = []
|
||||
|
||||
@@ -183,15 +244,15 @@ class RecipeIngredientBase(MealieModel):
|
||||
components.append(self.note or "")
|
||||
else:
|
||||
if self.quantity and self.unit:
|
||||
components.append(self.unit.abbreviation if self.unit.use_abbreviation else self.unit.name)
|
||||
components.append(self._format_unit_for_display())
|
||||
|
||||
if self.food:
|
||||
components.append(self.food.name)
|
||||
components.append(self._format_food_for_display())
|
||||
|
||||
if self.note:
|
||||
components.append(self.note)
|
||||
|
||||
return " ".join(components)
|
||||
return " ".join(components).strip()
|
||||
|
||||
|
||||
class IngredientUnitPagination(PaginationBase):
|
||||
|
||||
@@ -38,36 +38,60 @@ class ABCIngredientParser(ABC):
|
||||
self.group_id = group_id
|
||||
self.session = session
|
||||
|
||||
self._foods_by_name: dict[str, IngredientFood] | None = None
|
||||
self._units_by_name: dict[str, IngredientUnit] | None = None
|
||||
self._foods_by_alias: dict[str, IngredientFood] | None = None
|
||||
self._units_by_alias: dict[str, IngredientUnit] | None = None
|
||||
|
||||
@property
|
||||
def _repos(self) -> AllRepositories:
|
||||
return get_repositories(self.session)
|
||||
|
||||
@property
|
||||
def foods_by_normalized_name(self) -> dict[str, IngredientFood]:
|
||||
if self._foods_by_name is None:
|
||||
def foods_by_alias(self) -> dict[str, IngredientFood]:
|
||||
if self._foods_by_alias is None:
|
||||
foods_repo = self._repos.ingredient_foods.by_group(self.group_id)
|
||||
|
||||
query = PaginationQuery(page=1, per_page=-1)
|
||||
all_foods = foods_repo.page_all(query).items
|
||||
self._foods_by_name = {IngredientFoodModel.normalize(food.name): food for food in all_foods if food.name}
|
||||
|
||||
return self._foods_by_name
|
||||
foods_by_alias: dict[str, IngredientFood] = {}
|
||||
for food in all_foods:
|
||||
if food.name:
|
||||
foods_by_alias[IngredientFoodModel.normalize(food.name)] = food
|
||||
if food.plural_name:
|
||||
foods_by_alias[IngredientFoodModel.normalize(food.plural_name)] = food
|
||||
|
||||
for alias in food.aliases or []:
|
||||
if alias.name:
|
||||
foods_by_alias[IngredientFoodModel.normalize(alias.name)] = food
|
||||
|
||||
self._foods_by_alias = foods_by_alias
|
||||
|
||||
return self._foods_by_alias
|
||||
|
||||
@property
|
||||
def units_by_normalized_name_or_abbreviation(self) -> dict[str, IngredientUnit]:
|
||||
if self._units_by_name is None:
|
||||
def units_by_alias(self) -> dict[str, IngredientUnit]:
|
||||
if self._units_by_alias is None:
|
||||
units_repo = self._repos.ingredient_units.by_group(self.group_id)
|
||||
|
||||
query = PaginationQuery(page=1, per_page=-1)
|
||||
all_units = units_repo.page_all(query).items
|
||||
self._units_by_name = {
|
||||
IngredientUnitModel.normalize(unit.name): unit for unit in all_units if unit.name
|
||||
} | {IngredientUnitModel.normalize(unit.abbreviation): unit for unit in all_units if unit.abbreviation}
|
||||
|
||||
return self._units_by_name
|
||||
units_by_alias: dict[str, IngredientUnit] = {}
|
||||
for unit in all_units:
|
||||
if unit.name:
|
||||
units_by_alias[IngredientUnitModel.normalize(unit.name)] = unit
|
||||
if unit.plural_name:
|
||||
units_by_alias[IngredientUnitModel.normalize(unit.plural_name)] = unit
|
||||
if unit.abbreviation:
|
||||
units_by_alias[IngredientUnitModel.normalize(unit.abbreviation)] = unit
|
||||
if unit.plural_abbreviation:
|
||||
units_by_alias[IngredientUnitModel.normalize(unit.plural_abbreviation)] = unit
|
||||
|
||||
for alias in unit.aliases or []:
|
||||
if alias.name:
|
||||
units_by_alias[IngredientUnitModel.normalize(alias.name)] = unit
|
||||
|
||||
self._units_by_alias = units_by_alias
|
||||
|
||||
return self._units_by_alias
|
||||
|
||||
@property
|
||||
def food_fuzzy_match_threshold(self) -> int:
|
||||
@@ -111,7 +135,7 @@ class ABCIngredientParser(ABC):
|
||||
match_value = IngredientFoodModel.normalize(food.name)
|
||||
return self.find_match(
|
||||
match_value,
|
||||
store_map=self.foods_by_normalized_name,
|
||||
store_map=self.foods_by_alias,
|
||||
fuzzy_match_threshold=self.food_fuzzy_match_threshold,
|
||||
)
|
||||
|
||||
@@ -122,7 +146,7 @@ class ABCIngredientParser(ABC):
|
||||
match_value = IngredientUnitModel.normalize(unit.name)
|
||||
return self.find_match(
|
||||
match_value,
|
||||
store_map=self.units_by_normalized_name_or_abbreviation,
|
||||
store_map=self.units_by_alias,
|
||||
fuzzy_match_threshold=self.unit_fuzzy_match_threshold,
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user