diff --git a/cookbook/serializer.py b/cookbook/serializer.py
index c8350f095..fe33d9667 100644
--- a/cookbook/serializer.py
+++ b/cookbook/serializer.py
@@ -1139,7 +1139,7 @@ class MealPlanSerializer(SpacedModelSerializer, WritableNestedModelSerializer):
fields = (
'id', 'title', 'recipe', 'servings', 'note', 'note_markdown',
'from_date', 'to_date', 'meal_type', 'created_by', 'shared', 'recipe_name',
- 'meal_type_name', 'shopping','addshopping'
+ 'meal_type_name', 'shopping', 'addshopping'
)
read_only_fields = ('created_by',)
@@ -1155,26 +1155,12 @@ class AutoMealPlanSerializer(serializers.Serializer):
class ShoppingListRecipeSerializer(serializers.ModelSerializer):
- name = serializers.SerializerMethodField('get_name') # should this be done at the front end?
recipe_name = serializers.ReadOnlyField(source='recipe.name')
mealplan_note = serializers.ReadOnlyField(source='mealplan.note')
mealplan_from_date = serializers.ReadOnlyField(source='mealplan.from_date')
mealplan_type = serializers.ReadOnlyField(source='mealplan.meal_type.name')
servings = CustomDecimalField()
- @extend_schema_field(str)
- def get_name(self, obj):
- if not isinstance(value := obj.servings, Decimal):
- value = Decimal(value)
- value = value.quantize(
- Decimal(1)) if value == value.to_integral() else value.normalize() # strips trailing zero
- return (
- obj.name
- or getattr(obj.mealplan, 'title', None)
- or (d := getattr(obj.mealplan, 'date', None)) and ': '.join([obj.mealplan.recipe.name, str(d)])
- or obj.recipe.name
- ) + f' ({value:.2g})'
-
def update(self, instance, validated_data):
# TODO remove once old shopping list
if 'servings' in validated_data and self.context.get('view', None).__class__.__name__ != 'ShoppingListViewSet':
@@ -1252,6 +1238,16 @@ class ShoppingListEntrySerializer(WritableNestedModelSerializer):
read_only_fields = ('id', 'created_by', 'created_at')
+class ShoppingListEntrySimpleCreateSerializer(serializers.Serializer):
+ amount = CustomDecimalField()
+ unit_id = serializers.IntegerField(allow_null=True)
+ food_id = serializers.IntegerField(allow_null=True)
+
+
+class ShoppingListEntryBulkCreateSerializer(serializers.Serializer):
+ entries = serializers.ListField(child=ShoppingListEntrySimpleCreateSerializer())
+
+
class ShoppingListEntryBulkSerializer(serializers.Serializer):
ids = serializers.ListField()
checked = serializers.BooleanField()
@@ -1537,8 +1533,8 @@ class RecipeExportSerializer(WritableNestedModelSerializer):
class RecipeShoppingUpdateSerializer(serializers.ModelSerializer):
list_recipe = serializers.IntegerField(write_only=True, allow_null=True, required=False,
help_text=_("Existing shopping list to update"))
- ingredients = serializers.IntegerField(write_only=True, allow_null=True, required=False, help_text=_(
- "List of ingredient IDs from the recipe to add, if not provided all ingredients will be added."))
+ ingredients = serializers.ListField(child=serializers.IntegerField(write_only=True, allow_null=True, required=False, help_text=_(
+ "List of ingredient IDs from the recipe to add, if not provided all ingredients will be added.")))
servings = serializers.IntegerField(default=1, write_only=True, allow_null=True, required=False, help_text=_(
"Providing a list_recipe ID and servings of 0 will delete that shopping list."))
@@ -1627,9 +1623,10 @@ class SourceImportDuplicateSerializer(serializers.Serializer):
id = serializers.IntegerField()
name = serializers.CharField()
+
class RecipeFromSourceResponseSerializer(serializers.Serializer):
recipe = SourceImportRecipeSerializer(default=None)
- images = serializers.ListField(child=serializers.CharField(),default=[], allow_null=False)
+ images = serializers.ListField(child=serializers.CharField(), default=[], allow_null=False)
error = serializers.BooleanField(default=False)
msg = serializers.CharField(max_length=1024, default='')
duplicates = serializers.ListField(child=SourceImportDuplicateSerializer(), default=[], allow_null=False)
diff --git a/cookbook/views/api.py b/cookbook/views/api.py
index a0aed0dd7..163572f10 100644
--- a/cookbook/views/api.py
+++ b/cookbook/views/api.py
@@ -105,7 +105,7 @@ from cookbook.serializer import (AccessTokenSerializer, AutomationSerializer, Au
SupermarketSerializer, SyncLogSerializer, SyncSerializer,
UnitConversionSerializer, UnitSerializer, UserFileSerializer, UserPreferenceSerializer,
UserSerializer, UserSpaceSerializer, ViewLogSerializer, ImportImageSerializer,
- LocalizationSerializer, ServerSettingsSerializer, RecipeFromSourceResponseSerializer
+ LocalizationSerializer, ServerSettingsSerializer, RecipeFromSourceResponseSerializer, ShoppingListEntryBulkCreateSerializer
)
from cookbook.version_info import TANDOOR_VERSION
from cookbook.views.import_export import get_integration
@@ -1289,7 +1289,8 @@ class RecipeViewSet(LoggingMixin, viewsets.ModelViewSet):
return Response(content, status=http_status)
- @decorators.action(detail=True, methods=['GET'], serializer_class=RecipeSimpleSerializer)
+ @extend_schema(responses=RecipeSimpleSerializer(many=True))
+ @decorators.action(detail=True, pagination_class=None, methods=['GET'], serializer_class=RecipeSimpleSerializer)
def related(self, request, pk):
obj = self.get_object()
if obj.get_space() != request.space:
@@ -1371,8 +1372,34 @@ class ShoppingListRecipeViewSet(LoggingMixin, viewsets.ModelViewSet):
self.queryset = self.queryset.filter(Q(entries__space=self.request.space) | Q(recipe__space=self.request.space))
return self.queryset.filter(Q(entries__isnull=True)
| Q(entries__created_by=self.request.user)
- | Q(
- entries__created_by__in=list(self.request.user.get_shopping_share()))).distinct().all()
+ | Q(entries__created_by__in=list(self.request.user.get_shopping_share()))).distinct().all()
+
+ @decorators.action(detail=True, methods=['POST'], serializer_class=ShoppingListEntryBulkCreateSerializer, permission_classes=[CustomIsUser])
+ def bulk_create_entries(self, request, pk):
+ 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)
+
+ serializer = self.serializer_class(data=request.data)
+
+ if serializer.is_valid():
+ entries = []
+ for e in serializer.validated_data['entries']:
+ entries.append(
+ ShoppingListEntry(
+ list_recipe_id=obj.pk,
+ amount=e['amount'],
+ unit_id=e['unit_id'],
+ food_id=e['food_id'],
+ created_by_id=request.user.id,
+ space_id=request.space.id,
+ )
+ )
+
+ ShoppingListEntry.objects.bulk_create(entries)
+ return Response(serializer.validated_data)
+ else:
+ return Response(serializer.errors, 400)
@extend_schema_view(list=extend_schema(parameters=[
diff --git a/vue3/src/components/dialogs/AddToShoppingDialog.vue b/vue3/src/components/dialogs/AddToShoppingDialog.vue
index b802c0a96..adccce471 100644
--- a/vue3/src/components/dialogs/AddToShoppingDialog.vue
+++ b/vue3/src/components/dialogs/AddToShoppingDialog.vue
@@ -1,9 +1,133 @@
+
+
+
+
+
+
+ {{ recipe.name }}
+
+
+
+
+ {{ i }}
+
+
+
+
+
+
+ {{ r.name }}
+
+
+
+ {{ i }}
+
+
+
+
+
+
+
+
+ {{$t('Add_to_Shopping')}}
+
+
+