Merge branch 'mealie-next' into fix/warn-on-edit-nav

This commit is contained in:
boc-the-git 2024-02-08 22:21:35 +11:00 committed by GitHub
commit 5d049d5696
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 227 additions and 95 deletions

View File

@ -43,7 +43,7 @@ export const usePublicFoodStore = function (groupSlug: string) {
}, },
}; };
if (!foodStore.value) { if (!foodStore.value || foodStore.value.length === 0) {
foodStore = actions.getAll(); foodStore = actions.getAll();
} }
@ -61,7 +61,7 @@ export const useFoodStore = function () {
}, },
}; };
if (!foodStore) { if (!foodStore.value || foodStore.value.length === 0) {
foodStore = actions.getAll(); foodStore = actions.getAll();
} }

View File

@ -33,11 +33,11 @@ export function useLabelStore() {
const actions = { const actions = {
...useStoreActions<MultiPurposeLabelOut>(api.multiPurposeLabels, labelStore, loading), ...useStoreActions<MultiPurposeLabelOut>(api.multiPurposeLabels, labelStore, loading),
flushStore() { flushStore() {
labelStore.value =[]; labelStore.value = [];
}, },
}; };
if (!labelStore.value) { if (!labelStore.value || labelStore.value?.length === 0) {
labelStore = actions.getAll(); labelStore = actions.getAll();
} }

View File

@ -44,7 +44,7 @@ export const useUnitStore = function () {
}, },
}; };
if (!unitStore.value) { if (!unitStore.value || unitStore.value.length === 0) {
unitStore = actions.getAll(); unitStore = actions.getAll();
} }

View File

