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:
Olly Welch 2024-02-28 22:06:04 +00:00 committed by GitHub
parent 2809b87ab0
commit 52de8afe2d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 83 additions and 14 deletions

View File

@ -337,21 +337,50 @@ export default defineComponent({
const copy = useCopyList();
function copyListItems(copyType: CopyTypes) {
const items = shoppingList.value?.listItems?.filter((item) => !item.checked);
const text: string[] = [];
if (!items) {
return;
if (preferences.value.viewByLabel) {
// 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) {
case "markdown":
copy.copyMarkdownCheckList(text);
break;
return `- [ ] ${display}`
default:
copy.copyPlain(text);
break;
return display
}
}
function formatCopiedLabelHeading(copyType: CopyTypes, label: string): string {
switch (copyType) {
case "markdown":
return `# ${label}`
default:
return `[${label}]`
}
}

View File

@ -101,7 +101,7 @@ class ShoppingListItemOut(ShoppingListItemBase):
update_at: datetime | None = None
@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 (not self.label) and (self.food and self.food.label):
self.label = self.food.label

View File

@ -184,13 +184,13 @@ class Recipe(RecipeSummary):
model_config = ConfigDict(from_attributes=True)
@model_validator(mode="after")
def post_validate(self):
# the ingredient disable_amount property is unreliable,
# so we set it here and recalculate the display property
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

View File

@ -145,7 +145,7 @@ class RecipeIngredientBase(MealieModel):
"""
@model_validator(mode="after")
def post_validate(self):
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:
@ -156,7 +156,10 @@ class RecipeIngredientBase(MealieModel):
self.is_food = bool(self.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:
self.display = self._format_display()

View File

@ -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"