related recipes included when adding mealplan to shopping list

This commit is contained in:
smilerz
2021-10-28 20:40:56 -05:00
parent 27f358dd03
commit 8b682c33f3
13 changed files with 519 additions and 594 deletions

View File

@ -489,8 +489,8 @@ class ShoppingPreferenceForm(forms.ModelForm):
'of mobile data. If lower than instance limit it is reset when saving.' # noqa: E501 'of mobile data. If lower than instance limit it is reset when saving.' # noqa: E501
), ),
'mealplan_autoadd_shopping': _('Automatically add meal plan ingredients to shopping list.'), 'mealplan_autoadd_shopping': _('Automatically add meal plan ingredients to shopping list.'),
'mealplan_autoexclude_onhand': _('When automatically adding a meal plan to the shopping list, exclude ingredients that are on hand.'), 'mealplan_autoinclude_related': _('When adding a meal plan to the shopping list (manually or automatically), include all related recipes.'),
'mealplan_autoinclude_related': _('When automatically adding a meal plan to the shopping list, include all related recipes.'), 'mealplan_autoexclude_onhand': _('When adding a meal plan to the shopping list (manually or automatically), exclude ingredients that are on hand.'),
'default_delay': _('Default number of hours to delay a shopping list entry.'), 'default_delay': _('Default number of hours to delay a shopping list entry.'),
'filter_to_supermarket': _('Filter shopping list to only include supermarket categories.'), 'filter_to_supermarket': _('Filter shopping list to only include supermarket categories.'),
} }

View File

@ -1,11 +1,15 @@
from datetime import timedelta from datetime import timedelta
from decimal import Decimal
from django.contrib.postgres.aggregates import ArrayAgg from django.contrib.postgres.aggregates import ArrayAgg
from django.db.models import F, OuterRef, Q, Subquery, Value from django.db.models import F, OuterRef, Q, Subquery, Value
from django.db.models.functions import Coalesce from django.db.models.functions import Coalesce
from django.utils import timezone from django.utils import timezone
from cookbook.models import UserPreference from cookbook.helper.HelperFunctions import Round, str2bool
from cookbook.models import (Ingredient, ShoppingListEntry, ShoppingListRecipe,
SupermarketCategoryRelation)
from recipes import settings
def shopping_helper(qs, request): def shopping_helper(qs, request):
@ -35,3 +39,77 @@ def shopping_helper(qs, request):
supermarket_order = ['checked'] + supermarket_order supermarket_order = ['checked'] + supermarket_order
return qs.order_by(*supermarket_order).select_related('unit', 'food', 'ingredient', 'created_by', 'list_recipe', 'list_recipe__mealplan', 'list_recipe__recipe') return qs.order_by(*supermarket_order).select_related('unit', 'food', 'ingredient', 'created_by', 'list_recipe', 'list_recipe__mealplan', 'list_recipe__recipe')
def list_from_recipe(list_recipe=None, recipe=None, mealplan=None, servings=None, ingredients=None, created_by=None, space=None, append=False):
"""
Creates ShoppingListRecipe and associated ShoppingListEntrys from a recipe or a meal plan with a recipe
:param list_recipe: Modify an existing ShoppingListRecipe
:param recipe: Recipe to use as list of ingredients. One of [recipe, mealplan] are required
:param mealplan: alternatively use a mealplan recipe as source of ingredients
:param servings: Optional: Number of servings to use to scale shoppinglist. If servings = 0 an existing recipe list will be deleted
:param ingredients: Ingredients, list of ingredient IDs to include on the shopping list. When not provided all ingredients will be used
:param append: If False will remove any entries not included with ingredients, when True will append ingredients to the shopping list
"""
# TODO cascade to related recipes
r = recipe or getattr(mealplan, 'recipe', None) or getattr(list_recipe, 'recipe', None)
if not r:
raise ValueError(_("You must supply a recipe or mealplan"))
created_by = created_by or getattr(mealplan, 'created_by', None) or getattr(list_recipe, 'created_by', None)
if not created_by:
raise ValueError(_("You must supply a created_by"))
if type(servings) not in [int, float]:
servings = getattr(mealplan, 'servings', 1.0)
shared_users = list(created_by.get_shopping_share())
shared_users.append(created_by)
if list_recipe:
created = False
else:
list_recipe = ShoppingListRecipe.objects.create(recipe=r, mealplan=mealplan, servings=servings)
created = True
if servings == 0 and not created:
list_recipe.delete()
return []
elif ingredients:
ingredients = Ingredient.objects.filter(pk__in=ingredients, space=space)
else:
ingredients = Ingredient.objects.filter(step__recipe=r, space=space)
add_ingredients = ingredients.values_list('id', flat=True)
if not append:
existing_list = ShoppingListEntry.objects.filter(list_recipe=list_recipe)
# delete shopping list entries not included in ingredients
existing_list.exclude(ingredient__in=ingredients).delete()
# add shopping list entries that did not previously exist
add_ingredients = set(add_ingredients) - set(existing_list.values_list('ingredient__id', flat=True))
add_ingredients = Ingredient.objects.filter(id__in=add_ingredients, space=space)
# if servings have changed, update the ShoppingListRecipe and existing Entrys
if servings <= 0:
servings = 1
servings_factor = servings / r.servings
if not created and list_recipe.servings != servings:
update_ingredients = set(ingredients.values_list('id', flat=True)) - set(add_ingredients.values_list('id', flat=True))
for sle in ShoppingListEntry.objects.filter(list_recipe=list_recipe, ingredient__id__in=update_ingredients):
sle.amount = sle.ingredient.amount * Decimal(servings_factor)
sle.save()
# add any missing Entrys
for i in [x for x in add_ingredients if not x.food.ignore_shopping]:
ShoppingListEntry.objects.create(
list_recipe=list_recipe,
food=i.food,
unit=i.unit,
ingredient=i,
amount=i.amount * Decimal(servings_factor),
created_by=created_by,
space=space,
)
# return all shopping list items
return list_recipe

View File

