added bulk api endpoint for SLE checking

This commit is contained in:
vabene1111 2024-01-16 08:06:26 +08:00
parent 2b1eda12d1
commit 2697e42af7
4 changed files with 260 additions and 3816 deletions

View File

@ -57,7 +57,8 @@ class ExtendedRecipeMixin(serializers.ModelSerializer):
api_serializer = None api_serializer = None
# extended values are computationally expensive and not needed in normal circumstances # extended values are computationally expensive and not needed in normal circumstances
try: try:
if str2bool(self.context['request'].query_params.get('extended', False)) and self.__class__ == api_serializer: if str2bool(
self.context['request'].query_params.get('extended', False)) and self.__class__ == api_serializer:
return fields return fields
except (AttributeError, KeyError): except (AttributeError, KeyError):
pass pass
@ -81,12 +82,14 @@ class ExtendedRecipeMixin(serializers.ModelSerializer):
class OpenDataModelMixin(serializers.ModelSerializer): class OpenDataModelMixin(serializers.ModelSerializer):
def create(self, validated_data): def create(self, validated_data):
if 'open_data_slug' in validated_data and validated_data['open_data_slug'] is not None and validated_data['open_data_slug'].strip() == '': if 'open_data_slug' in validated_data and validated_data['open_data_slug'] is not None and validated_data[
'open_data_slug'].strip() == '':
validated_data['open_data_slug'] = None validated_data['open_data_slug'] = None
return super().create(validated_data) return super().create(validated_data)
def update(self, instance, validated_data): def update(self, instance, validated_data):
if 'open_data_slug' in validated_data and validated_data['open_data_slug'] is not None and validated_data['open_data_slug'].strip() == '': if 'open_data_slug' in validated_data and validated_data['open_data_slug'] is not None and validated_data[
'open_data_slug'].strip() == '':
validated_data['open_data_slug'] = None validated_data['open_data_slug'] = None
return super().update(instance, validated_data) return super().update(instance, validated_data)
@ -122,7 +125,8 @@ class CustomOnHandField(serializers.Field):
if not self.context["request"].user.is_authenticated: if not self.context["request"].user.is_authenticated:
return [] return []
shared_users = [] shared_users = []
if c := caches['default'].get(f'shopping_shared_users_{self.context["request"].space.id}_{self.context["request"].user.id}', None): if c := caches['default'].get(
f'shopping_shared_users_{self.context["request"].space.id}_{self.context["request"].user.id}', None):
shared_users = c shared_users = c
else: else:
try: try:
@ -312,7 +316,8 @@ class SpaceSerializer(WritableNestedModelSerializer):
'id', 'name', 'created_by', 'created_at', 'message', 'max_recipes', 'max_file_storage_mb', 'max_users', 'id', 'name', 'created_by', 'created_at', 'message', 'max_recipes', 'max_file_storage_mb', 'max_users',
'allow_sharing', 'demo', 'food_inherit', 'user_count', 'recipe_count', 'file_size_mb', 'allow_sharing', 'demo', 'food_inherit', 'user_count', 'recipe_count', 'file_size_mb',
'image', 'nav_logo', 'space_theme', 'custom_space_theme', 'nav_bg_color', 'nav_text_color', 'use_plural', 'image', 'nav_logo', 'space_theme', 'custom_space_theme', 'nav_bg_color', 'nav_text_color', 'use_plural',
'logo_color_32', 'logo_color_128', 'logo_color_144', 'logo_color_180', 'logo_color_192', 'logo_color_512', 'logo_color_svg',) 'logo_color_32', 'logo_color_128', 'logo_color_144', 'logo_color_180', 'logo_color_192', 'logo_color_512',
'logo_color_svg',)
read_only_fields = ( read_only_fields = (
'id', 'created_by', 'created_at', 'max_recipes', 'max_file_storage_mb', 'max_users', 'allow_sharing', 'id', 'created_by', 'created_at', 'max_recipes', 'max_file_storage_mb', 'max_users', 'allow_sharing',
'demo',) 'demo',)
@ -332,7 +337,8 @@ class UserSpaceSerializer(WritableNestedModelSerializer):
class Meta: class Meta:
model = UserSpace model = UserSpace
fields = ('id', 'user', 'space', 'groups', 'active', 'internal_note', 'invite_link', 'created_at', 'updated_at',) fields = (
'id', 'user', 'space', 'groups', 'active', 'internal_note', 'invite_link', 'created_at', 'updated_at',)
read_only_fields = ('id', 'invite_link', 'created_at', 'updated_at', 'space') read_only_fields = ('id', 'invite_link', 'created_at', 'updated_at', 'space')
@ -348,7 +354,8 @@ class MealTypeSerializer(SpacedModelSerializer, WritableNestedModelSerializer):
validated_data['name'] = validated_data['name'].strip() validated_data['name'] = validated_data['name'].strip()
space = validated_data.pop('space', self.context['request'].space) space = validated_data.pop('space', self.context['request'].space)
validated_data['created_by'] = self.context['request'].user validated_data['created_by'] = self.context['request'].user
obj, created = MealType.objects.get_or_create(name__iexact=validated_data['name'], space=space, defaults=validated_data) obj, created = MealType.objects.get_or_create(name__iexact=validated_data['name'], space=space,
defaults=validated_data)
return obj return obj
class Meta: class Meta:
@ -382,13 +389,15 @@ class UserPreferenceSerializer(WritableNestedModelSerializer):
class Meta: class Meta:
model = UserPreference model = UserPreference
fields = ( fields = (
'user', 'image', 'theme', 'nav_bg_color', 'nav_text_color', 'nav_show_logo', 'default_unit', 'default_page', 'use_fractions', 'use_kj', 'user', 'image', 'theme', 'nav_bg_color', 'nav_text_color', 'nav_show_logo', 'default_unit', 'default_page',
'use_fractions', 'use_kj',
'plan_share', 'nav_sticky', 'plan_share', 'nav_sticky',
'ingredient_decimals', 'comments', 'shopping_auto_sync', 'mealplan_autoadd_shopping', 'ingredient_decimals', 'comments', 'shopping_auto_sync', 'mealplan_autoadd_shopping',
'food_inherit_default', 'default_delay', 'food_inherit_default', 'default_delay',
'mealplan_autoinclude_related', 'mealplan_autoexclude_onhand', 'shopping_share', 'shopping_recent_days', 'mealplan_autoinclude_related', 'mealplan_autoexclude_onhand', 'shopping_share', 'shopping_recent_days',
'csv_delim', 'csv_prefix', 'csv_delim', 'csv_prefix',
'filter_to_supermarket', 'shopping_add_onhand', 'left_handed', 'show_step_ingredients', 'food_children_exist' 'filter_to_supermarket', 'shopping_add_onhand', 'left_handed', 'show_step_ingredients',
'food_children_exist'
) )
@ -477,10 +486,13 @@ class UnitSerializer(UniqueFieldsMixin, ExtendedRecipeMixin, OpenDataModelMixin)
if x := validated_data.get('name', None): if x := validated_data.get('name', None):
validated_data['plural_name'] = x.strip() validated_data['plural_name'] = x.strip()
if unit := Unit.objects.filter(Q(name__iexact=validated_data['name']) | Q(plural_name__iexact=validated_data['name']), space=space).first(): if unit := Unit.objects.filter(
Q(name__iexact=validated_data['name']) | Q(plural_name__iexact=validated_data['name']),
space=space).first():
return unit return unit
obj, created = Unit.objects.get_or_create(name__iexact=validated_data['name'], space=space, defaults=validated_data) obj, created = Unit.objects.get_or_create(name__iexact=validated_data['name'], space=space,
defaults=validated_data)
return obj return obj
def update(self, instance, validated_data): def update(self, instance, validated_data):
@ -500,7 +512,8 @@ class SupermarketCategorySerializer(UniqueFieldsMixin, WritableNestedModelSerial
def create(self, validated_data): def create(self, validated_data):
validated_data['name'] = validated_data['name'].strip() validated_data['name'] = validated_data['name'].strip()
space = validated_data.pop('space', self.context['request'].space) space = validated_data.pop('space', self.context['request'].space)
obj, created = SupermarketCategory.objects.get_or_create(name__iexact=validated_data['name'], space=space, defaults=validated_data) obj, created = SupermarketCategory.objects.get_or_create(name__iexact=validated_data['name'], space=space,
defaults=validated_data)
return obj return obj
def update(self, instance, validated_data): def update(self, instance, validated_data):
@ -525,7 +538,8 @@ class SupermarketSerializer(UniqueFieldsMixin, SpacedModelSerializer, OpenDataMo
def create(self, validated_data): def create(self, validated_data):
validated_data['name'] = validated_data['name'].strip() validated_data['name'] = validated_data['name'].strip()
space = validated_data.pop('space', self.context['request'].space) space = validated_data.pop('space', self.context['request'].space)
obj, created = Supermarket.objects.get_or_create(name__iexact=validated_data['name'], space=space, defaults=validated_data) obj, created = Supermarket.objects.get_or_create(name__iexact=validated_data['name'], space=space,
defaults=validated_data)
return obj return obj
class Meta: class Meta:
@ -540,7 +554,8 @@ class PropertyTypeSerializer(OpenDataModelMixin, WritableNestedModelSerializer,
def create(self, validated_data): def create(self, validated_data):
validated_data['name'] = validated_data['name'].strip() validated_data['name'] = validated_data['name'].strip()
space = validated_data.pop('space', self.context['request'].space) space = validated_data.pop('space', self.context['request'].space)
obj, created = PropertyType.objects.get_or_create(name__iexact=validated_data['name'], space=space, defaults=validated_data) obj, created = PropertyType.objects.get_or_create(name__iexact=validated_data['name'], space=space,
defaults=validated_data)
return obj return obj
class Meta: class Meta:
@ -665,12 +680,14 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedR
properties = validated_data.pop('properties', None) properties = validated_data.pop('properties', None)
obj, created = Food.objects.get_or_create(name=name, plural_name=plural_name, space=space, properties_food_unit=properties_food_unit, obj, created = Food.objects.get_or_create(name=name, plural_name=plural_name, space=space,
properties_food_unit=properties_food_unit,
defaults=validated_data) defaults=validated_data)
if properties and len(properties) > 0: if properties and len(properties) > 0:
for p in properties: for p in properties:
obj.properties.add(Property.objects.create(property_type_id=p['property_type']['id'], property_amount=p['property_amount'], space=space)) obj.properties.add(Property.objects.create(property_type_id=p['property_type']['id'],
property_amount=p['property_amount'], space=space))
return obj return obj
@ -702,7 +719,8 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedR
'properties', 'properties_food_amount', 'properties_food_unit', 'fdc_id', 'properties', 'properties_food_amount', 'properties_food_unit', 'fdc_id',
'food_onhand', 'supermarket_category', 'food_onhand', 'supermarket_category',
'image', 'parent', 'numchild', 'numrecipe', 'inherit_fields', 'full_name', 'ignore_shopping', 'image', 'parent', 'numchild', 'numrecipe', 'inherit_fields', 'full_name', 'ignore_shopping',
'substitute', 'substitute_siblings', 'substitute_children', 'substitute_onhand', 'child_inherit_fields', 'open_data_slug', 'substitute', 'substitute_siblings', 'substitute_children', 'substitute_onhand', 'child_inherit_fields',
'open_data_slug',
) )
read_only_fields = ('id', 'numchild', 'parent', 'image', 'numrecipe') read_only_fields = ('id', 'numchild', 'parent', 'image', 'numrecipe')
@ -726,7 +744,8 @@ class IngredientSimpleSerializer(WritableNestedModelSerializer):
uch = UnitConversionHelper(self.context['request'].space) uch = UnitConversionHelper(self.context['request'].space)
conversions = [] conversions = []
for c in uch.get_conversions(obj): for c in uch.get_conversions(obj):
conversions.append({'food': c.food.name, 'unit': c.unit.name, 'amount': c.amount}) # TODO do formatting in helper conversions.append(
{'food': c.food.name, 'unit': c.unit.name, 'amount': c.amount}) # TODO do formatting in helper
return conversions return conversions
else: else:
return [] return []
@ -828,7 +847,8 @@ class UnitConversionSerializer(WritableNestedModelSerializer, OpenDataModelMixin
class Meta: class Meta:
model = UnitConversion model = UnitConversion
fields = ('id', 'name', 'base_amount', 'base_unit', 'converted_amount', 'converted_unit', 'food', 'open_data_slug') fields = (
'id', 'name', 'base_amount', 'base_unit', 'converted_amount', 'converted_unit', 'food', 'open_data_slug')
class NutritionInformationSerializer(serializers.ModelSerializer): class NutritionInformationSerializer(serializers.ModelSerializer):
@ -898,7 +918,8 @@ class RecipeSerializer(RecipeBaseSerializer):
fields = ( fields = (
'id', 'name', 'description', 'image', 'keywords', 'steps', 'working_time', 'id', 'name', 'description', 'image', 'keywords', 'steps', 'working_time',
'waiting_time', 'created_by', 'created_at', 'updated_at', 'source_url', 'waiting_time', 'created_by', 'created_at', 'updated_at', 'source_url',
'internal', 'show_ingredient_overview', 'nutrition', 'properties', 'food_properties', 'servings', 'file_path', 'servings_text', 'rating', 'internal', 'show_ingredient_overview', 'nutrition', 'properties', 'food_properties', 'servings',
'file_path', 'servings_text', 'rating',
'last_cooked', 'last_cooked',
'private', 'shared', 'private', 'shared',
) )
@ -977,7 +998,8 @@ class RecipeBookEntrySerializer(serializers.ModelSerializer):
def create(self, validated_data): def create(self, validated_data):
book = validated_data['book'] book = validated_data['book']
recipe = validated_data['recipe'] recipe = validated_data['recipe']
if not book.get_owner() == self.context['request'].user and not self.context['request'].user in book.get_shared(): if not book.get_owner() == self.context['request'].user and not self.context[
'request'].user in book.get_shared():
raise NotFound(detail=None, code=None) raise NotFound(detail=None, code=None)
obj, created = RecipeBookEntry.objects.get_or_create(book=book, recipe=recipe) obj, created = RecipeBookEntry.objects.get_or_create(book=book, recipe=recipe)
return obj return obj
@ -1066,7 +1088,8 @@ class ShoppingListRecipeSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = ShoppingListRecipe model = ShoppingListRecipe
fields = ('id', 'recipe_name', 'name', 'recipe', 'mealplan', 'servings', 'mealplan_note', 'mealplan_from_date', 'mealplan_type') fields = ('id', 'recipe_name', 'name', 'recipe', 'mealplan', 'servings', 'mealplan_note', 'mealplan_from_date',
'mealplan_type')
read_only_fields = ('id',) read_only_fields = ('id',)
@ -1132,6 +1155,11 @@ class ShoppingListEntrySerializer(WritableNestedModelSerializer):
read_only_fields = ('id', 'created_by', 'created_at',) read_only_fields = ('id', 'created_by', 'created_at',)
class ShoppingListEntryBulkSerializer(serializers.Serializer):
ids = serializers.ListField()
checked = serializers.BooleanField()
# TODO deprecate # TODO deprecate
class ShoppingListEntryCheckedSerializer(serializers.ModelSerializer): class ShoppingListEntryCheckedSerializer(serializers.ModelSerializer):
class Meta: class Meta:
@ -1284,7 +1312,8 @@ class InviteLinkSerializer(WritableNestedModelSerializer):
class Meta: class Meta:
model = InviteLink model = InviteLink
fields = ( fields = (
'id', 'uuid', 'email', 'group', 'valid_until', 'used_by', 'reusable', 'internal_note', 'created_by', 'created_at',) 'id', 'uuid', 'email', 'group', 'valid_until', 'used_by', 'reusable', 'internal_note', 'created_by',
'created_at',)
read_only_fields = ('id', 'uuid', 'created_by', 'created_at',) read_only_fields = ('id', 'uuid', 'created_by', 'created_at',)

View File

@ -103,7 +103,8 @@ from cookbook.serializer import (AccessTokenSerializer, AutomationSerializer,
SupermarketCategorySerializer, SupermarketSerializer, SupermarketCategorySerializer, SupermarketSerializer,
SyncLogSerializer, SyncSerializer, UnitConversionSerializer, SyncLogSerializer, SyncSerializer, UnitConversionSerializer,
UnitSerializer, UserFileSerializer, UserPreferenceSerializer, UnitSerializer, UserFileSerializer, UserPreferenceSerializer,
UserSerializer, UserSpaceSerializer, ViewLogSerializer) UserSerializer, UserSpaceSerializer, ViewLogSerializer,
ShoppingListEntryBulkSerializer)
from cookbook.views.import_export import get_integration from cookbook.views.import_export import get_integration
from recipes import settings from recipes import settings
from recipes.settings import FDC_API_KEY, DRF_THROTTLE_RECIPE_URL_IMPORT from recipes.settings import FDC_API_KEY, DRF_THROTTLE_RECIPE_URL_IMPORT
@ -1175,6 +1176,22 @@ class ShoppingListEntryViewSet(viewsets.ModelViewSet):
# TODO once old shopping list is removed this needs updated to sharing users in preferences # TODO once old shopping list is removed this needs updated to sharing users in preferences
return self.queryset return self.queryset
@decorators.action(
detail=False,
methods=['POST'],
serializer_class=ShoppingListEntryBulkSerializer,
)
def bulk(self, request):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
ShoppingListEntry.objects.filter(
Q(created_by=self.request.user)
| Q(shoppinglist__shared=self.request.user)
| Q(created_by__in=list(self.request.user.get_shopping_share()))
).filter(space=request.space, id__in=serializer.validated_data['ids']).update(checked=serializer.validated_data['checked'])
return Response(serializer.data)
else:
return Response(serializer.errors, 400)
# TODO deprecate # TODO deprecate
class ShoppingListViewSet(viewsets.ModelViewSet): class ShoppingListViewSet(viewsets.ModelViewSet):

View File

@ -299,10 +299,19 @@ export const useShoppingListStore = defineStore(_STORE_ID, {
this.registerChange((checked ? 'CHECKED' : 'UNCHECKED'), entries) this.registerChange((checked ? 'CHECKED' : 'UNCHECKED'), entries)
} }
let entry_id_list = []
for (let i in entries) { for (let i in entries) {
this.entries[i].checked = checked Vue.set(this.entries[i], 'checked', checked)
this.updateObject(this.entries[i]) Vue.set(this.entries[i], 'update_at', moment().format())
entry_id_list.push(i)
} }
let apiClient = new ApiApiFactory()
apiClient.bulkShoppingListEntry({'ids': entry_id_list, 'checked': checked}).then((r) => {
}).catch((err) => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
})
}, },
/** /**
* function to handle user "delaying" and "undelaying" shopping entries * function to handle user "delaying" and "undelaying" shopping entries

File diff suppressed because it is too large Load Diff