mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-06-03 05:35:02 -04:00
feat: sort by labels in shopping list copy if labels toggled (#3226)
* feat: sort by labels in shopping list copy if labels toggled * fix: call parent validator in shopping list item out (#3227) * fix: add a unit test for (#3227) * fixed messy post_validate logic * feat: label headings in shopping list copy * feat: blank line for each group in shopping list copy --------- Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
This commit is contained in:
parent
2809b87ab0
commit
52de8afe2d
@ -337,21 +337,50 @@ export default defineComponent({
|
|||||||
const copy = useCopyList();
|
const copy = useCopyList();
|
||||||
|
|
||||||
function copyListItems(copyType: CopyTypes) {
|
function copyListItems(copyType: CopyTypes) {
|
||||||
const items = shoppingList.value?.listItems?.filter((item) => !item.checked);
|
const text: string[] = [];
|
||||||
|
|
||||||
if (!items) {
|
if (preferences.value.viewByLabel) {
|
||||||
return;
|
// if we're sorting by label, we want the copied text in subsections
|
||||||
|
Object.entries(itemsByLabel.value).forEach(([label, items], idx) => {
|
||||||
|
// for every group except the first, add a blank line
|
||||||
|
if (idx) {
|
||||||
|
text.push("")
|
||||||
|
}
|
||||||
|
|
||||||
|
// add an appropriate heading for the label depending on the copy format
|
||||||
|
text.push(formatCopiedLabelHeading(copyType, label))
|
||||||
|
|
||||||
|
// now add the appropriately formatted list items with the given label
|
||||||
|
items.forEach((item) => text.push(formatCopiedListItem(copyType, item)))
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// labels are toggled off, so just copy in the order they come in
|
||||||
|
const items = shoppingList.value?.listItems?.filter((item) => !item.checked)
|
||||||
|
|
||||||
|
items?.forEach((item) => {
|
||||||
|
text.push(formatCopiedListItem(copyType, item))
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const text: string[] = items.map((itm) => itm.display || "");
|
copy.copyPlain(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatCopiedListItem(copyType: CopyTypes, item: ShoppingListItemOut): string {
|
||||||
|
const display = item.display || ""
|
||||||
switch (copyType) {
|
switch (copyType) {
|
||||||
case "markdown":
|
case "markdown":
|
||||||
copy.copyMarkdownCheckList(text);
|
return `- [ ] ${display}`
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
copy.copyPlain(text);
|
return display
|
||||||
break;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatCopiedLabelHeading(copyType: CopyTypes, label: string): string {
|
||||||
|
switch (copyType) {
|
||||||
|
case "markdown":
|
||||||
|
return `# ${label}`
|
||||||
|
default:
|
||||||
|
return `[${label}]`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +101,7 @@ class ShoppingListItemOut(ShoppingListItemBase):
|
|||||||
update_at: datetime | None = None
|
update_at: datetime | None = None
|
||||||
|
|
||||||
@model_validator(mode="after")
|
@model_validator(mode="after")
|
||||||
def post_validate(self):
|
def populate_missing_label(self):
|
||||||
# if we're missing a label, but the food has a label, use that as the label
|
# if we're missing a label, but the food has a label, use that as the label
|
||||||
if (not self.label) and (self.food and self.food.label):
|
if (not self.label) and (self.food and self.food.label):
|
||||||
self.label = self.food.label
|
self.label = self.food.label
|
||||||
|
@ -184,13 +184,13 @@ class Recipe(RecipeSummary):
|
|||||||
model_config = ConfigDict(from_attributes=True)
|
model_config = ConfigDict(from_attributes=True)
|
||||||
|
|
||||||
@model_validator(mode="after")
|
@model_validator(mode="after")
|
||||||
def post_validate(self):
|
def calculate_missing_food_flags_and_format_display(self):
|
||||||
# the ingredient disable_amount property is unreliable,
|
|
||||||
# so we set it here and recalculate the display property
|
|
||||||
disable_amount = self.settings.disable_amount if self.settings else True
|
disable_amount = self.settings.disable_amount if self.settings else True
|
||||||
for ingredient in self.recipe_ingredient:
|
for ingredient in self.recipe_ingredient:
|
||||||
ingredient.disable_amount = disable_amount
|
ingredient.disable_amount = disable_amount
|
||||||
ingredient.is_food = not ingredient.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()
|
ingredient.display = ingredient._format_display()
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
@ -145,7 +145,7 @@ class RecipeIngredientBase(MealieModel):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
@model_validator(mode="after")
|
@model_validator(mode="after")
|
||||||
def post_validate(self):
|
def calculate_missing_food_flags(self):
|
||||||
# calculate missing is_food and disable_amount values
|
# calculate missing is_food and disable_amount values
|
||||||
# we can't do this in a validator since they depend on each other
|
# 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:
|
if self.is_food is None and self.disable_amount is not None:
|
||||||
@ -156,7 +156,10 @@ class RecipeIngredientBase(MealieModel):
|
|||||||
self.is_food = bool(self.food)
|
self.is_food = bool(self.food)
|
||||||
self.disable_amount = not self.is_food
|
self.disable_amount = not self.is_food
|
||||||
|
|
||||||
# format the display property
|
return self
|
||||||
|
|
||||||
|
@model_validator(mode="after")
|
||||||
|
def format_display(self):
|
||||||
if not self.display:
|
if not self.display:
|
||||||
self.display = self._format_display()
|
self.display = self._format_display()
|
||||||
|
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
from mealie.schema.group.group_shopping_list import ShoppingListItemOut
|
||||||
|
|
||||||
|
|
||||||
|
def test_shopping_list_ingredient_validation():
|
||||||
|
db_obj = {
|
||||||
|
"quantity": 8,
|
||||||
|
"unit": None,
|
||||||
|
"food": {
|
||||||
|
"id": "4cf32eeb-d136-472d-86c7-287b6328d21f",
|
||||||
|
"name": "bell peppers",
|
||||||
|
"pluralName": None,
|
||||||
|
"description": "",
|
||||||
|
"extras": {},
|
||||||
|
"labelId": None,
|
||||||
|
"aliases": [],
|
||||||
|
"label": None,
|
||||||
|
"createdAt": "2024-02-26T18:29:46.190754",
|
||||||
|
"updateAt": "2024-02-26T18:29:46.190758",
|
||||||
|
},
|
||||||
|
"note": "",
|
||||||
|
"isFood": True,
|
||||||
|
"disableAmount": False,
|
||||||
|
"shoppingListId": "dc8bce82-2da9-49f0-94e6-6d69d311490e",
|
||||||
|
"checked": False,
|
||||||
|
"position": 5,
|
||||||
|
"foodId": "4cf32eeb-d136-472d-86c7-287b6328d21f",
|
||||||
|
"labelId": None,
|
||||||
|
"unitId": None,
|
||||||
|
"extras": {},
|
||||||
|
"id": "80f4df25-6139-4d30-be0c-4100f50e5396",
|
||||||
|
"label": None,
|
||||||
|
"recipeReferences": [],
|
||||||
|
"createdAt": "2024-02-27T10:18:19.274677",
|
||||||
|
"updateAt": "2024-02-27T11:26:32.643392",
|
||||||
|
}
|
||||||
|
out = ShoppingListItemOut.model_validate(db_obj)
|
||||||
|
assert out.display == "8 bell peppers"
|
Loading…
x
Reference in New Issue
Block a user