@ -200,7 +200,7 @@
"created-on-date": "נוצר ב-{0}", "created-on-date": "נוצר ב-{0}",
"unsaved-changes": "יש שינויים שלא נשמרו. לצאת לפני שמירה? אשר לשמירה, בטל למחיקת שינויים.", "unsaved-changes": "יש שינויים שלא נשמרו. לצאת לפני שמירה? אשר לשמירה, בטל למחיקת שינויים.",
"clipboard-copy-failure": "כשלון בהעתקה ללוח ההדבקה.", "clipboard-copy-failure": "כשלון בהעתקה ללוח ההדבקה.",
"confirm-delete-generic-items": "Are you sure you want to delete the following items?" "confirm-delete-generic-items": "האם אתה בטוח שברצונך למחוק את הפריטים הנבחרים?"
}, },
"group": { "group": {
"are-you-sure-you-want-to-delete-the-group": "האם את/ה בטוח/ה שברצונך למחוק את <b>{groupName}<b/>?", "are-you-sure-you-want-to-delete-the-group": "האם את/ה בטוח/ה שברצונך למחוק את <b>{groupName}<b/>?",
@ -259,7 +259,7 @@
}, },
"meal-plan": { "meal-plan": {
"create-a-new-meal-plan": "יצירת תכנית ארוחות חדשה", "create-a-new-meal-plan": "יצירת תכנית ארוחות חדשה",
"update-this-meal-plan": "Update this Meal Plan", "update-this-meal-plan": "עדכן את תכנון הארוחות",
"dinner-this-week": "ארוחות ערב השבוע", "dinner-this-week": "ארוחות ערב השבוע",
"dinner-today": "ארוחת ערב היום", "dinner-today": "ארוחת ערב היום",
"dinner-tonight": "ארוחת ערב היום", "dinner-tonight": "ארוחת ערב היום",
@ -474,11 +474,11 @@
"add-to-timeline": "הוסף לציר הזמן", "add-to-timeline": "הוסף לציר הזמן",
"recipe-added-to-list": "מתכון נוסף לרשימה", "recipe-added-to-list": "מתכון נוסף לרשימה",
"recipes-added-to-list": "מתכונים הוספו לרשימה", "recipes-added-to-list": "מתכונים הוספו לרשימה",
"successfully-added-to-list": "Successfully added to list", "successfully-added-to-list": "נוסף לרשימה בהצלחה",
"recipe-added-to-mealplan": "מתכון נוסף לתכנון ארוחות", "recipe-added-to-mealplan": "מתכון נוסף לתכנון ארוחות",
"failed-to-add-recipes-to-list": "כשלון בהוספת מתכון לרשימה", "failed-to-add-recipes-to-list": "כשלון בהוספת מתכון לרשימה",
"failed-to-add-recipe-to-mealplan": "הוספת מתכון לתכנון ארוחות נכשלה", "failed-to-add-recipe-to-mealplan": "הוספת מתכון לתכנון ארוחות נכשלה",
"failed-to-add-to-list": "Failed to add to list", "failed-to-add-to-list": "כשלון בהוספה לרשימה",
"yield": "תשואה", "yield": "תשואה",
"quantity": "כמות", "quantity": "כמות",
"choose-unit": "בחירת יחידת מידה", "choose-unit": "בחירת יחידת מידה",
@ -515,7 +515,7 @@
"how-did-it-turn-out": "איך יצא?", "how-did-it-turn-out": "איך יצא?",
"user-made-this": "{user} הכין את זה", "user-made-this": "{user} הכין את זה",
"last-made-date": "נעשה לאחרונה ב{date}", "last-made-date": "נעשה לאחרונה ב{date}",
"api-extras-description": "Recipes extras are a key feature of the Mealie API. They allow you to create custom JSON key/value pairs within a recipe, to reference from 3rd party applications. You can use these keys to provide information, for example to trigger automations or custom messages to relay to your desired device.", "api-extras-description": "מתכונים נוספים הם יכולת מפתח של Mealie API. הם מאפשרים ליצור צמדי key/value בצורת JSON על מנת לקרוא אותם בתוכנת צד שלישית. תוכלו להשתמש בצמדים האלה כדי לספק מידע, לדוגמא להפעיל אוטומציות או הודעות מותאמות אישית למכשירים מסויימים.",
"message-key": "מפתח הודעה", "message-key": "מפתח הודעה",
"parse": "ניתוח", "parse": "ניתוח",
"attach-images-hint": "הוסף תמונות ע\"י גרירה ושחרור אל תוך העורך", "attach-images-hint": "הוסף תמונות ע\"י גרירה ושחרור אל תוך העורך",
@ -537,8 +537,8 @@
"new-recipe-names-must-be-unique": "שם מתכון חדש חייב להיות ייחודי", "new-recipe-names-must-be-unique": "שם מתכון חדש חייב להיות ייחודי",
"scrape-recipe": "קריאת מתכון", "scrape-recipe": "קריאת מתכון",
"scrape-recipe-description": "קריאת מתכון בעזרת לינק. ספק את הלינק של האתר שברצונך לקרוא, ומילי תנסה לקרוא את המתכון מהאתר ולהוסיף אותו לאוסף.", "scrape-recipe-description": "קריאת מתכון בעזרת לינק. ספק את הלינק של האתר שברצונך לקרוא, ומילי תנסה לקרוא את המתכון מהאתר ולהוסיף אותו לאוסף.",
"scrape-recipe-have-a-lot-of-recipes": "Have a lot of recipes you want to scrape at once?", "scrape-recipe-have-a-lot-of-recipes": "יש לך הרבה מתכונים שאתה רוצה לקרוא בבת אחת?",
"scrape-recipe-suggest-bulk-importer": "Try out the bulk importer", "scrape-recipe-suggest-bulk-importer": "נסה את יכולת קריאת רשימה",
"import-original-keywords-as-tags": "ייבא שמות מפתח מקוריות כתגיות", "import-original-keywords-as-tags": "ייבא שמות מפתח מקוריות כתגיות",
"stay-in-edit-mode": "השאר במצב עריכה", "stay-in-edit-mode": "השאר במצב עריכה",
"import-from-zip": "ייבא מקובץ", "import-from-zip": "ייבא מקובץ",
@ -562,7 +562,7 @@
"upload-image": "העלה תמונה", "upload-image": "העלה תמונה",
"screen-awake": "השאר את המסך פעיל", "screen-awake": "השאר את המסך פעיל",
"remove-image": "האם למחוק את התמונה?", "remove-image": "האם למחוק את התמונה?",
"nextStep": "Next step" "nextStep": "השלב הבא"
}, },
"search": { "search": {
"advanced-search": "חיפוש מתקדם", "advanced-search": "חיפוש מתקדם",
@ -797,7 +797,7 @@
"untagged-count": "לא מתוייג {count}", "untagged-count": "לא מתוייג {count}",
"create-a-tag": "צור תגית", "create-a-tag": "צור תגית",
"tag-name": "שם תגית", "tag-name": "שם תגית",
"tag": "Tag" "tag": "תגית"
}, },
"tool": { "tool": {
"tools": "כלים", "tools": "כלים",
@ -807,7 +807,7 @@
"create-new-tool": "יצירת כלי חדש", "create-new-tool": "יצירת כלי חדש",
"on-hand-checkbox-label": "הראה מה יש לי במטבח", "on-hand-checkbox-label": "הראה מה יש לי במטבח",
"required-tools": "צריך כלים", "required-tools": "צריך כלים",
"tool": "Tool" "tool": "כלי"
}, },
"user": { "user": {
"admin": "אדמין", "admin": "אדמין",
@ -898,10 +898,10 @@
"user-can-organize-group-data": "משתמש יכול לשנות מידע של קבוצה", "user-can-organize-group-data": "משתמש יכול לשנות מידע של קבוצה",
"enable-advanced-features": "אפשר אפשרויות מתקדמות", "enable-advanced-features": "אפשר אפשרויות מתקדמות",
"it-looks-like-this-is-your-first-time-logging-in": "נראה שזו ההתחברות הראשונה שלך.", "it-looks-like-this-is-your-first-time-logging-in": "נראה שזו ההתחברות הראשונה שלך.",
"dont-want-to-see-this-anymore-be-sure-to-change-your-email": "Don't want to see this anymore? Be sure to change your email in your user settings!", "dont-want-to-see-this-anymore-be-sure-to-change-your-email": "לא רוצה לראות את זה יותר? דאג לשנות את המייל של בהגדרות המשתמש!",
"forgot-password": "שכחתי סיסמא", "forgot-password": "שכחתי סיסמא",
"forgot-password-text": "Please enter your email address and we will send you a link to reset your password.", "forgot-password-text": "נא לספק כתובת דוא\"ל. אנו נשלח לך הודעת דוא\"ל לצורך איפוס הסיסמה שלך.",
"changes-reflected-immediately": "Changes to this user will be reflected immediately." "changes-reflected-immediately": "השינויים למשתמש זה יבוצעו מיידית."
}, },
"language-dialog": { "language-dialog": {
"translated": "תורגם", "translated": "תורגם",
@ -923,8 +923,8 @@
"food-label": "תוית אוכל", "food-label": "תוית אוכל",
"edit-food": "עריכת מזון", "edit-food": "עריכת מזון",
"food-data": "נתוני אוכל", "food-data": "נתוני אוכל",
"example-food-singular": "ex: Onion", "example-food-singular": "דוגמא: בצל",
"example-food-plural": "ex: Onions" "example-food-plural": "דוגמא: בצלים"
}, },
"units": { "units": {
"seed-dialog-text": "אכלס את מסד הנתונים עם יחידות מדידה בהתאם לשפה המקומית שלך.", "seed-dialog-text": "אכלס את מסד הנתונים עם יחידות מדידה בהתאם לשפה המקומית שלך.",
@ -935,7 +935,7 @@
"merging-unit-into-unit": "ממזג את {0} לתוך {1}", "merging-unit-into-unit": "ממזג את {0} לתוך {1}",
"create-unit": "יצירת יחידה", "create-unit": "יצירת יחידה",
"abbreviation": "קיצור", "abbreviation": "קיצור",
"plural-abbreviation": "Plural Abbreviation", "plural-abbreviation": "צורת הרבית",
"description": "תיאור", "description": "תיאור",
"display-as-fraction": "הצגה כשבר", "display-as-fraction": "הצגה כשבר",
"use-abbreviation": "השתמש בקיצור", "use-abbreviation": "השתמש בקיצור",
@ -943,10 +943,10 @@
"unit-data": "נתוני יחידה", "unit-data": "נתוני יחידה",
"use-abbv": "השתמש בקיצור", "use-abbv": "השתמש בקיצור",
"fraction": "שבר", "fraction": "שבר",
"example-unit-singular": "ex: Tablespoon", "example-unit-singular": "דוגמא: כפית",
"example-unit-plural": "ex: Tablespoons", "example-unit-plural": "דוגמא: כפיות",
"example-unit-abbreviation-singular": "ex: Tbsp", "example-unit-abbreviation-singular": "דוגמא: כף",
"example-unit-abbreviation-plural": "ex: Tbsps" "example-unit-abbreviation-plural": "דוגמא: כפות"
}, },
"labels": { "labels": {
"seed-dialog-text": "אכלס את מסד הנתונים בתגיות נפוצות בהתאם לשפה המקומית שלך.", "seed-dialog-text": "אכלס את מסד הנתונים בתגיות נפוצות בהתאם לשפה המקומית שלך.",
@ -1187,7 +1187,7 @@
"require-all-tools": "זקוק לכל הכלים", "require-all-tools": "זקוק לכל הכלים",
"cookbook-name": "שם ספר בישול", "cookbook-name": "שם ספר בישול",
"cookbook-with-name": "ספר בישול {0}", "cookbook-with-name": "ספר בישול {0}",
"create-a-cookbook": "Create a Cookbook", "create-a-cookbook": "צור ספר בישול חדש",
"cookbook": "Cookbook" "cookbook": "ספר בישול"
} }
} }

