diff --git a/cookbook/helper/shopping_helper.py b/cookbook/helper/shopping_helper.py index 98bfe03f..7e1fa6c7 100644 --- a/cookbook/helper/shopping_helper.py +++ b/cookbook/helper/shopping_helper.py @@ -79,7 +79,7 @@ def list_from_recipe(list_recipe=None, recipe=None, mealplan=None, servings=None elif ingredients: ingredients = Ingredient.objects.filter(pk__in=ingredients, space=space) else: - ingredients = Ingredient.objects.filter(step__recipe=r, space=space) + ingredients = Ingredient.objects.filter(step__recipe=r, food__ignore_shopping=False, space=space) if exclude_onhand := created_by.userpreference.mealplan_autoexclude_onhand: ingredients = ingredients.exclude(food__onhand_users__id__in=[x.id for x in shared_users]) @@ -101,9 +101,9 @@ def list_from_recipe(list_recipe=None, recipe=None, mealplan=None, servings=None if ingredients.filter(food__recipe=x).exists(): for ing in ingredients.filter(food__recipe=x): if exclude_onhand: - x_ing = Ingredient.objects.filter(step__recipe=x, space=space).exclude(food__onhand_users__id__in=[x.id for x in shared_users]) + x_ing = Ingredient.objects.filter(step__recipe=x, food__ignore_shopping=False, space=space).exclude(food__onhand_users__id__in=[x.id for x in shared_users]) else: - x_ing = Ingredient.objects.filter(step__recipe=x, space=space) + x_ing = Ingredient.objects.filter(step__recipe=x, food__ignore_shopping=False, space=space).exclude(food__ignore_shopping=True) for i in [x for x in x_ing]: ShoppingListEntry.objects.create( list_recipe=list_recipe, diff --git a/cookbook/serializer.py b/cookbook/serializer.py index 2a675a3b..6fa63f94 100644 --- a/cookbook/serializer.py +++ b/cookbook/serializer.py @@ -166,6 +166,11 @@ class UserPreferenceSerializer(WritableNestedModelSerializer): food_inherit_default = FoodInheritFieldSerializer(source='space.food_inherit', many=True, allow_null=True, required=False, read_only=True) plan_share = UserNameSerializer(many=True, allow_null=True, required=False, read_only=True) shopping_share = UserNameSerializer(many=True, allow_null=True, required=False) + food_children_exist = serializers.SerializerMethodField('get_food_children_exist') + + def get_food_children_exist(self, obj): + space = getattr(self.context.get('request', None), 'space', None) + return Food.objects.filter(depth__gt=0, space=space).exists() def create(self, validated_data): if not validated_data.get('user', None): @@ -180,7 +185,7 @@ class UserPreferenceSerializer(WritableNestedModelSerializer): 'user', 'theme', 'nav_color', 'default_unit', 'default_page', 'use_fractions', 'use_kj', 'search_style', 'show_recent', 'plan_share', 'ingredient_decimals', 'comments', 'shopping_auto_sync', 'mealplan_autoadd_shopping', 'food_inherit_default', 'default_delay', 'mealplan_autoinclude_related', 'mealplan_autoexclude_onhand', 'shopping_share', 'shopping_recent_days', 'csv_delim', 'csv_prefix', - 'filter_to_supermarket', 'shopping_add_onhand', 'left_handed' + 'filter_to_supermarket', 'shopping_add_onhand', 'left_handed', 'food_children_exist' ) @@ -426,7 +431,7 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedR model = Food fields = ( 'id', 'name', 'description', 'shopping', 'recipe', 'food_onhand', 'supermarket_category', - 'image', 'parent', 'numchild', 'numrecipe', 'inherit_fields', 'full_name' + 'image', 'parent', 'numchild', 'numrecipe', 'inherit_fields', 'full_name', 'ignore_shopping' ) read_only_fields = ('id', 'numchild', 'parent', 'image', 'numrecipe') diff --git a/vue/src/apps/RecipeSearchView/RecipeSearchView.vue b/vue/src/apps/RecipeSearchView/RecipeSearchView.vue index 7a66b7c4..48bfb259 100644 --- a/vue/src/apps/RecipeSearchView/RecipeSearchView.vue +++ b/vue/src/apps/RecipeSearchView/RecipeSearchView.vue @@ -1,6 +1,6 @@ @@ -47,6 +48,7 @@ export default { class_list: { type: String, default: "mb-3" }, show_label: { type: Boolean, default: true }, clear: { type: Number }, + help: { type: String, default: undefined }, }, data() { return { diff --git a/vue/src/components/Modals/TextInput.vue b/vue/src/components/Modals/TextInput.vue index 94d5225b..bd25ebc2 100644 --- a/vue/src/components/Modals/TextInput.vue +++ b/vue/src/components/Modals/TextInput.vue @@ -2,6 +2,8 @@
+ {{ help }} + {{ subtitle }}
@@ -14,7 +16,8 @@ export default { label: { type: String, default: "Text Field" }, value: { type: String, default: "" }, placeholder: { type: String, default: "You Should Add Placeholder Text" }, - show_merge: { type: Boolean, default: false }, + help: { type: String, default: undefined }, + subtitle: { type: String, default: undefined }, }, data() { return { diff --git a/vue/src/locales/en.json b/vue/src/locales/en.json index f289a5b3..a5dbd34d 100644 --- a/vue/src/locales/en.json +++ b/vue/src/locales/en.json @@ -289,5 +289,9 @@ "remember_hours": "Hours to Remember", "tree_select": "Use Tree Selection", "left_handed": "Left-handed mode", - "left_handed_help": "Will optimize the UI for use with your left hand." + "left_handed_help": "Will optimize the UI for use with your left hand.", + "OnHand_help": "Food is in inventory and will not be automatically added to a shopping list.", + "ignore_shopping_help": "Never add food to the shopping list (e.g. water)", + "shopping_category_help": "Supermarkets can be ordered and filtered by Shopping Category according to the layout of the aisles.", + "food_recipe_help": "Linking a recipe here will include the linked recipe in any other recipe that use this food" } diff --git a/vue/src/utils/models.js b/vue/src/utils/models.js index 209f10ea..1b0e0cf0 100644 --- a/vue/src/utils/models.js +++ b/vue/src/utils/models.js @@ -76,15 +76,17 @@ export class Models { // REQUIRED: unordered array of fields that can be set during create create: { // if not defined partialUpdate will use the same parameters, prepending 'id' - params: [["name", "description", "recipe", "food_onhand", "supermarket_category", "inherit", "inherit_fields"]], + params: [["name", "description", "recipe", "food_onhand", "supermarket_category", "inherit", "inherit_fields", "ignore_shopping"]], form: { + show_help: true, name: { form_field: true, type: "text", field: "name", label: i18n.t("Name"), placeholder: "", + subtitle_field: "full_name", }, description: { form_field: true, @@ -99,12 +101,21 @@ export class Models { field: "recipe", list: "RECIPE", label: i18n.t("Recipe"), + help_text: i18n.t("food_recipe_help"), }, - shopping: { + onhand: { form_field: true, type: "checkbox", field: "food_onhand", label: i18n.t("OnHand"), + help_text: i18n.t("OnHand_help"), + }, + ignore_shopping: { + form_field: true, + type: "checkbox", + field: "ignore_shopping", + label: i18n.t("Ignore_Shopping"), + help_text: i18n.t("ignore_shopping_help"), }, shopping_category: { form_field: true, @@ -113,6 +124,7 @@ export class Models { list: "SHOPPING_CATEGORY", label: i18n.t("Shopping_Category"), allow_create: true, + help_text: i18n.t("shopping_category_help"), }, inherit_fields: { form_field: true, @@ -121,12 +133,7 @@ export class Models { field: "inherit_fields", list: "FOOD_INHERIT_FIELDS", label: i18n.t("InheritFields"), - condition: { field: "parent", value: true, condition: "exists" }, - }, - full_name: { - form_field: true, - type: "smalltext", - field: "full_name", + condition: { field: "food_children_exist", value: true, condition: "preference_equals" }, }, form_function: "FoodCreateDefault", }, diff --git a/vue/src/utils/openapi/api.ts b/vue/src/utils/openapi/api.ts index 8a8c1cff..43b5182c 100644 --- a/vue/src/utils/openapi/api.ts +++ b/vue/src/utils/openapi/api.ts @@ -211,10 +211,10 @@ export interface Food { recipe?: FoodRecipe | null; /** * - * @type {boolean} + * @type {string} * @memberof Food */ - food_onhand?: boolean; + food_onhand?: string | null; /** * * @type {FoodSupermarketCategory} @@ -245,6 +245,12 @@ export interface Food { * @memberof Food */ full_name?: string; + /** + * + * @type {boolean} + * @memberof Food + */ + ignore_shopping?: boolean; } /** * @@ -607,10 +613,10 @@ export interface IngredientFood { recipe?: FoodRecipe | null; /** * - * @type {boolean} + * @type {string} * @memberof IngredientFood */ - food_onhand?: boolean; + food_onhand?: string | null; /** * * @type {FoodSupermarketCategory} @@ -641,6 +647,12 @@ export interface IngredientFood { * @memberof IngredientFood */ full_name?: string; + /** + * + * @type {boolean} + * @memberof IngredientFood + */ + ignore_shopping?: boolean; } /** * @@ -1182,7 +1194,7 @@ export interface MealPlanRecipe { * @type {any} * @memberof MealPlanRecipe */ - image?: any; + image?: any | null; /** * * @type {Array} @@ -1255,6 +1267,12 @@ export interface MealPlanRecipe { * @memberof MealPlanRecipe */ _new?: string; + /** + * + * @type {string} + * @memberof MealPlanRecipe + */ + recent?: string; } /** * @@ -1372,7 +1390,7 @@ export interface Recipe { * @type {any} * @memberof Recipe */ - image?: any; + image?: any | null; /** * * @type {Array} @@ -1715,25 +1733,25 @@ export interface RecipeNutrition { * @type {string} * @memberof RecipeNutrition */ - carbohydrates?: string; + carbohydrates: string; /** * * @type {string} * @memberof RecipeNutrition */ - fats?: string; + fats: string; /** * * @type {string} * @memberof RecipeNutrition */ - proteins?: string; + proteins: string; /** * * @type {string} * @memberof RecipeNutrition */ - calories?: string; + calories: string; /** * * @type {string} @@ -1770,7 +1788,7 @@ export interface RecipeOverview { * @type {any} * @memberof RecipeOverview */ - image?: any; + image?: any | null; /** * * @type {Array} @@ -1843,6 +1861,12 @@ export interface RecipeOverview { * @memberof RecipeOverview */ _new?: string; + /** + * + * @type {string} + * @memberof RecipeOverview + */ + recent?: string; } /** * @@ -1918,12 +1942,6 @@ export interface RecipeSteps { * @memberof RecipeSteps */ name?: string; - /** - * - * @type {string} - * @memberof RecipeSteps - */ - type?: RecipeStepsTypeEnum; /** * * @type {string} @@ -1991,18 +2009,6 @@ export interface RecipeSteps { */ numrecipe?: string; } - -/** - * @export - * @enum {string} - */ -export enum RecipeStepsTypeEnum { - Text = 'TEXT', - Time = 'TIME', - File = 'FILE', - Recipe = 'RECIPE' -} - /** * * @export @@ -2523,12 +2529,6 @@ export interface Step { * @memberof Step */ name?: string; - /** - * - * @type {string} - * @memberof Step - */ - type?: StepTypeEnum; /** * * @type {string} @@ -2596,18 +2596,6 @@ export interface Step { */ numrecipe?: string; } - -/** - * @export - * @enum {string} - */ -export enum StepTypeEnum { - Text = 'TEXT', - Time = 'TIME', - File = 'FILE', - Recipe = 'RECIPE' -} - /** * * @export @@ -2952,6 +2940,12 @@ export interface UserPreference { * @memberof UserPreference */ default_page?: UserPreferenceDefaultPageEnum; + /** + * + * @type {boolean} + * @memberof UserPreference + */ + use_fractions?: boolean; /** * * @type {boolean} @@ -3026,10 +3020,10 @@ export interface UserPreference { mealplan_autoexclude_onhand?: boolean; /** * - * @type {Array} + * @type {Array} * @memberof UserPreference */ - shopping_share?: Array; + shopping_share?: Array | null; /** * * @type {number} @@ -3054,6 +3048,18 @@ export interface UserPreference { * @memberof UserPreference */ filter_to_supermarket?: boolean; + /** + * + * @type {boolean} + * @memberof UserPreference + */ + shopping_add_onhand?: boolean; + /** + * + * @type {boolean} + * @memberof UserPreference + */ + left_handed?: boolean; } /** diff --git a/vue/src/utils/utils.js b/vue/src/utils/utils.js index 5d332ee5..f03024de 100644 --- a/vue/src/utils/utils.js +++ b/vue/src/utils/utils.js @@ -156,7 +156,7 @@ export function getUserPreference(pref = undefined) { return undefined } if (pref) { - return user_preference[pref] + return user_preference?.[pref] } return user_preference } @@ -389,6 +389,8 @@ export function getForm(model, action, item1, item2) { } if (value?.form_field) { value["value"] = item1?.[value?.field] ?? undefined + value["help"] = item1?.[value?.help_text_field] ?? value?.help_text ?? undefined + value["subtitle"] = item1?.[value?.subtitle_field] ?? value?.subtitle ?? undefined form.fields.push({ ...value, ...{