diff --git a/cookbook/apps.py b/cookbook/apps.py index c785e5536..e551319db 100644 --- a/cookbook/apps.py +++ b/cookbook/apps.py @@ -35,4 +35,3 @@ class CookbookConfig(AppConfig): # if DEBUG: # traceback.print_exc() # pass # dont break startup just because fix could not run, need to investigate cases when this happens - diff --git a/cookbook/helper/scope_middleware.py b/cookbook/helper/scope_middleware.py index 7f0676c7e..08b7b3d33 100644 --- a/cookbook/helper/scope_middleware.py +++ b/cookbook/helper/scope_middleware.py @@ -5,6 +5,7 @@ from rest_framework.authtoken.models import Token from rest_framework.exceptions import AuthenticationFailed from cookbook.views import views +from recipes import settings class ScopeMiddleware: @@ -14,14 +15,15 @@ class ScopeMiddleware: def __call__(self, request): if request.user.is_authenticated: - if request.path.startswith('/admin/'): + prefix = settings.JS_REVERSE_SCRIPT_PREFIX or '' + if request.path.startswith(prefix + '/admin/'): with scopes_disabled(): return self.get_response(request) - if request.path.startswith('/signup/') or request.path.startswith('/invite/'): + if request.path.startswith(prefix + '/signup/') or request.path.startswith(prefix + '/invite/'): return self.get_response(request) - if request.path.startswith('/accounts/'): + if request.path.startswith(prefix + '/accounts/'): return self.get_response(request) with scopes_disabled(): diff --git a/cookbook/helper/shopping_helper.py b/cookbook/helper/shopping_helper.py index 75a70d36b..4f725ffa0 100644 --- a/cookbook/helper/shopping_helper.py +++ b/cookbook/helper/shopping_helper.py @@ -17,8 +17,7 @@ def shopping_helper(qs, request): supermarket = request.query_params.get('supermarket', None) checked = request.query_params.get('checked', 'recent') user = request.user - - supermarket_order = ['food__supermarket_category__name', 'food__name'] + supermarket_order = [F('food__supermarket_category__name').asc(nulls_first=True), 'food__name'] # TODO created either scheduled task or startup task to delete very old shopping list entries # TODO create user preference to define 'very old' diff --git a/cookbook/tests/api/test_api_related_recipe.py b/cookbook/tests/api/test_api_related_recipe.py index ce6221880..1a381ed5c 100644 --- a/cookbook/tests/api/test_api_related_recipe.py +++ b/cookbook/tests/api/test_api_related_recipe.py @@ -65,7 +65,6 @@ def test_related_mixed_space(request, recipe, u1_s2): reverse(RELATED_URL, args={recipe.id})).content)) == 0 -# TODO add tests for mealplan related when thats added # TODO if/when related recipes includes multiple levels (related recipes of related recipes) add the following tests # -- step recipes included in step recipes # -- step recipes included in food recipes diff --git a/cookbook/tests/api/test_api_shopping_recipe.py b/cookbook/tests/api/test_api_shopping_recipe.py index 438de046e..d564bf2ab 100644 --- a/cookbook/tests/api/test_api_shopping_recipe.py +++ b/cookbook/tests/api/test_api_shopping_recipe.py @@ -233,7 +233,6 @@ def test_shopping_recipe_mixed_authors(u1_s1, u2_s1): assert len(json.loads(u2_s1.get(reverse(SHOPPING_LIST_URL)).content)) == 0 -# TODO test adding recipe with ingredients that are not food @pytest.mark.parametrize("recipe", [{'steps__ingredients__header': 1}], indirect=['recipe']) def test_shopping_with_header_ingredient(u1_s1, recipe): # with scope(space=recipe.space): diff --git a/cookbook/views/api.py b/cookbook/views/api.py index 8b3ff8fb9..b5006752d 100644 --- a/cookbook/views/api.py +++ b/cookbook/views/api.py @@ -689,8 +689,11 @@ class RecipeViewSet(viewsets.ModelViewSet): obj = self.get_object() if obj.get_space() != request.space: raise PermissionDenied(detail='You do not have the required permission to perform this action', code=403) - qs = obj.get_related_recipes(levels=1) # TODO: make levels a user setting, included in request data?, keep solely in the backend? - # mealplans= TODO get todays mealplans + try: + levels = int(request.query_params.get('levels', 1)) + except (ValueError, TypeError): + levels = 1 + qs = obj.get_related_recipes(levels=levels) # TODO: make levels a user setting, included in request data?, keep solely in the backend? return Response(self.serializer_class(qs, many=True).data) diff --git a/vue/src/apps/ModelListView/ModelListView.vue b/vue/src/apps/ModelListView/ModelListView.vue index d889267c7..1d90667de 100644 --- a/vue/src/apps/ModelListView/ModelListView.vue +++ b/vue/src/apps/ModelListView/ModelListView.vue @@ -7,7 +7,7 @@
-
+
diff --git a/vue/src/apps/ShoppingListView/ShoppingListView.vue b/vue/src/apps/ShoppingListView/ShoppingListView.vue index 37aa5e2df..465e52f91 100644 --- a/vue/src/apps/ShoppingListView/ShoppingListView.vue +++ b/vue/src/apps/ShoppingListView/ShoppingListView.vue @@ -58,16 +58,28 @@
- - - {{ i }} - +
@@ -594,6 +606,7 @@ export default { // if a supermarket is selected and filtered to only supermarket categories filter out everything else if (this.selected_supermarket && this.supermarket_categories_only) { let shopping_categories = this.supermarkets // category IDs configured on supermarket + .filter((x) => x.id === this.selected_supermarket) .map((x) => x.category_to_supermarket) .flat() .map((x) => x.category.id) @@ -603,7 +616,7 @@ export default { shopping_list = shopping_list.filter((x) => x?.food?.supermarket_category) } - let groups = { false: {}, true: {} } // force unchecked to always be first + var groups = { false: {}, true: {} } // force unchecked to always be first if (this.selected_supermarket) { let super_cats = this.supermarkets .filter((x) => x.id === this.selected_supermarket) @@ -611,10 +624,13 @@ export default { .flat() .map((x) => x.category.name) new Set([...super_cats, ...this.shopping_categories.map((x) => x.name)]).forEach((cat) => { - groups["false"][cat.name] = {} - groups["true"][cat.name] = {} + groups["false"][cat] = {} + groups["true"][cat] = {} }) } else { + // TODO: make nulls_first a user setting + groups.false[this.$t("Undefined")] = {} + groups.true[this.$t("Undefined")] = {} this.shopping_categories.forEach((cat) => { groups.false[cat.name] = {} groups.true[cat.name] = {} @@ -917,8 +933,17 @@ export default { // TODO make decision - should inheritance always be set manually or give user a choice at front-end or make it a setting? let food = this.items.filter((x) => x.food.id == item?.[0]?.food.id ?? item.food.id)[0].food - food.supermarket_category = this.shopping_categories.filter((x) => x?.id === this.shopcat)?.[0] - this.updateFood(food, "supermarket_category") + let supermarket_category = this.shopping_categories.filter((x) => x?.id === this.shopcat)?.[0] + food.supermarket_category = supermarket_category + this.updateFood(food, "supermarket_category").then((result) => { + this.items = this.items.map((x) => { + if (x.food.id === food.id) { + return { ...x, food: { ...x.food, supermarket_category: supermarket_category } } + } else { + return x + } + }) + }) this.shopcat = null }, onHand: function (item) { @@ -940,8 +965,13 @@ export default { }) }) }, - openContextMenu(e, value) { - this.shopcat = value?.food?.supermarket_category?.id ?? value?.[0]?.food?.supermarket_category?.id ?? undefined + openContextMenu(e, value, section = false) { + if (section) { + value = Object.values(value).flat() + } else { + this.shopcat = value?.food?.supermarket_category?.id ?? value?.[0]?.food?.supermarket_category?.id ?? undefined + } + this.$refs.menu.open(e, value) }, saveSettings: function () { @@ -1010,24 +1040,15 @@ export default { }, updateFood: function (food, field) { let api = new ApiApiFactory() - let ignore_category if (field) { - ignore_category = food.inherit_fields - .map((x) => food.inherit_fields.fields) - .flat() - .includes(field) - } else { - ignore_category = true + // assume if field is changing it should no longer be inheritted + food.inherit_fields = food.inherit_fields.filter((x) => x.field !== field) } return api .partialUpdateFood(food.id, food) .then((result) => { - if (food.supermarket_category && !ignore_category && food.parent) { - makeToast(this.$t("Warning"), this.$t("InheritWarning", { food: food.name }), "warning") - } else { - StandardToasts.makeStandardToast(StandardToasts.SUCCESS_UPDATE) - } + StandardToasts.makeStandardToast(StandardToasts.SUCCESS_UPDATE) if (food?.numchild > 0) { this.getShoppingList() // if food has children, just get the whole list. probably could be more efficient } diff --git a/vue/src/components/GenericHorizontalCard.vue b/vue/src/components/GenericHorizontalCard.vue index 0635702e2..39a31d907 100644 --- a/vue/src/components/GenericHorizontalCard.vue +++ b/vue/src/components/GenericHorizontalCard.vue @@ -26,7 +26,7 @@
{{ item[subtitle] }}
{{ getFullname }}
- + diff --git a/vue/src/components/GenericPill.vue b/vue/src/components/GenericPill.vue index 183d83ec8..276042523 100644 --- a/vue/src/components/GenericPill.vue +++ b/vue/src/components/GenericPill.vue @@ -10,7 +10,7 @@ export default { name: "GenericPill", props: { - item_list: { type: Object }, + item_list: { type: Array }, label: { type: String, default: "name" }, color: { type: String, default: "light" }, }, diff --git a/vue/src/components/IngredientComponent.vue b/vue/src/components/IngredientComponent.vue index 184765c97..595db720b 100644 --- a/vue/src/components/IngredientComponent.vue +++ b/vue/src/components/IngredientComponent.vue @@ -108,7 +108,7 @@ export default { this.shop = false // don't check any boxes until user selects a shopping list to edit if (count_shopping_ingredient >= 1) { this.shopping_status = true // ingredient is in the shopping list - probably (but not definitely, this ingredient) - } else if (this.ingredient.food.shopping) { + } else if (this.ingredient?.food?.shopping) { this.shopping_status = null // food is in the shopping list, just not for this ingredient/recipe } else { // food is not in any shopping list @@ -123,7 +123,7 @@ export default { if (count_shopping_ingredient >= 1) { // ingredient is in this shopping list (not entirely sure how this could happen?) this.shopping_status = true - } else if (count_shopping_ingredient == 0 && this.ingredient.food.shopping) { + } else if (count_shopping_ingredient == 0 && this.ingredient?.food?.shopping) { // food is in the shopping list, just not for this ingredient/recipe this.shopping_status = null } else {