View File

@ -200,7 +200,7 @@
"created-on-date": "Létrehozva: {0}", "created-on-date": "Létrehozva: {0}",
"unsaved-changes": "El nem mentett módosításai vannak. Szeretné elmenteni, mielőtt kilép? A mentéshez kattintson az Ok, a módosítások elvetéséhez a Mégsem gombra.", "unsaved-changes": "El nem mentett módosításai vannak. Szeretné elmenteni, mielőtt kilép? A mentéshez kattintson az Ok, a módosítások elvetéséhez a Mégsem gombra.",
"clipboard-copy-failure": "Nem sikerült a vágólapra másolás.", "clipboard-copy-failure": "Nem sikerült a vágólapra másolás.",
"confirm-delete-generic-items": "Are you sure you want to delete the following items?" "confirm-delete-generic-items": "Biztos benne, hogy törölni szeretné az alábbi tételeket?"
}, },
"group": { "group": {
"are-you-sure-you-want-to-delete-the-group": "Biztosan törölni szeretnéd ezt: <b>{groupName}<b/>?", "are-you-sure-you-want-to-delete-the-group": "Biztosan törölni szeretnéd ezt: <b>{groupName}<b/>?",

View File

@ -200,7 +200,7 @@
"created-on-date": "Skapad {0}", "created-on-date": "Skapad {0}",
"unsaved-changes": "Du har osparade ändringar. Vill du spara innan du lämnar? Tryck Okej att spara, Avbryt för att ignorera ändringar.", "unsaved-changes": "Du har osparade ändringar. Vill du spara innan du lämnar? Tryck Okej att spara, Avbryt för att ignorera ändringar.",
"clipboard-copy-failure": "Det gick inte att kopiera till urklipp.", "clipboard-copy-failure": "Det gick inte att kopiera till urklipp.",
"confirm-delete-generic-items": "Are you sure you want to delete the following items?" "confirm-delete-generic-items": "Är du säker på att du vill radera följande objekt?"
}, },
"group": { "group": {
"are-you-sure-you-want-to-delete-the-group": "Är du säker på att du vill radera <b>{groupName}<b/>?", "are-you-sure-you-want-to-delete-the-group": "Är du säker på att du vill radera <b>{groupName}<b/>?",

View File

@ -200,7 +200,7 @@
"created-on-date": "{0} tarihinde oluşturuldu", "created-on-date": "{0} tarihinde oluşturuldu",
"unsaved-changes": "Kaydedilmemiş değişiklikleriniz mevcut. Ayrılmadan önce kaydetmek ister misiniz? Kaydetmek için Tamam'ı, değişiklikleri iptal etmek için İptal'i seçin.", "unsaved-changes": "Kaydedilmemiş değişiklikleriniz mevcut. Ayrılmadan önce kaydetmek ister misiniz? Kaydetmek için Tamam'ı, değişiklikleri iptal etmek için İptal'i seçin.",
"clipboard-copy-failure": "Panoya kopyalanamadı.", "clipboard-copy-failure": "Panoya kopyalanamadı.",
"confirm-delete-generic-items": "Are you sure you want to delete the following items?" "confirm-delete-generic-items": "Aşağıdaki öğeleri silmek istediğinizden emin misiniz?"
}, },
"group": { "group": {
"are-you-sure-you-want-to-delete-the-group": "<b>{groupName}<b/>'i silmek istediğine emin misin?", "are-you-sure-you-want-to-delete-the-group": "<b>{groupName}<b/>'i silmek istediğine emin misin?",
@ -1187,7 +1187,7 @@
"require-all-tools": "Require All Tools", "require-all-tools": "Require All Tools",
"cookbook-name": "Cookbook Name", "cookbook-name": "Cookbook Name",
"cookbook-with-name": "Cookbook {0}", "cookbook-with-name": "Cookbook {0}",
"create-a-cookbook": "Create a Cookbook", "create-a-cookbook": "Yemek Kitabı Oluştur",
"cookbook": "Cookbook" "cookbook": "Yemek kitabı"
} }
} }