@ -783,17 +783,6 @@ class MealPlan(ExportModelOperationsMixin('meal_plan'), models.Model, Permission
space = models.ForeignKey(Space, on_delete=models.CASCADE) space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space') objects = ScopedManager(space='space')
# TODO override create method to check if recipes are always added
# @classmethod
# def generate_shoppinglist(self, ingredients=None):
# recipe_list = ShoppingListRecipe.objects.create()
# if not ingredients:
# ingredients = Ingredient.objects.filter(step__recipe=self.recipe)
# for i in ingredients:
# ShoppingListEntry.objects.create(
# )
def get_label(self): def get_label(self):
if self.title: if self.title:
return self.title return self.title
@ -847,149 +836,6 @@ class ShoppingListEntry(ExportModelOperationsMixin('shopping_list_entry'), model
space = models.ForeignKey(Space, on_delete=models.CASCADE) space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space') objects = ScopedManager(space='space')
@classmethod
@atomic
def list_from_recipe(self, list_recipe=None, recipe=None, mealplan=None, servings=None, ingredients=None, created_by=None, space=None):
"""
Creates ShoppingListRecipe and associated ShoppingListEntrys from a recipe or a meal plan with a recipe
:param list_recipe: Modify an existing ShoppingListRecipe
:param recipe: Recipe to use as list of ingredients. One of [recipe, mealplan] are required
:param mealplan: alternatively use a mealplan recipe as source of ingredients
:param servings: Optional: Number of servings to use to scale shoppinglist. If servings = 0 an existing recipe list will be deleted
:param ingredients: Ingredients, list of ingredient IDs to include on the shopping list. When not provided all ingredients will be used
"""
# TODO cascade to related recipes
r = recipe or getattr(mealplan, 'recipe', None) or getattr(list_recipe, 'recipe', None)
if not r:
raise ValueError(_("You must supply a recipe or mealplan"))
created_by = created_by or getattr(mealplan, 'created_by', None) or getattr(list_recipe, 'created_by', None)
if not created_by:
raise ValueError(_("You must supply a created_by"))
if type(servings) not in [int, float]:
servings = getattr(mealplan, 'servings', 1.0)
shared_users = list(created_by.get_shopping_share())
shared_users.append(created_by)
if list_recipe:
created = False
else:
list_recipe = ShoppingListRecipe.objects.create(recipe=r, mealplan=mealplan, servings=servings)
created = True
if servings == 0 and not created:
list_recipe.delete()
return []
elif ingredients:
ingredients = Ingredient.objects.filter(pk__in=ingredients, space=space)
else:
ingredients = Ingredient.objects.filter(step__recipe=r, space=space)
existing_list = ShoppingListEntry.objects.filter(list_recipe=list_recipe)
# delete shopping list entries not included in ingredients
existing_list.exclude(ingredient__in=ingredients).delete()
# add shopping list entries that did not previously exist
add_ingredients = set(ingredients.values_list('id', flat=True)) - set(existing_list.values_list('ingredient__id', flat=True))
add_ingredients = Ingredient.objects.filter(id__in=add_ingredients, space=space)
# if servings have changed, update the ShoppingListRecipe and existing Entrys
if servings <= 0:
servings = 1
servings_factor = servings / r.servings
if not created and list_recipe.servings != servings:
update_ingredients = set(ingredients.values_list('id', flat=True)) - set(add_ingredients.values_list('id', flat=True))
for sle in ShoppingListEntry.objects.filter(list_recipe=list_recipe, ingredient__id__in=update_ingredients):
sle.amount = sle.ingredient.amount * Decimal(servings_factor)
sle.save()
# add any missing Entrys
shoppinglist = [
ShoppingListEntry(
list_recipe=list_recipe,
food=i.food,
unit=i.unit,
ingredient=i,
amount=i.amount * Decimal(servings),
created_by=created_by,
space=space
)
for i in ingredients
]
return ShoppingListEntry.objects.bulk_create(shoppinglist)
@classmethod
@atomic
def list_from_recipe(self, list_recipe=None, recipe=None, mealplan=None, servings=None, ingredients=None, created_by=None, space=None):
"""
Creates ShoppingListRecipe and associated ShoppingListEntrys from a recipe or a meal plan with a recipe
:param list_recipe: Modify an existing ShoppingListRecipe
:param recipe: Recipe to use as list of ingredients. One of [recipe, mealplan] are required
:param mealplan: alternatively use a mealplan recipe as source of ingredients
:param servings: Optional: Number of servings to use to scale shoppinglist. If servings = 0 an existing recipe list will be deleted
:param ingredients: Ingredients, list of ingredient IDs to include on the shopping list. When not provided all ingredients will be used
"""
# TODO cascade to related recipes
r = recipe or getattr(mealplan, 'recipe', None) or getattr(list_recipe, 'recipe', None)
if not r:
raise ValueError(_("You must supply a recipe or mealplan"))
created_by = created_by or getattr(mealplan, 'created_by', None) or getattr(list_recipe, 'created_by', None)
if not created_by:
raise ValueError(_("You must supply a created_by"))
if type(servings) not in [int, float]:
servings = getattr(mealplan, 'servings', 1.0)
shared_users = list(created_by.get_shopping_share())
shared_users.append(created_by)
if list_recipe:
created = False
else:
list_recipe = ShoppingListRecipe.objects.create(recipe=r, mealplan=mealplan, servings=servings)
created = True
if servings == 0 and not created:
list_recipe.delete()
return []
elif ingredients:
ingredients = Ingredient.objects.filter(pk__in=ingredients, space=space)
else:
ingredients = Ingredient.objects.filter(step__recipe=r, space=space)
existing_list = ShoppingListEntry.objects.filter(list_recipe=list_recipe)
# delete shopping list entries not included in ingredients
existing_list.exclude(ingredient__in=ingredients).delete()
# add shopping list entries that did not previously exist
add_ingredients = set(ingredients.values_list('id', flat=True)) - set(existing_list.values_list('ingredient__id', flat=True))
add_ingredients = Ingredient.objects.filter(id__in=add_ingredients, space=space)
# if servings have changed, update the ShoppingListRecipe and existing Entrys
if servings <= 0:
servings = 1
servings_factor = servings / r.servings
if not created and list_recipe.servings != servings:
update_ingredients = set(ingredients.values_list('id', flat=True)) - set(add_ingredients.values_list('id', flat=True))
for sle in ShoppingListEntry.objects.filter(list_recipe=list_recipe, ingredient__id__in=update_ingredients):
sle.amount = sle.ingredient.amount * Decimal(servings_factor)
sle.save()
# add any missing Entrys
shoppinglist = [
ShoppingListEntry(
list_recipe=list_recipe,
food=i.food,
unit=i.unit,
ingredient=i,
amount=i.amount * Decimal(servings_factor),
created_by=created_by,
space=space
)
for i in [x for x in add_ingredients if not x.food.ignore_shopping]
]
ShoppingListEntry.objects.bulk_create(shoppinglist)
# return all shopping list items
print('end of servings')
return ShoppingListEntry.objects.filter(list_recipe=list_recipe)
@ staticmethod @ staticmethod
def get_space_key(): def get_space_key():
return 'shoppinglist', 'space' return 'shoppinglist', 'space'

View File

@ -12,6 +12,7 @@ from drf_writable_nested import UniqueFieldsMixin, WritableNestedModelSerializer
from rest_framework import serializers from rest_framework import serializers
from rest_framework.exceptions import NotFound, ValidationError from rest_framework.exceptions import NotFound, ValidationError
from cookbook.helper.shopping_helper import list_from_recipe
from cookbook.models import (Automation, BookmarkletImport, Comment, CookLog, Food, from cookbook.models import (Automation, BookmarkletImport, Comment, CookLog, Food,
FoodInheritField, ImportLog, Ingredient, Keyword, MealPlan, MealType, FoodInheritField, ImportLog, Ingredient, Keyword, MealPlan, MealType,
NutritionInformation, Recipe, RecipeBook, RecipeBookEntry, NutritionInformation, Recipe, RecipeBook, RecipeBookEntry,
@ -616,7 +617,7 @@ class MealPlanSerializer(SpacedModelSerializer, WritableNestedModelSerializer):
validated_data['created_by'] = self.context['request'].user validated_data['created_by'] = self.context['request'].user
mealplan = super().create(validated_data) mealplan = super().create(validated_data)
if self.context['request'].data.get('addshopping', False): if self.context['request'].data.get('addshopping', False):
ShoppingListEntry.list_from_recipe(mealplan=mealplan, space=validated_data['space'], created_by=validated_data['created_by']) list_from_recipe(mealplan=mealplan, space=validated_data['space'], created_by=validated_data['created_by'])
return mealplan return mealplan
class Meta: class Meta:
@ -648,7 +649,7 @@ class ShoppingListRecipeSerializer(serializers.ModelSerializer):
def update(self, instance, validated_data): def update(self, instance, validated_data):
if 'servings' in validated_data: if 'servings' in validated_data:
ShoppingListEntry.list_from_recipe( list_from_recipe(
list_recipe=instance, list_recipe=instance,
servings=validated_data['servings'], servings=validated_data['servings'],
created_by=self.context['request'].user, created_by=self.context['request'].user,

View File

@ -1,3 +1,4 @@
from decimal import Decimal
from functools import wraps from functools import wraps
from django.contrib.postgres.search import SearchVector from django.contrib.postgres.search import SearchVector
@ -5,8 +6,10 @@ from django.db.models.signals import post_save
from django.dispatch import receiver from django.dispatch import receiver
from django.utils import translation from django.utils import translation
from cookbook.helper.shopping_helper import list_from_recipe
from cookbook.managers import DICTIONARY from cookbook.managers import DICTIONARY
from cookbook.models import Food, FoodInheritField, Recipe, Step from cookbook.models import (Food, FoodInheritField, Ingredient, MealPlan, Recipe,
ShoppingListEntry, Step)
# wraps a signal with the ability to set 'skip_signal' to avoid creating recursive signals # wraps a signal with the ability to set 'skip_signal' to avoid creating recursive signals
@ -78,3 +81,52 @@ def update_food_inheritance(sender, instance=None, created=False, **kwargs):
# don't cascade empty supermarket category # don't cascade empty supermarket category
if instance.supermarket_category: if instance.supermarket_category:
instance.get_children().filter(inherit=True).exclude(ignore_inherit__field='supermarket_category').update(supermarket_category=instance.supermarket_category) instance.get_children().filter(inherit=True).exclude(ignore_inherit__field='supermarket_category').update(supermarket_category=instance.supermarket_category)
@receiver(post_save, sender=MealPlan)
def auto_add_shopping(sender, instance=None, created=False, weak=False, **kwargs):
user = instance.get_owner()
if not created or not user.userpreference.mealplan_autoadd_shopping:
return
# if creating a mealplan - perform shopping list activities
space = instance.space
if user.userpreference.mealplan_autoadd_shopping:
kwargs = {
'mealplan': instance,
'space': space,
'created_by': user,
'servings': instance.servings
}
recipe_ingredients = Ingredient.objects.filter(step__recipe=instance.recipe, space=space)
if exclude_onhand := user.userpreference.mealplan_autoexclude_onhand:
recipe_ingredients = recipe_ingredients.exclude(food__on_hand=True)
if related := user.userpreference.mealplan_autoinclude_related:
# TODO: add levels of related recipes to use when auto-adding mealplans
related_recipes = instance.recipe.get_related_recipes()
# dont' add recipes that are going to have their recipes added to the shopping list
kwargs['ingredients'] = recipe_ingredients.exclude(food__recipe__in=related_recipes).values_list('id', flat=True)
else:
kwargs['ingredients'] = recipe_ingredients.values_list('id', flat=True)
list_recipe = list_from_recipe(**kwargs)
if related:
servings_factor = Decimal(instance.servings / instance.recipe.servings)
kwargs['list_recipe'] = list_recipe
food_recipes = recipe_ingredients.filter(food__recipe__in=related_recipes).values('food__recipe', 'amount')
for recipe in related_recipes:
kwargs['ingredients'] = []
if exclude_onhand:
kwargs['ingredients'] = Ingredient.objects.filter(step__recipe=recipe, food__on_hand=False, space=space).values_list('id', flat=True)
kwargs['recipe'] = recipe
# assume related recipes are intended to be 'full sized' to parent recipe
# Recipe1 (servings:4) includes StepRecipe2(servings:2) a Meal Plan serving size of 8 would assume 4 servings of StepRecipe2
if recipe.id in [x['food__recipe'] for x in food_recipes if x['food__recipe'] == recipe.id]:
kwargs['servings'] = Decimal(recipe.servings) * sum([x['amount'] for x in food_recipes if x['food__recipe'] == recipe.id]) * servings_factor
else:
# TODO: When modifying step recipes to allow serving size - will need to update this
kwargs['servings'] = Decimal(recipe.servings) * servings_factor
list_from_recipe(**kwargs, append=True)

View File

@ -243,7 +243,7 @@
window.location.hash = e.target.hash; window.location.hash = e.target.hash;
}) })
// listen for events // listen for events
$(document).ready(function(){ {% comment %} $(document).ready(function(){
hideShow() hideShow()
// call hideShow when the user clicks on the mealplan_autoadd checkbox // call hideShow when the user clicks on the mealplan_autoadd checkbox
$("#id_shopping-mealplan_autoadd_shopping").click(function(event){ $("#id_shopping-mealplan_autoadd_shopping").click(function(event){
@ -261,7 +261,7 @@
{ {
$('#div_id_shopping-mealplan_autoexclude_onhand').hide(); $('#div_id_shopping-mealplan_autoexclude_onhand').hide();
$('#div_id_shopping-mealplan_autoinclude_related').hide(); $('#div_id_shopping-mealplan_autoinclude_related').hide();
} } {% endcomment %}
} }
</script> </script>
{% endblock %} {% endblock %}

View File

@ -38,7 +38,7 @@ from cookbook.helper.permission_helper import (CustomIsAdmin, CustomIsGuest, Cus
from cookbook.helper.recipe_html_import import get_recipe_from_source from cookbook.helper.recipe_html_import import get_recipe_from_source
from cookbook.helper.recipe_search import get_facet, old_search, search_recipes from cookbook.helper.recipe_search import get_facet, old_search, search_recipes
from cookbook.helper.recipe_url_import import get_from_scraper from cookbook.helper.recipe_url_import import get_from_scraper
from cookbook.helper.shopping_helper import shopping_helper from cookbook.helper.shopping_helper import list_from_recipe, shopping_helper
from cookbook.models import (Automation, BookmarkletImport, CookLog, Food, FoodInheritField, from cookbook.models import (Automation, BookmarkletImport, CookLog, Food, FoodInheritField,
ImportLog, Ingredient, Keyword, MealPlan, MealType, Recipe, RecipeBook, ImportLog, Ingredient, Keyword, MealPlan, MealType, Recipe, RecipeBook,
RecipeBookEntry, ShareLink, ShoppingList, ShoppingListEntry, RecipeBookEntry, ShareLink, ShoppingList, ShoppingListEntry,
@ -658,7 +658,7 @@ class RecipeViewSet(viewsets.ModelViewSet):
list_recipe = request.data.get('list_recipe', None) list_recipe = request.data.get('list_recipe', None)
content = {'msg': _(f'{obj.name} was added to the shopping list.')} content = {'msg': _(f'{obj.name} was added to the shopping list.')}
# TODO: Consider if this should be a Recipe method # TODO: Consider if this should be a Recipe method
ShoppingListEntry.list_from_recipe(list_recipe=list_recipe, recipe=obj, ingredients=ingredients, servings=servings, space=request.space, created_by=request.user) list_from_recipe(list_recipe=list_recipe, recipe=obj, ingredients=ingredients, servings=servings, space=request.space, created_by=request.user)
return Response(content, status=status.HTTP_204_NO_CONTENT) return Response(content, status=status.HTTP_204_NO_CONTENT)

View File

@ -81,18 +81,11 @@
<hr /> <hr />
<div class="row"> <div class="row">
<div class="col-md-8 order-md-1 col-sm-12 order-sm-2 col-12 order-2" v-if="recipe && ingredient_count > 0"> <div class="col-md-6 order-md-1 col-sm-12 order-sm-2 col-12 order-2" v-if="recipe && ingredient_count > 0">
<ingredients-card <ingredients-card :steps="recipe.steps" :ingredient_factor="ingredient_factor" :servings="servings" :header="true" @checked-state-changed="updateIngredientCheckedState" />
:steps="recipe.steps"
:recipe="recipe.id"
:ingredient_factor="ingredient_factor"
:servings="servings"
:header="true"
@checked-state-changed="updateIngredientCheckedState"
/>
</div> </div>
<div class="col-12 order-1 col-sm-12 order-sm-1 col-md-4 order-md-2"> <div class="col-12 order-1 col-sm-12 order-sm-1 col-md-6 order-md-2">
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<img class="img img-fluid rounded" :src="recipe.image" style="max-height: 30vh" :alt="$t('Recipe_Image')" v-if="recipe.image !== null" /> <img class="img img-fluid rounded" :src="recipe.image" style="max-height: 30vh" :alt="$t('Recipe_Image')" v-if="recipe.image !== null" />
@ -149,11 +142,11 @@ import { apiLoadRecipe } from "@/utils/api"
import Step from "@/components/Step" import Step from "@/components/Step"
import RecipeContextMenu from "@/components/ContextMenu/RecipeContextMenu" import RecipeContextMenu from "@/components/ContextMenu/RecipeContextMenu"
import { ResolveUrlMixin, ToastMixin } from "@/utils/utils" import { ResolveUrlMixin, ToastMixin } from "@/utils/utils"
import IngredientsCard from "@/components/IngredientsCard"
import PdfViewer from "@/components/PdfViewer" import PdfViewer from "@/components/PdfViewer"
import ImageViewer from "@/components/ImageViewer" import ImageViewer from "@/components/ImageViewer"
import Nutrition from "@/components/Nutrition" import Nutrition from "@/components/Nutrition"
import IngredientsCard from "@/components/IngredientsCard"
import moment from "moment" import moment from "moment"
import Keywords from "@/components/Keywords" import Keywords from "@/components/Keywords"

View File

@ -1,34 +0,0 @@
<template>
<div>
<div style="position: static;" class=" btn-group">
<div class="dropdown b-dropdown position-static">
<li @click="$refs.submenu.open($event)" role="presentation" class="btn dropdown-toggle btn-link text-decoration-none text-body pr-1 dropdown-toggle-no-caret">
<slot />
</li>
</div>
</div>
<ContextMenu ref="submenu">
<template #menu="{ contextData }">
<ContextMenuItem
@click="
$refs.menu.close()
moveEntry(contextData)
"
>
<a class="dropdown-item p-2" href="#"><i class="fas fa-cubes"></i> submenu item</a>
</ContextMenuItem>
</template>
</ContextMenu>
</div>
</template>
<script>
import ContextMenu from "@/components/ContextMenu/ContextMenu"
import ContextMenuItem from "@/components/ContextMenu/ContextMenuItem"
export default {
name: "ContextSubmenu.vue",
components: { ContextMenu, ContextMenuItem },
}
</script>
<style scoped></style>

View File

@ -1,193 +1,189 @@
<template> <template>
<div> <div>
<div class="dropdown d-print-none">
<a class="btn shadow-none" href="#" role="button" id="dropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="fas fa-ellipsis-v fa-lg"></i>
</a>
<div class="dropdown d-print-none"> <div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuLink">
<a class="btn shadow-none" href="javascript:void(0);" role="button" id="dropdownMenuLink" <a class="dropdown-item" :href="resolveDjangoUrl('edit_recipe', recipe.id)"><i class="fas fa-pencil-alt fa-fw"></i> {{ $t("Edit") }}</a>
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="fas fa-ellipsis-v fa-lg"></i>
</a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuLink"> <a class="dropdown-item" :href="resolveDjangoUrl('edit_convert_recipe', recipe.id)" v-if="!recipe.internal"
><i class="fas fa-exchange-alt fa-fw"></i> {{ $t("convert_internal") }}</a
>
<a class="dropdown-item" :href="resolveDjangoUrl('edit_recipe', recipe.id)"><i <a href="#">
class="fas fa-pencil-alt fa-fw"></i> {{ $t('Edit') }}</a> <button class="dropdown-item" @click="$bvModal.show(`id_modal_add_book_${modal_id}`)"><i class="fas fa-bookmark fa-fw"></i> {{ $t("Manage_Books") }}</button>
</a>
<a class="dropdown-item" :href="resolveDjangoUrl('edit_convert_recipe', recipe.id)" v-if="!recipe.internal"><i <a
class="fas fa-exchange-alt fa-fw"></i> {{ $t('convert_internal') }}</a> class="dropdown-item"
:href="`${resolveDjangoUrl('view_shopping')}?r=[${recipe.id},${servings_value}]`"
v-if="recipe.internal"
target="_blank"
rel="noopener noreferrer"
>
<i class="fas fa-shopping-cart fa-fw"></i> {{ $t("Add_to_Shopping") }}
</a>
<a href="javascript:void(0);"> <a class="dropdown-item" v-if="recipe.internal" @click="addToShopping" href="#"> <i class="fas fa-shopping-cart fa-fw"></i> New {{ $t("Add_to_Shopping") }} </a>
<button class="dropdown-item" @click="$bvModal.show(`id_modal_add_book_${modal_id}`)">
<i class="fas fa-bookmark fa-fw"></i> {{ $t('Manage_Books') }}
</button>
</a>
<a class="dropdown-item" :href="`${resolveDjangoUrl('view_shopping') }?r=[${recipe.id},${servings_value}]`" <a class="dropdown-item" @click="createMealPlan" href="#"><i class="fas fa-calendar fa-fw"></i> {{ $t("Add_to_Plan") }} </a>
v-if="recipe.internal" target="_blank" rel="noopener noreferrer">
<i class="fas fa-shopping-cart fa-fw"></i> {{ $t('Add_to_Shopping') }}
</a>
<a class="dropdown-item" v-if="recipe.internal" @click="addToShopping" href="#"> <a href="#">
<i class="fas fa-shopping-cart fa-fw"></i> New {{ $t('Add_to_Shopping') }} <button class="dropdown-item" @click="$bvModal.show(`id_modal_cook_log_${modal_id}`)">
</a> <i class="fas fa-clipboard-list fa-fw"></i> {{ $t("Log_Cooking") }}
</button>
</a>
<a class="dropdown-item" @click="createMealPlan" href="#"><i <a href="#">
class="fas fa-calendar fa-fw"></i> {{ $t('Add_to_Plan') }} <button class="dropdown-item" onclick="window.print()"><i class="fas fa-print fa-fw"></i> {{ $t("Print") }}</button>
</a> </a>
<a href="javascript:void(0);"> <a class="dropdown-item" :href="resolveDjangoUrl('view_export') + '?r=' + recipe.id" target="_blank" rel="noopener noreferrer"
<button class="dropdown-item" @click="$bvModal.show(`id_modal_cook_log_${modal_id}`)"><i ><i class="fas fa-file-export fa-fw"></i> {{ $t("Export") }}</a
class="fas fa-clipboard-list fa-fw"></i> {{ $t('Log_Cooking') }} >
</button>
</a>
<a href="javascript:void(0);"> <a href="#">
<button class="dropdown-item" onclick="window.print()"><i <button class="dropdown-item" @click="createShareLink()" v-if="recipe.internal"><i class="fas fa-share-alt fa-fw"></i> {{ $t("Share") }}</button>
class="fas fa-print fa-fw"></i> {{ $t('Print') }} </a>
</button> </div>
</a>
<a class="dropdown-item" :href="resolveDjangoUrl('view_export') + '?r=' + recipe.id" target="_blank"
rel="noopener noreferrer"><i class="fas fa-file-export fa-fw"></i> {{ $t('Export') }}</a>
<a href="javascript:void(0);">
<button class="dropdown-item" @click="createShareLink()" v-if="recipe.internal"><i
class="fas fa-share-alt fa-fw"></i> {{ $t('Share') }}
</button>
</a>
</div>
</div>
<cook-log :recipe="recipe" :modal_id="modal_id"></cook-log>
<add-recipe-to-book :recipe="recipe" :modal_id="modal_id"></add-recipe-to-book>
<b-modal :id="`modal-share-link_${modal_id}`" v-bind:title="$t('Share')" hide-footer>
<div class="row">
<div class="col col-md-12">
<label v-if="recipe_share_link !== undefined">{{ $t('Public share link') }}</label>
<input ref="share_link_ref" class="form-control" v-model="recipe_share_link"/>
<b-button class="mt-2 mb-3 d-none d-md-inline" variant="secondary"
@click="$bvModal.hide(`modal-share-link_${modal_id}`)">{{ $t('Close') }}
</b-button>
<b-button class="mt-2 mb-3 ml-md-2" variant="primary" @click="copyShareLink()">{{ $t('Copy') }}</b-button>
<b-button class="mt-2 mb-3 ml-2 float-right" variant="success" @click="shareIntend()">{{ $t('Share') }} <i
class="fa fa-share-alt"></i></b-button>
</div> </div>
</div>
</b-modal>
<meal-plan-edit-modal :entry="entryEditing" :entryEditing_initial_recipe="[recipe]" <cook-log :recipe="recipe" :modal_id="modal_id"></cook-log>
:entry-editing_initial_meal_type="[]" @save-entry="saveMealPlan" <add-recipe-to-book :recipe="recipe" :modal_id="modal_id"></add-recipe-to-book>
:modal_id="`modal-meal-plan_${modal_id}`" :allow_delete="false" :modal_title="$t('Create_Meal_Plan_Entry')"></meal-plan-edit-modal>
<shopping-modal :recipe="recipe" :servings="servings_value" :modal_id="modal_id"/> <b-modal :id="`modal-share-link_${modal_id}`" v-bind:title="$t('Share')" hide-footer>
</div> <div class="row">
<div class="col col-md-12">
<label v-if="recipe_share_link !== undefined">{{ $t("Public share link") }}</label>
<input ref="share_link_ref" class="form-control" v-model="recipe_share_link" />
<b-button class="mt-2 mb-3 d-none d-md-inline" variant="secondary" @click="$bvModal.hide(`modal-share-link_${modal_id}`)">{{ $t("Close") }} </b-button>
<b-button class="mt-2 mb-3 ml-md-2" variant="primary" @click="copyShareLink()">{{ $t("Copy") }}</b-button>
<b-button class="mt-2 mb-3 ml-2 float-right" variant="success" @click="shareIntend()">{{ $t("Share") }} <i class="fa fa-share-alt"></i></b-button>
</div>
</div>
</b-modal>
<meal-plan-edit-modal
:entry="entryEditing"
:entryEditing_initial_recipe="[recipe]"
:entryEditing_inital_servings="recipe.servings"
:entry-editing_initial_meal_type="[]"
@save-entry="saveMealPlan"
:modal_id="`modal-meal-plan_${modal_id}`"
:allow_delete="false"
:modal_title="$t('Create_Meal_Plan_Entry')"
></meal-plan-edit-modal>
<shopping-modal :recipe="recipe" :servings="servings_value" :modal_id="modal_id" />
</div>
</template> </template>
<script> <script>
import { makeToast, resolveDjangoUrl, ResolveUrlMixin, StandardToasts } from "@/utils/utils"
import {makeToast, resolveDjangoUrl, ResolveUrlMixin, StandardToasts} from "@/utils/utils"; import CookLog from "@/components/CookLog"
import CookLog from "@/components/CookLog"; import axios from "axios"
import axios from "axios"; import AddRecipeToBook from "@/components/Modals/AddRecipeToBook"
import AddRecipeToBook from "@/components/Modals/AddRecipeToBook"; import MealPlanEditModal from "@/components/Modals/MealPlanEditModal"
import MealPlanEditModal from "@/components/Modals/MealPlanEditModal"; import ShoppingModal from "@/components/Modals/ShoppingModal"
import ShoppingModal from "@/components/Modals/ShoppingModal"; import moment from "moment"
import moment from "moment"; import Vue from "vue"
import Vue from "vue"; import { ApiApiFactory } from "@/utils/openapi/api"
import {ApiApiFactory} from "@/utils/openapi/api";
Vue.prototype.moment = moment Vue.prototype.moment = moment
export default { export default {
name: 'RecipeContextMenu', name: "RecipeContextMenu",
mixins: [ mixins: [ResolveUrlMixin],
ResolveUrlMixin components: {
], AddRecipeToBook,
components: { CookLog,
AddRecipeToBook, MealPlanEditModal,
CookLog, ShoppingModal,
MealPlanEditModal, },
ShoppingModal data() {
}, return {
data() { servings_value: 0,
return { recipe_share_link: undefined,
servings_value: 0, modal_id: this.recipe.id + Math.round(Math.random() * 100000),
recipe_share_link: undefined, options: {
modal_id: this.recipe.id + Math.round(Math.random() * 100000), entryEditing: {
options: { date: null,
entryEditing: { id: -1,
date: null, meal_type: null,
id: -1, note: "",
meal_type: null, note_markdown: "",
note: "", recipe: null,
note_markdown: "", servings: 1,
recipe: null, shared: [],
servings: 1, title: "",
shared: [], title_placeholder: this.$t("Title"),
title: '', },
title_placeholder: this.$t('Title'), },
entryEditing: {},
} }
}, },
entryEditing: {}, props: {
} recipe: Object,
}, servings: {
props: { type: Number,
recipe: Object, default: -1,
servings: { },
type: Number, },
default: -1 mounted() {
} this.servings_value = this.servings === -1 ? this.recipe.servings : this.servings
}, },
mounted() { methods: {
this.servings_value = ((this.servings === -1) ? this.recipe.servings : this.servings) saveMealPlan: function(entry) {
}, entry.date = moment(entry.date).format("YYYY-MM-DD")
methods: {
saveMealPlan: function (entry) {
entry.date = moment(entry.date).format("YYYY-MM-DD")
let apiClient = new ApiApiFactory() let apiClient = new ApiApiFactory()
apiClient.createMealPlan(entry).then(result => { apiClient
this.$bvModal.hide(`modal-meal-plan_${this.modal_id}`) .createMealPlan(entry)
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_CREATE) .then((result) => {
}).catch(error => { this.$bvModal.hide(`modal-meal-plan_${this.modal_id}`)
StandardToasts.makeStandardToast(StandardToasts.FAIL_CREATE) StandardToasts.makeStandardToast(StandardToasts.SUCCESS_CREATE)
}) })
.catch((error) => {
StandardToasts.makeStandardToast(StandardToasts.FAIL_CREATE)
})
},
createMealPlan(data) {
this.entryEditing = this.options.entryEditing
this.entryEditing.recipe = this.recipe
this.entryEditing.date = moment(new Date()).format("YYYY-MM-DD")
this.$bvModal.show(`modal-meal-plan_${this.modal_id}`)
},
createShareLink: function() {
axios
.get(resolveDjangoUrl("api_share_link", this.recipe.id))
.then((result) => {
this.$bvModal.show(`modal-share-link_${this.modal_id}`)
this.recipe_share_link = result.data.link
})
.catch((err) => {
if (err.response.status === 403) {
makeToast(this.$t("Share"), this.$t("Sharing is not enabled for this space."), "danger")
}
})
},
copyShareLink: function() {
let share_input = this.$refs.share_link_ref
share_input.select()
document.execCommand("copy")
},
shareIntend: function() {
let shareData = {
title: this.recipe.name,
text: `${this.$t("Check out this recipe: ")} ${this.recipe.name}`,
url: this.recipe_share_link,
}
navigator.share(shareData)
},
addToShopping() {
this.$bvModal.show(`shopping_${this.modal_id}`)
},
}, },
createMealPlan(data) {
this.entryEditing = this.options.entryEditing
this.entryEditing.recipe = this.recipe
this.entryEditing.date = moment(new Date()).format('YYYY-MM-DD')
this.$bvModal.show(`modal-meal-plan_${this.modal_id}`)
},
createShareLink: function () {
axios.get(resolveDjangoUrl('api_share_link', this.recipe.id)).then(result => {
this.$bvModal.show(`modal-share-link_${this.modal_id}`)
this.recipe_share_link = result.data.link
}).catch(err => {
if (err.response.status === 403) {
makeToast(this.$t('Share'), this.$t('Sharing is not enabled for this space.'), 'danger')
}
})
},
copyShareLink: function () {
let share_input = this.$refs.share_link_ref;
share_input.select();
document.execCommand("copy");
},
shareIntend: function () {
let shareData = {
title: this.recipe.name,
text: `${this.$t('Check out this recipe: ')} ${this.recipe.name}`,
url: this.recipe_share_link
}
navigator.share(shareData)
},
addToShopping() {
this.$bvModal.show(`shopping_${this.modal_id}`)
},
}
} }
</script> </script>

View File

@ -12,15 +12,11 @@
@change="missing_recipe = false" @change="missing_recipe = false"
></b-form-input> ></b-form-input>
<b-input-group-append class="d-none d-lg-block"> <b-input-group-append class="d-none d-lg-block">
<b-button variant="primary" @click="entryEditing.title = ''" <b-button variant="primary" @click="entryEditing.title = ''"><i class="fa fa-eraser"></i></b-button>
><i class="fa fa-eraser"></i
></b-button>
</b-input-group-append> </b-input-group-append>
</b-input-group> </b-input-group>
<span class="text-danger" v-if="missing_recipe">{{ $t("Title_or_Recipe_Required") }}</span> <span class="text-danger" v-if="missing_recipe">{{ $t("Title_or_Recipe_Required") }}</span>
<small tabindex="-1" class="form-text text-muted" v-if="!missing_recipe">{{ <small tabindex="-1" class="form-text text-muted" v-if="!missing_recipe">{{ $t("Title") }}</small>
$t("Title")
}}</small>
</div> </div>
<div class="col-6 col-lg-3"> <div class="col-6 col-lg-3">
<input type="date" id="DateInput" class="form-control" v-model="entryEditing.date" /> <input type="date" id="DateInput" class="form-control" v-model="entryEditing.date" />
@ -58,24 +54,13 @@
@new="createMealType" @new="createMealType"
></generic-multiselect> ></generic-multiselect>
<span class="text-danger" v-if="missing_meal_type">{{ $t("Meal_Type_Required") }}</span> <span class="text-danger" v-if="missing_meal_type">{{ $t("Meal_Type_Required") }}</span>
<small tabindex="-1" class="form-text text-muted" v-if="!missing_meal_type">{{ <small tabindex="-1" class="form-text text-muted" v-if="!missing_meal_type">{{ $t("Meal_Type") }}</small>
$t("Meal_Type")
}}</small>
</b-form-group> </b-form-group>
<b-form-group label-for="NoteInput" :description="$t('Note')" class="mt-3"> <b-form-group label-for="NoteInput" :description="$t('Note')" class="mt-3">
<textarea <textarea class="form-control" id="NoteInput" v-model="entryEditing.note" :placeholder="$t('Note')"></textarea>
class="form-control"
id="NoteInput"
v-model="entryEditing.note"
:placeholder="$t('Note')"
></textarea>
</b-form-group> </b-form-group>
<b-input-group> <b-input-group>
<b-form-input <b-form-input id="ServingsInput" v-model="entryEditing.servings" :placeholder="$t('Servings')"></b-form-input>
id="ServingsInput"
v-model="entryEditing.servings"
:placeholder="$t('Servings')"
></b-form-input>
</b-input-group> </b-input-group>
<small tabindex="-1" class="form-text text-muted">{{ $t("Servings") }}</small> <small tabindex="-1" class="form-text text-muted">{{ $t("Servings") }}</small>
<!-- TODO: hide this checkbox if autoadding menuplans, but allow editing on-hand --> <!-- TODO: hide this checkbox if autoadding menuplans, but allow editing on-hand -->
@ -90,9 +75,7 @@
</div> </div>
<div class="row mt-3 mb-3"> <div class="row mt-3 mb-3">
<div class="col-12"> <div class="col-12">
<b-button variant="danger" @click="deleteEntry" v-if="allow_delete" <b-button variant="danger" @click="deleteEntry" v-if="allow_delete">{{ $t("Delete") }} </b-button>
>{{ $t("Delete") }}
</b-button>
<b-button class="float-right" variant="primary" @click="editEntry">{{ $t("Save") }}</b-button> <b-button class="float-right" variant="primary" @click="editEntry">{{ $t("Save") }}</b-button>
</div> </div>
</div> </div>
@ -119,6 +102,7 @@ export default {
entry: Object, entry: Object,
entryEditing_initial_recipe: Array, entryEditing_initial_recipe: Array,
entryEditing_initial_meal_type: Array, entryEditing_initial_meal_type: Array,
entryEditing_inital_servings: Number,
modal_title: String, modal_title: String,
modal_id: { modal_id: {
type: String, type: String,
@ -145,6 +129,9 @@ export default {
entry: { entry: {
handler() { handler() {
this.entryEditing = Object.assign({}, this.entry) this.entryEditing = Object.assign({}, this.entry)
if (this.entryEditing_inital_servings) {
this.entryEditing.servings = this.entryEditing_inital_servings
}
}, },
deep: true, deep: true,
}, },

View File

@ -233,8 +233,8 @@
"shopping_share_desc": "Users will see all items you add to your shopping list. They must add you to see items on their list.", "shopping_share_desc": "Users will see all items you add to your shopping list. They must add you to see items on their list.",
"shopping_auto_sync_desc": "Setting to 0 will disable auto sync. When viewing a shopping list the list is updated every set seconds to sync changes someone else might have made. Useful when shopping with multiple people but will use mobile data.", "shopping_auto_sync_desc": "Setting to 0 will disable auto sync. When viewing a shopping list the list is updated every set seconds to sync changes someone else might have made. Useful when shopping with multiple people but will use mobile data.",
"mealplan_autoadd_shopping_desc": "Automatically add meal plan ingredients to shopping list.", "mealplan_autoadd_shopping_desc": "Automatically add meal plan ingredients to shopping list.",
"mealplan_autoexclude_onhand_desc": "When automatically adding a meal plan to the shopping list, exclude ingredients that are on hand.", "mealplan_autoexclude_onhand_desc": "When adding a meal plan to the shopping list (manually or automatically), exclude ingredients that are on hand.",
"mealplan_autoinclude_related_desc": "When automatically adding a meal plan to the shopping list, include all related recipes.", "mealplan_autoinclude_related_desc": "When adding a meal plan to the shopping list (manually or automatically), include all related recipes.",
"default_delay_desc": "Default number of hours to delay a shopping list entry.", "default_delay_desc": "Default number of hours to delay a shopping list entry.",
"filter_to_supermarket": "Filter to Supermarket", "filter_to_supermarket": "Filter to Supermarket",
"filter_to_supermarket_desc": "Filter shopping list to only include supermarket categories.", "filter_to_supermarket_desc": "Filter shopping list to only include supermarket categories.",

View File

@ -229,10 +229,10 @@ export interface Food {
parent?: string; parent?: string;
/** /**
* *
* @type {boolean} * @type {number}
* @memberof Food * @memberof Food
*/ */
on_hand?: boolean; numchild?: number;
/** /**
* *
* @type {boolean} * @type {boolean}
@ -269,13 +269,13 @@ export interface FoodIgnoreInherit {
* @type {string} * @type {string}
* @memberof FoodIgnoreInherit * @memberof FoodIgnoreInherit
*/ */
name?: string; name: string;
/** /**
* *
* @type {string} * @type {string}
* @memberof FoodIgnoreInherit * @memberof FoodIgnoreInherit
*/ */
field?: string; field: string;
} }
/** /**
* *
@ -294,13 +294,13 @@ export interface FoodInheritField {
* @type {string} * @type {string}
* @memberof FoodInheritField * @memberof FoodInheritField
*/ */
name?: string; name: string;
/** /**
* *
* @type {string} * @type {string}
* @memberof FoodInheritField * @memberof FoodInheritField
*/ */
field?: string; field: string;
} }
/** /**
* *
@ -958,37 +958,6 @@ export interface InlineResponse2009 {
*/ */
results?: Array<ViewLog>; results?: Array<ViewLog>;
} }
/**
*
* @export
* @interface InlineResponse2009
*/
export interface InlineResponse2009 {
/**
*
* @type {number}
* @memberof InlineResponse2009
*/
count?: number;
/**
*
* @type {string}
* @memberof InlineResponse2009
*/
next?: string | null;
/**
*
* @type {string}
* @memberof InlineResponse2009
*/
previous?: string | null;
/**
*
* @type {Array<ViewLog>}
* @memberof InlineResponse2009
*/
results?: Array<ViewLog>;
}
/** /**
* *
* @export * @export
@ -1006,7 +975,13 @@ export interface Keyword {
* @type {string} * @type {string}
* @memberof Keyword * @memberof Keyword
*/ */
parent?: string; name: string;
/**
*
* @type {string}
* @memberof Keyword
*/
icon?: string | null;
/** /**
* *
* @type {string} * @type {string}
@ -1018,13 +993,31 @@ export interface Keyword {
* @type {string} * @type {string}
* @memberof Keyword * @memberof Keyword
*/ */
created_at?: string; description?: string;
/**
*
* @type {string}
* @memberof Keyword
*/
parent?: string;
/** /**
* *
* @type {number} * @type {number}
* @memberof Keyword * @memberof Keyword
*/ */
numchild?: number; numchild?: number;
/**
*
* @type {string}
* @memberof Keyword
*/
created_at?: string;
/**
*
* @type {string}
* @memberof Keyword
*/
updated_at?: string;
} }
/** /**
* *
@ -1632,61 +1625,6 @@ export interface RecipeIngredients {
*/ */
no_amount?: boolean; no_amount?: boolean;
} }
/**
*
* @export
* @interface RecipeKeywords
*/
export interface RecipeIngredients {
/**
*
* @type {number}
* @memberof RecipeIngredients
*/
id?: number;
/**
*
* @type {IngredientFood}
* @memberof RecipeIngredients
*/
food: IngredientFood | null;
/**
*
* @type {FoodSupermarketCategory}
* @memberof RecipeIngredients
*/
unit: FoodSupermarketCategory | null;
/**
*
* @type {string}
* @memberof RecipeIngredients
*/
amount: string;
/**
*
* @type {string}
* @memberof RecipeIngredients
*/
note?: string | null;
/**
*
* @type {number}
* @memberof RecipeIngredients
*/
order?: number;
/**
*
* @type {boolean}
* @memberof RecipeIngredients
*/
is_header?: boolean;
/**
*
* @type {boolean}
* @memberof RecipeIngredients
*/
no_amount?: boolean;
}
/** /**
* *
* @export * @export
@ -1704,7 +1642,13 @@ export interface RecipeKeywords {
* @type {string} * @type {string}
* @memberof RecipeKeywords * @memberof RecipeKeywords
*/ */
parent?: string; name: string;
/**
*
* @type {string}
* @memberof RecipeKeywords
*/
icon?: string | null;
/** /**
* *
* @type {string} * @type {string}
@ -1716,13 +1660,31 @@ export interface RecipeKeywords {
* @type {string} * @type {string}
* @memberof RecipeKeywords * @memberof RecipeKeywords
*/ */
created_at?: string; description?: string;
/**
*
* @type {string}
* @memberof RecipeKeywords
*/
parent?: string;
/** /**
* *
* @type {number} * @type {number}
* @memberof RecipeKeywords * @memberof RecipeKeywords
*/ */
numchild?: number; numchild?: number;
/**
*
* @type {string}
* @memberof RecipeKeywords
*/
created_at?: string;
/**
*
* @type {string}
* @memberof RecipeKeywords
*/
updated_at?: string;
} }
/** /**
* *
@ -2129,10 +2091,10 @@ export interface ShoppingListEntries {
id?: number; id?: number;
/** /**
* *
* @type {string} * @type {number}
* @memberof ShoppingListEntries * @memberof ShoppingListEntries
*/ */
list_recipe?: string; list_recipe?: number | null;
/** /**
* *
* @type {IngredientFood} * @type {IngredientFood}
@ -2186,7 +2148,7 @@ export interface ShoppingListEntries {
* @type {ShoppingListCreatedBy} * @type {ShoppingListCreatedBy}
* @memberof ShoppingListEntries * @memberof ShoppingListEntries
*/ */
created_by: ShoppingListCreatedBy; created_by?: ShoppingListCreatedBy;
/** /**
* *
* @type {string} * @type {string}
@ -2198,7 +2160,13 @@ export interface ShoppingListEntries {
* @type {string} * @type {string}
* @memberof ShoppingListEntries * @memberof ShoppingListEntries
*/ */
completed_at?: string | null; completed_at: string | null;
/**
*
* @type {string}
* @memberof ShoppingListEntries
*/
delay_until?: string | null;
} }
/** /**
* *
@ -2214,10 +2182,10 @@ export interface ShoppingListEntry {
id?: number; id?: number;
/** /**
* *
* @type {string} * @type {number}
* @memberof ShoppingListEntry * @memberof ShoppingListEntry
*/ */
list_recipe?: string; list_recipe?: number | null;
/** /**
* *
* @type {IngredientFood} * @type {IngredientFood}
@ -2283,7 +2251,13 @@ export interface ShoppingListEntry {
* @type {string} * @type {string}
* @memberof ShoppingListEntry * @memberof ShoppingListEntry
*/ */
completed_at?: string; completed_at: string | null;
/**
*
* @type {string}
* @memberof ShoppingListEntry
*/
delay_until?: string | null;
} }
/** /**
* *
@ -2297,6 +2271,12 @@ export interface ShoppingListRecipe {
* @memberof ShoppingListRecipe * @memberof ShoppingListRecipe
*/ */
id?: number; id?: number;
/**
*
* @type {string}
* @memberof ShoppingListRecipe
*/
recipe_name?: string;
/** /**
* *
* @type {string} * @type {string}
@ -2312,7 +2292,7 @@ export interface ShoppingListRecipe {
/** /**
* *
* @type {number} * @type {number}
* @memberof ShoppingListEntry * @memberof ShoppingListRecipe
*/ */
ingredient?: number | null; ingredient?: number | null;
/** /**
@ -2333,79 +2313,6 @@ export interface ShoppingListRecipe {
* @memberof ShoppingListRecipe * @memberof ShoppingListRecipe
*/ */
servings: string; servings: string;
/**
*
* @type {string}
* @memberof ShoppingListRecipe
*/
checked?: boolean;
/**
*
* @type {ShoppingListRecipeMealplan}
* @memberof ShoppingListEntry
*/
recipe_mealplan?: ShoppingListRecipeMealplan;
/**
*
* @type {ShoppingListCreatedBy}
* @memberof ShoppingListEntry
*/
created_by: ShoppingListCreatedBy;
/**
*
* @type {string}
* @memberof ShoppingListEntry
*/
created_at?: string;
/**
*
* @type {string}
* @memberof ShoppingListEntry
*/
completed_at?: string | null;
}
/**
*
* @export
* @interface ShoppingListRecipeMealplan
*/
export interface ShoppingListRecipeMealplan {
/**
*
* @type {number}
* @memberof ShoppingListRecipeMealplan
*/
id?: number;
/**
*
* @type {string}
* @memberof ShoppingListRecipeMealplan
*/
name?: string;
/**
*
* @type {number}
* @memberof ShoppingListRecipeMealplan
*/
recipe?: number | null;
/**
*
* @type {number}
* @memberof ShoppingListRecipe
*/
mealplan?: number | null;
/**
*
* @type {string}
* @memberof ShoppingListRecipe
*/
mealplan?: number | null;
/**
*
* @type {string}
* @memberof ShoppingListRecipeMealplan
*/
servings: string;
/** /**
* *
* @type {string} * @type {string}
@ -2428,7 +2335,13 @@ export interface ShoppingListRecipeMealplan {
/** /**
* *
* @type {string} * @type {string}
* @memberof ShoppingListRecipes * @memberof ShoppingListRecipeMealplan
*/
recipe_name?: string;
/**
*
* @type {string}
* @memberof ShoppingListRecipeMealplan
*/ */
name?: string; name?: string;
/** /**
@ -2437,6 +2350,61 @@ export interface ShoppingListRecipeMealplan {
* @memberof ShoppingListRecipeMealplan * @memberof ShoppingListRecipeMealplan
*/ */
recipe?: number | null; recipe?: number | null;
/**
*
* @type {number}
* @memberof ShoppingListRecipe
*/
mealplan?: number | null;
/**
*
* @type {string}
* @memberof ShoppingListRecipe
*/
mealplan?: number | null;
/**
*
* @type {string}
* @memberof ShoppingListRecipeMealplan
*/
servings: string;
/**
*
* @type {string}
* @memberof ShoppingListRecipeMealplan
*/
mealplan_note?: string;
}
/**
*
* @export
* @interface ShoppingListRecipes
*/
export interface ShoppingListRecipes {
/**
*
* @type {number}
* @memberof ShoppingListRecipes
*/
id?: number;
/**
*
* @type {string}
* @memberof ShoppingListRecipes
*/
recipe_name?: string;
/**
*
* @type {string}
* @memberof ShoppingListRecipes
*/
name?: string;
/**
*
* @type {number}
* @memberof ShoppingListRecipes
*/
recipe?: number | null;
/** /**
* *
* @type {number} * @type {number}
@ -2452,13 +2420,13 @@ export interface ShoppingListRecipeMealplan {
/** /**
* *
* @type {string} * @type {string}
* @memberof ShoppingListRecipeMealplan * @memberof ShoppingListRecipes
*/ */
servings: string; servings: string;
/** /**
* *
* @type {string} * @type {string}
* @memberof ShoppingListRecipeMealplan * @memberof ShoppingListRecipes
*/ */
mealplan_note?: string; mealplan_note?: string;
} }
@ -3038,6 +3006,36 @@ export interface UserPreference {
* @memberof UserPreference * @memberof UserPreference
*/ */
mealplan_autoadd_shopping?: boolean; mealplan_autoadd_shopping?: boolean;
/**
*
* @type {string}
* @memberof UserPreference
*/
food_ignore_default?: string;
/**
*
* @type {number}
* @memberof UserPreference
*/
default_delay?: number;
/**
*
* @type {boolean}
* @memberof UserPreference
*/
mealplan_autoinclude_related?: boolean;
/**
*
* @type {boolean}
* @memberof UserPreference
*/
mealplan_autoexclude_onhand?: boolean;
/**
*
* @type {Array<number>}
* @memberof UserPreference
*/
shopping_share?: Array<number>;
} }
/** /**
@ -5359,12 +5357,13 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration)
}, },
/** /**
* *
* @param {string} [checked] Filter shopping list entries on checked. Valid values are true, false, both and &lt;b&gt;false+&lt;/b&gt;.&lt;br&gt; - false+ includes unchecked items and recently completed items.
* @param {number} [id] Returns the shopping list entry with a primary key of id. Multiple values allowed. * @param {number} [id] Returns the shopping list entry with a primary key of id. Multiple values allowed.
* @param {string} [checked] Filter shopping list entries on checked. [true, false, both, &lt;b&gt;recent&lt;/b&gt;]&lt;br&gt; - recent includes unchecked items and recently completed items.
* @param {number} [supermarket] Returns the shopping list entries sorted by supermarket category order.
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @throws {RequiredError}
*/ */
listShoppingListEntrys: async (checked?: string, id?: number, options: any = {}): Promise<RequestArgs> => { listShoppingListEntrys: async (id?: number, checked?: string, supermarket?: number, options: any = {}): Promise<RequestArgs> => {
const localVarPath = `/api/shopping-list-entry/`; const localVarPath = `/api/shopping-list-entry/`;
// use dummy base URL string because the URL constructor only accepts absolute URLs. // use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
@ -5377,12 +5376,16 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration)
const localVarHeaderParameter = {} as any; const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any; const localVarQueryParameter = {} as any;
if (id !== undefined) {
localVarQueryParameter['id'] = id;
}
if (checked !== undefined) { if (checked !== undefined) {
localVarQueryParameter['checked'] = checked; localVarQueryParameter['checked'] = checked;
} }
if (id !== undefined) { if (supermarket !== undefined) {
localVarQueryParameter['id'] = id; localVarQueryParameter['supermarket'] = supermarket;
} }
@ -9663,13 +9666,14 @@ export const ApiApiFp = function(configuration?: Configuration) {
}, },
/** /**
* *
* @param {string} [checked] Filter shopping list entries on checked. Valid values are true, false, both and &lt;b&gt;false+&lt;/b&gt;.&lt;br&gt; - false+ includes unchecked items and recently completed items.
* @param {number} [id] Returns the shopping list entry with a primary key of id. Multiple values allowed. * @param {number} [id] Returns the shopping list entry with a primary key of id. Multiple values allowed.
* @param {string} [checked] Filter shopping list entries on checked. [true, false, both, &lt;b&gt;recent&lt;/b&gt;]&lt;br&gt; - recent includes unchecked items and recently completed items.
* @param {number} [supermarket] Returns the shopping list entries sorted by supermarket category order.
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @throws {RequiredError}
*/ */
async listShoppingListEntrys(checked?: string, id?: number, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<ShoppingListEntry>>> { async listShoppingListEntrys(id?: number, checked?: string, supermarket?: number, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<ShoppingListEntry>>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.listShoppingListEntrys(checked, id, options); const localVarAxiosArgs = await localVarAxiosParamCreator.listShoppingListEntrys(id, checked, supermarket, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
}, },
/** /**
@ -11345,13 +11349,14 @@ export const ApiApiFactory = function (configuration?: Configuration, basePath?:
}, },
/** /**
* *
* @param {string} [checked] Filter shopping list entries on checked. Valid values are true, false, both and &lt;b&gt;false+&lt;/b&gt;.&lt;br&gt; - false+ includes unchecked items and recently completed items.
* @param {number} [id] Returns the shopping list entry with a primary key of id. Multiple values allowed. * @param {number} [id] Returns the shopping list entry with a primary key of id. Multiple values allowed.
* @param {string} [checked] Filter shopping list entries on checked. [true, false, both, &lt;b&gt;recent&lt;/b&gt;]&lt;br&gt; - recent includes unchecked items and recently completed items.
* @param {number} [supermarket] Returns the shopping list entries sorted by supermarket category order.
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @throws {RequiredError}
*/ */
listShoppingListEntrys(checked?: string, id?: number, options?: any): AxiosPromise<Array<ShoppingListEntry>> { listShoppingListEntrys(id?: number, checked?: string, supermarket?: number, options?: any): AxiosPromise<Array<ShoppingListEntry>> {
return localVarFp.listShoppingListEntrys(checked, id, options).then((request) => request(axios, basePath)); return localVarFp.listShoppingListEntrys(id, checked, supermarket, options).then((request) => request(axios, basePath));
}, },
/** /**
* *
@ -13054,14 +13059,15 @@ export class ApiApi extends BaseAPI {
/** /**
* *
* @param {string} [checked] Filter shopping list entries on checked. Valid values are true, false, both and &lt;b&gt;false+&lt;/b&gt;.&lt;br&gt; - false+ includes unchecked items and recently completed items.
* @param {number} [id] Returns the shopping list entry with a primary key of id. Multiple values allowed. * @param {number} [id] Returns the shopping list entry with a primary key of id. Multiple values allowed.
* @param {string} [checked] Filter shopping list entries on checked. [true, false, both, &lt;b&gt;recent&lt;/b&gt;]&lt;br&gt; - recent includes unchecked items and recently completed items.
* @param {number} [supermarket] Returns the shopping list entries sorted by supermarket category order.
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @throws {RequiredError}
* @memberof ApiApi * @memberof ApiApi
*/ */
public listShoppingListEntrys(checked?: string, id?: number, options?: any) { public listShoppingListEntrys(id?: number, checked?: string, supermarket?: number, options?: any) {
return ApiApiFp(this.configuration).listShoppingListEntrys(checked, id, options).then((request) => request(this.axios, this.basePath)); return ApiApiFp(this.configuration).listShoppingListEntrys(id, checked, supermarket, options).then((request) => request(this.axios, this.basePath));
} }
/** /**