View File

@ -33,6 +33,7 @@ export interface AppInfo {
version: string; version: string;
demoStatus: boolean; demoStatus: boolean;
allowSignup: boolean; allowSignup: boolean;
defaultGroupSlug?: string;
} }
export interface AppStartupInfo { export interface AppStartupInfo {
isFirstLogin: boolean; isFirstLogin: boolean;

View File

@ -4,17 +4,28 @@
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, useContext, useRouter } from "@nuxtjs/composition-api"; import { computed, defineComponent, useContext, useRouter } from "@nuxtjs/composition-api";
import { AppInfo } from "~/lib/api/types/admin";
export default defineComponent({ export default defineComponent({
layout: "blank", layout: "blank",
setup() { setup() {
const { $auth } = useContext(); const { $auth, $axios } = useContext();
const router = useRouter(); const router = useRouter();
const groupSlug = computed(() => $auth.user?.groupSlug); const groupSlug = computed(() => $auth.user?.groupSlug);
async function redirectPublicUserToDefaultGroup() {
const { data } = await $axios.get<AppInfo>("/api/app/about");
if (data?.defaultGroupSlug) {
router.push(`/g/${data.defaultGroupSlug}`);
} else {
router.push("/login");
}
}
if (groupSlug.value) { if (groupSlug.value) {
router.push(`/g/${groupSlug.value}`); router.push(`/g/${groupSlug.value}`);
} else { } else {
router.push("/login"); redirectPublicUserToDefaultGroup();
} }
} }
}); });

View File

@ -5,21 +5,30 @@ from mealie.core.config import get_app_settings
from mealie.core.settings.static import APP_VERSION from mealie.core.settings.static import APP_VERSION
from mealie.db.db_setup import generate_session from mealie.db.db_setup import generate_session
from mealie.db.models.users.users import User from mealie.db.models.users.users import User
from mealie.repos.all_repositories import get_repositories
from mealie.schema.admin.about import AppInfo, AppStartupInfo, AppTheme from mealie.schema.admin.about import AppInfo, AppStartupInfo, AppTheme
router = APIRouter(prefix="/about") router = APIRouter(prefix="/about")
@router.get("", response_model=AppInfo) @router.get("", response_model=AppInfo)
def get_app_info(): def get_app_info(session: Session = Depends(generate_session)):
"""Get general application information""" """Get general application information"""
settings = get_app_settings() settings = get_app_settings()
repos = get_repositories(session)
default_group = repos.groups.get_by_name(settings.DEFAULT_GROUP)
if default_group and default_group.preferences and not default_group.preferences.private_group:
default_group_slug = default_group.slug
else:
default_group_slug = None
return AppInfo( return AppInfo(
version=APP_VERSION, version=APP_VERSION,
demo_status=settings.IS_DEMO, demo_status=settings.IS_DEMO,
production=settings.PRODUCTION, production=settings.PRODUCTION,
allow_signup=settings.ALLOW_SIGNUP, allow_signup=settings.ALLOW_SIGNUP,
default_group_slug=default_group_slug,
) )

View File

@ -14,6 +14,7 @@ class AppInfo(MealieModel):
version: str version: str
demo_status: bool demo_status: bool
allow_signup: bool allow_signup: bool
default_group_slug: str | None = None
class AppTheme(MealieModel): class AppTheme(MealieModel):

View File

@ -132,7 +132,7 @@ def parse_ingredient(tokens) -> tuple[str, str]:
return ingredient, note return ingredient, note
def parse(ing_str) -> BruteParsedIngredient: def parse(ing_str, parser) -> BruteParsedIngredient:
amount = 0.0 amount = 0.0
unit = "" unit = ""
ingredient = "" ingredient = ""
@ -192,12 +192,20 @@ def parse(ing_str) -> BruteParsedIngredient:
# which means this is the ingredient # which means this is the ingredient
ingredient = tokens[1] ingredient = tokens[1]
except ValueError: except ValueError:
try: # can't parse first argument as amount
# can't parse first argument as amount # try to parse as unit and ingredient (e.g. "a tblsp salt"), with unit in first three tokens
# -> no unit -> parse everything as ingredient # won't work for units that have spaces
ingredient, note = parse_ingredient(tokens) for index, token in enumerate(tokens[:3]):
except ValueError: if parser.find_unit_match(token):
ingredient = " ".join(tokens[1:]) unit = token
ingredient, note = parse_ingredient(tokens[index + 1 :])
break
if not unit:
try:
# no unit -> parse everything as ingredient
ingredient, note = parse_ingredient(tokens)
except ValueError:
ingredient = " ".join(tokens[1:])
if unit_note not in note: if unit_note not in note:
note += " " + unit_note note += " " + unit_note

View File

@ -126,22 +126,24 @@ class ABCIngredientParser(ABC):
return store_map[fuzz_result[0]] return store_map[fuzz_result[0]]
def find_food_match(self, food: IngredientFood | CreateIngredientFood) -> IngredientFood | None: def find_food_match(self, food: IngredientFood | CreateIngredientFood | str) -> IngredientFood | None:
if isinstance(food, IngredientFood): if isinstance(food, IngredientFood):
return food return food
match_value = IngredientFoodModel.normalize(food.name) food_name = food if isinstance(food, str) else food.name
match_value = IngredientFoodModel.normalize(food_name)
return self.find_match( return self.find_match(
match_value, match_value,
store_map=self.foods_by_alias, store_map=self.foods_by_alias,
fuzzy_match_threshold=self.food_fuzzy_match_threshold, fuzzy_match_threshold=self.food_fuzzy_match_threshold,
) )
def find_unit_match(self, unit: IngredientUnit | CreateIngredientUnit) -> IngredientUnit | None: def find_unit_match(self, unit: IngredientUnit | CreateIngredientUnit | str) -> IngredientUnit | None:
if isinstance(unit, IngredientUnit): if isinstance(unit, IngredientUnit):
return unit return unit
match_value = IngredientUnitModel.normalize(unit.name) unit_name = unit if isinstance(unit, str) else unit.name
match_value = IngredientUnitModel.normalize(unit_name)
return self.find_match( return self.find_match(
match_value, match_value,
store_map=self.units_by_alias, store_map=self.units_by_alias,
@ -155,6 +157,16 @@ class ABCIngredientParser(ABC):
if ingredient.ingredient.unit and (unit_match := self.find_unit_match(ingredient.ingredient.unit)): if ingredient.ingredient.unit and (unit_match := self.find_unit_match(ingredient.ingredient.unit)):
ingredient.ingredient.unit = unit_match ingredient.ingredient.unit = unit_match
# Parser might have wrongly split a food into a unit and food.
if isinstance(ingredient.ingredient.food, CreateIngredientFood) and isinstance(
ingredient.ingredient.unit, CreateIngredientUnit
):
if food_match := self.find_food_match(
f"{ingredient.ingredient.unit.name} {ingredient.ingredient.food.name}"
):
ingredient.ingredient.food = food_match
ingredient.ingredient.unit = None
return ingredient return ingredient
@ -164,7 +176,7 @@ class BruteForceParser(ABCIngredientParser):
""" """
def parse_one(self, ingredient: str) -> ParsedIngredient: def parse_one(self, ingredient: str) -> ParsedIngredient:
bfi = brute.parse(ingredient) bfi = brute.parse(ingredient, self)
parsed_ingredient = ParsedIngredient( parsed_ingredient = ParsedIngredient(
input=ingredient, input=ingredient,

44
poetry.lock generated
View File

@ -1220,13 +1220,13 @@ min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-imp
[[package]] [[package]]
name = "mkdocs-material" name = "mkdocs-material"
version = "9.5.7" version = "9.5.8"
description = "Documentation that simply works" description = "Documentation that simply works"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "mkdocs_material-9.5.7-py3-none-any.whl", hash = "sha256:0be8ce8bcfebb52bae9b00cf9b851df45b8a92d629afcfd7f2c09b2dfa155ea3"}, {file = "mkdocs_material-9.5.8-py3-none-any.whl", hash = "sha256:14563314bbf97da4bfafc69053772341babfaeb3329cde01d3e63cec03997af8"},
{file = "mkdocs_material-9.5.7.tar.gz", hash = "sha256:16110292575d88a338d2961f3cb665cf12943ff8829e551a9b364f24019e46af"}, {file = "mkdocs_material-9.5.8.tar.gz", hash = "sha256:2a429213e83f84eda7a588e2b186316d806aac602b7f93990042f7a1f3d3cf65"},
] ]
[package.dependencies] [package.dependencies]
@ -1243,7 +1243,7 @@ regex = ">=2022.4"
requests = ">=2.26,<3.0" requests = ">=2.26,<3.0"
[package.extras] [package.extras]
git = ["mkdocs-git-committers-plugin-2 (>=1.1,<2.0)", "mkdocs-git-revision-date-localized-plugin (>=1.2,<2.0)"] git = ["mkdocs-git-committers-plugin-2 (>=1.1,<2.0)", "mkdocs-git-revision-date-localized-plugin (>=1.2.4,<2.0)"]
imaging = ["cairosvg (>=2.6,<3.0)", "pillow (>=10.2,<11.0)"] imaging = ["cairosvg (>=2.6,<3.0)", "pillow (>=10.2,<11.0)"]
recommended = ["mkdocs-minify-plugin (>=0.7,<1.0)", "mkdocs-redirects (>=1.2,<2.0)", "mkdocs-rss-plugin (>=1.6,<2.0)"] recommended = ["mkdocs-minify-plugin (>=0.7,<1.0)", "mkdocs-redirects (>=1.2,<2.0)", "mkdocs-rss-plugin (>=1.6,<2.0)"]
@ -2381,28 +2381,28 @@ pyasn1 = ">=0.1.3"
[[package]] [[package]]
name = "ruff" name = "ruff"
version = "0.2.0" version = "0.2.1"
description = "An extremely fast Python linter and code formatter, written in Rust." description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "ruff-0.2.0-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:638ea3294f800d18bae84a492cb5a245c8d29c90d19a91d8e338937a4c27fca0"}, {file = "ruff-0.2.1-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:dd81b911d28925e7e8b323e8d06951554655021df8dd4ac3045d7212ac4ba080"},
{file = "ruff-0.2.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3ff35433fcf4dff6d610738712152df6b7d92351a1bde8e00bd405b08b3d5759"}, {file = "ruff-0.2.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:dc586724a95b7d980aa17f671e173df00f0a2eef23f8babbeee663229a938fec"},
{file = "ruff-0.2.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf9faafbdcf4f53917019f2c230766da437d4fd5caecd12ddb68bb6a17d74399"}, {file = "ruff-0.2.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c92db7101ef5bfc18e96777ed7bc7c822d545fa5977e90a585accac43d22f18a"},
{file = "ruff-0.2.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8153a3e4128ed770871c47545f1ae7b055023e0c222ff72a759f5a341ee06483"}, {file = "ruff-0.2.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:13471684694d41ae0f1e8e3a7497e14cd57ccb7dd72ae08d56a159d6c9c3e30e"},
{file = "ruff-0.2.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8a75a98ae989a27090e9c51f763990ad5bbc92d20626d54e9701c7fe597f399"}, {file = "ruff-0.2.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a11567e20ea39d1f51aebd778685582d4c56ccb082c1161ffc10f79bebe6df35"},
{file = "ruff-0.2.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:87057dd2fdde297130ff99553be8549ca38a2965871462a97394c22ed2dfc19d"}, {file = "ruff-0.2.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:00a818e2db63659570403e44383ab03c529c2b9678ba4ba6c105af7854008105"},
{file = "ruff-0.2.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6d232f99d3ab00094ebaf88e0fb7a8ccacaa54cc7fa3b8993d9627a11e6aed7a"}, {file = "ruff-0.2.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be60592f9d218b52f03384d1325efa9d3b41e4c4d55ea022cd548547cc42cd2b"},
{file = "ruff-0.2.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d3c641f95f435fc6754b05591774a17df41648f0daf3de0d75ad3d9f099ab92"}, {file = "ruff-0.2.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbd2288890b88e8aab4499e55148805b58ec711053588cc2f0196a44f6e3d855"},
{file = "ruff-0.2.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3826fb34c144ef1e171b323ed6ae9146ab76d109960addca730756dc19dc7b22"}, {file = "ruff-0.2.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3ef052283da7dec1987bba8d8733051c2325654641dfe5877a4022108098683"},
{file = "ruff-0.2.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:eceab7d85d09321b4de18b62d38710cf296cb49e98979960a59c6b9307c18cfe"}, {file = "ruff-0.2.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7022d66366d6fded4ba3889f73cd791c2d5621b2ccf34befc752cb0df70f5fad"},
{file = "ruff-0.2.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:30ad74687e1f4a9ff8e513b20b82ccadb6bd796fe5697f1e417189c5cde6be3e"}, {file = "ruff-0.2.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0a725823cb2a3f08ee743a534cb6935727d9e47409e4ad72c10a3faf042ad5ba"},
{file = "ruff-0.2.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a7e3818698f8460bd0f8d4322bbe99db8327e9bc2c93c789d3159f5b335f47da"}, {file = "ruff-0.2.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0034d5b6323e6e8fe91b2a1e55b02d92d0b582d2953a2b37a67a2d7dedbb7acc"},
{file = "ruff-0.2.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:edf23041242c48b0d8295214783ef543847ef29e8226d9f69bf96592dba82a83"}, {file = "ruff-0.2.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e5cb5526d69bb9143c2e4d2a115d08ffca3d8e0fddc84925a7b54931c96f5c02"},
{file = "ruff-0.2.0-py3-none-win32.whl", hash = "sha256:e155147199c2714ff52385b760fe242bb99ea64b240a9ffbd6a5918eb1268843"}, {file = "ruff-0.2.1-py3-none-win32.whl", hash = "sha256:6b95ac9ce49b4fb390634d46d6ece32ace3acdd52814671ccaf20b7f60adb232"},
{file = "ruff-0.2.0-py3-none-win_amd64.whl", hash = "sha256:ba918e01cdd21e81b07555564f40d307b0caafa9a7a65742e98ff244f5035c59"}, {file = "ruff-0.2.1-py3-none-win_amd64.whl", hash = "sha256:e3affdcbc2afb6f5bd0eb3130139ceedc5e3f28d206fe49f63073cb9e65988e0"},
{file = "ruff-0.2.0-py3-none-win_arm64.whl", hash = "sha256:3fbaff1ba9564a2c5943f8f38bc221f04bac687cc7485e45237579fee7ccda79"}, {file = "ruff-0.2.1-py3-none-win_arm64.whl", hash = "sha256:efababa8e12330aa94a53e90a81eb6e2d55f348bc2e71adbf17d9cad23c03ee6"},
{file = "ruff-0.2.0.tar.gz", hash = "sha256:63856b91837606c673537d2889989733d7dffde553828d3b0f0bacfa6def54be"}, {file = "ruff-0.2.1.tar.gz", hash = "sha256:3b42b5d8677cd0c72b99fcaf068ffc62abb5a19e71b4a3b9cfa50658a0af02f1"},
] ]
[[package]] [[package]]

View File

@ -1,11 +1,36 @@
import pytest
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from mealie.core.config import get_app_settings from mealie.core.config import get_app_settings
from mealie.core.settings.static import APP_VERSION from mealie.core.settings.static import APP_VERSION
from mealie.repos.repository_factory import AllRepositories
from tests.utils import api_routes from tests.utils import api_routes
from tests.utils.fixture_schemas import TestUser from tests.utils.fixture_schemas import TestUser
@pytest.mark.parametrize("is_private_group", [True, False], ids=["private group", "public group"])
def test_public_about_get_app_info(api_client: TestClient, is_private_group: bool, database: AllRepositories):
settings = get_app_settings()
group = database.groups.get_by_name(settings.DEFAULT_GROUP)
assert group and group.preferences
group.preferences.private_group = is_private_group
database.group_preferences.update(group.id, group.preferences)
response = api_client.get(api_routes.app_about)
as_dict = response.json()
assert as_dict["production"] == settings.PRODUCTION
assert as_dict["version"] == APP_VERSION
assert as_dict["demoStatus"] == settings.IS_DEMO
assert as_dict["allowSignup"] == settings.ALLOW_SIGNUP
if is_private_group:
assert as_dict["defaultGroupSlug"] == None
else:
assert as_dict["defaultGroupSlug"] == group.slug
def test_admin_about_get_app_info(api_client: TestClient, admin_user: TestUser): def test_admin_about_get_app_info(api_client: TestClient, admin_user: TestUser):
response = api_client.get(api_routes.admin_about, headers=admin_user.token) response = api_client.get(api_routes.admin_about, headers=admin_user.token)

View File

@ -135,7 +135,7 @@ test_ingredients = [
@pytest.mark.skipif(not crf_exists(), reason="CRF++ not installed") @pytest.mark.skipif(not crf_exists(), reason="CRF++ not installed")
def test_nlp_parser(): def test_nlp_parser() -> None:
models: list[CRFIngredient] = convert_list_to_crf_model([x.input for x in test_ingredients]) models: list[CRFIngredient] = convert_list_to_crf_model([x.input for x in test_ingredients])
# Iterate over models and test_ingredients to gather # Iterate over models and test_ingredients to gather
@ -147,37 +147,102 @@ def test_nlp_parser():
assert model.unit == test_ingredient.unit assert model.unit == test_ingredient.unit
def test_brute_parser(unique_user: TestUser): @pytest.mark.parametrize(
# input: (quantity, unit, food, comments) "input, quantity, unit, food, comment",
expectations = { [
# Dutch pytest.param("1 theelepel koffie", 1, "theelepel", "koffie", "", id="1 theelepel koffie"),
"1 theelepel koffie": (1, "theelepel", "koffie", ""), pytest.param("3 theelepels koffie", 3, "theelepels", "koffie", "", id="3 theelepels koffie"),
"3 theelepels koffie": (3, "theelepels", "koffie", ""), pytest.param("1 eetlepel tarwe", 1, "eetlepel", "tarwe", "", id="1 eetlepel tarwe"),
"1 eetlepel tarwe": (1, "eetlepel", "tarwe", ""), pytest.param("20 eetlepels bloem", 20, "eetlepels", "bloem", "", id="20 eetlepels bloem"),
"20 eetlepels bloem": (20, "eetlepels", "bloem", ""), pytest.param("1 mespunt kaneel", 1, "mespunt", "kaneel", "", id="1 mespunt kaneel"),
"1 mespunt kaneel": (1, "mespunt", "kaneel", ""), pytest.param("1 snuf(je) zout", 1, "snuf(je)", "zout", "", id="1 snuf(je) zout"),
"1 snuf(je) zout": (1, "snuf(je)", "zout", ""), pytest.param(
"2 tbsp minced cilantro, leaves and stems": (2, "tbsp", "minced cilantro", "leaves and stems"), "2 tbsp minced cilantro, leaves and stems",
"1 large yellow onion, coarsely chopped": (1, "large", "yellow onion", "coarsely chopped"),
"1 1/2 tsp garam masala": (1.5, "tsp", "garam masala", ""),
"2 cups mango chunks, (2 large mangoes) (fresh or frozen)": (
2, 2,
"cups", "tbsp",
"minced cilantro",
"leaves and stems",
id="2 tbsp minced cilantro, leaves and stems",
),
pytest.param(
"1 large yellow onion, coarsely chopped",
1,
"large",
"yellow onion",
"coarsely chopped",
id="1 large yellow onion, coarsely chopped",
),
pytest.param("1 1/2 tsp garam masala", 1.5, "tsp", "garam masala", "", id="1 1/2 tsp garam masala"),
pytest.param(
"2 cups mango chunks, (2 large mangoes) (fresh or frozen)",
2,
"Cups",
"mango chunks, (2 large mangoes)", "mango chunks, (2 large mangoes)",
"fresh or frozen", "fresh or frozen",
id="2 cups mango chunks, (2 large mangoes) (fresh or frozen)",
), ),
} pytest.param("stalk onion", 0, "Stalk", "onion", "", id="stalk onion"),
pytest.param("a stalk bell peppers", 0, "Stalk", "bell peppers", "", id="a stalk bell peppers"),
pytest.param("a tablespoon unknownFood", 0, "Tablespoon", "unknownFood", "", id="a tablespoon unknownFood"),
pytest.param(
"stalk bell peppers, cut in pieces",
0,
"Stalk",
"bell peppers",
"cut in pieces",
id="stalk bell peppers, cut in pieces",
),
pytest.param(
"a stalk bell peppers, cut in pieces",
0,
"Stalk",
"bell peppers",
"cut in pieces",
id="stalk bell peppers, cut in pieces",
),
pytest.param("red pepper flakes", 0, "", "red pepper flakes", "", id="red pepper flakes"),
pytest.param("1 red pepper flakes", 1, "", "red pepper flakes", "", id="1 red pepper flakes"),
pytest.param("1 bell peppers", 1, "", "bell peppers", "", id="1 bell peppers"),
pytest.param("1 stalk bell peppers", 1, "Stalk", "bell peppers", "", id="1 big stalk bell peppers"),
pytest.param("a big stalk bell peppers", 0, "Stalk", "bell peppers", "", id="a big stalk bell peppers"),
pytest.param(
"1 bell peppers, cut in pieces", 1, "", "bell peppers", "cut in pieces", id="1 bell peppers, cut in pieces"
),
pytest.param(
"bell peppers, cut in pieces", 0, "", "bell peppers", "cut in pieces", id="bell peppers, cut in pieces"
),
],
)
def test_brute_parser(
unique_local_group_id: UUID4,
parsed_ingredient_data: tuple[list[IngredientFood], list[IngredientUnit]], # required so database is populated
input: str,
quantity: int | float,
unit: str,
food: str,
comment: str,
):
with session_context() as session: with session_context() as session:
parser = get_parser(RegisteredParser.brute, unique_user.group_id, session) parser = get_parser(RegisteredParser.brute, unique_local_group_id, session)
parsed = parser.parse_one(input)
ing = parsed.ingredient
for key, val in expectations.items(): if ing.quantity:
parsed = parser.parse_one(key) assert ing.quantity == quantity
else:
assert parsed.ingredient.quantity == val[0] assert not quantity
assert parsed.ingredient.unit.name == val[1] if ing.unit:
assert parsed.ingredient.food.name == val[2] assert ing.unit.name == unit
assert parsed.ingredient.note in {val[3], None} else:
assert not unit
if ing.food:
assert ing.food.name == food
else:
assert not food
if ing.note:
assert ing.note == comment
else:
assert not comment
@pytest.mark.parametrize( @pytest.mark.parametrize(