diff --git a/cookbook/admin.py b/cookbook/admin.py index fc148afe5..4c862c9f6 100644 --- a/cookbook/admin.py +++ b/cookbook/admin.py @@ -13,7 +13,7 @@ from cookbook.managers import DICTIONARY from .models import (BookmarkletImport, Comment, CookLog, Food, ImportLog, Ingredient, InviteLink, Keyword, MealPlan, MealType, NutritionInformation, Property, PropertyType, Recipe, RecipeBook, RecipeBookEntry, RecipeImport, SearchPreference, ShareLink, - ShoppingList, ShoppingListEntry, ShoppingListRecipe, Space, Step, Storage, + ShoppingListEntry, ShoppingListRecipe, Space, Step, Storage, Supermarket, SupermarketCategory, SupermarketCategoryRelation, Sync, SyncLog, TelegramBot, Unit, UnitConversion, UserFile, UserPreference, UserSpace, ViewLog) @@ -361,13 +361,6 @@ class ShoppingListEntryAdmin(admin.ModelAdmin): admin.site.register(ShoppingListEntry, ShoppingListEntryAdmin) -# class ShoppingListAdmin(admin.ModelAdmin): -# list_display = ('id', 'created_by', 'created_at') - - -# admin.site.register(ShoppingList, ShoppingListAdmin) - - class ShareLinkAdmin(admin.ModelAdmin): list_display = ('recipe', 'created_by', 'uuid', 'created_at',) diff --git a/cookbook/helper/permission_helper.py b/cookbook/helper/permission_helper.py index 889d75055..b6e49771f 100644 --- a/cookbook/helper/permission_helper.py +++ b/cookbook/helper/permission_helper.py @@ -75,7 +75,7 @@ def is_object_owner(user, obj): if not user.is_authenticated: return False try: - return obj.get_owner() == user + return obj.get_owner() == 'orphan' or obj.get_owner() == user except Exception: return False diff --git a/cookbook/helper/shopping_helper.py b/cookbook/helper/shopping_helper.py index c7772a078..c4c5659b2 100644 --- a/cookbook/helper/shopping_helper.py +++ b/cookbook/helper/shopping_helper.py @@ -1,9 +1,8 @@ -from datetime import timedelta + from decimal import Decimal from django.db.models import F, OuterRef, Q, Subquery, Value from django.db.models.functions import Coalesce -from django.utils import timezone from django.utils.translation import gettext as _ from cookbook.models import (Ingredient, MealPlan, Recipe, ShoppingListEntry, ShoppingListRecipe, @@ -76,10 +75,8 @@ class RecipeShoppingEditor(): @staticmethod def get_shopping_list_recipe(id, user, space): - return ShoppingListRecipe.objects.filter(id=id).filter(Q(shoppinglist__space=space) | Q(entries__space=space)).filter( - Q(shoppinglist__created_by=user) - | Q(shoppinglist__shared=user) - | Q(entries__created_by=user) + return ShoppingListRecipe.objects.filter(id=id).filter(entries__space=space).filter( + Q(entries__created_by=user) | Q(entries__created_by__in=list(user.get_shopping_share())) ).prefetch_related('entries').first() diff --git a/cookbook/migrations/0214_delete_shopping_model.py b/cookbook/migrations/0214_delete_shopping_model.py new file mode 100644 index 000000000..74941a830 --- /dev/null +++ b/cookbook/migrations/0214_delete_shopping_model.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.10 on 2024-02-19 20:21 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ( + "cookbook", + "0213_remove_property_property_unique_import_food_per_space_and_more", + ), + ] + + operations = [ + migrations.DeleteModel( + name="ShoppingList", + ), + ] diff --git a/cookbook/models.py b/cookbook/models.py index fc1a62ad1..5786599ca 100644 --- a/cookbook/models.py +++ b/cookbook/models.py @@ -368,9 +368,8 @@ class Space(ExportModelOperationsMixin('space'), models.Model): Sync.objects.filter(space=self).delete() Storage.objects.filter(space=self).delete() - ShoppingListEntry.objects.filter(shoppinglist__space=self).delete() - ShoppingListRecipe.objects.filter(shoppinglist__space=self).delete() - ShoppingList.objects.filter(space=self).delete() + ShoppingListEntry.objects.filter(space=self).delete() + ShoppingListRecipe.objects.filter(recipe__space=self).delete() SupermarketCategoryRelation.objects.filter(supermarket__space=self).delete() SupermarketCategory.objects.filter(space=self).delete() @@ -1169,7 +1168,10 @@ class ShoppingListRecipe(ExportModelOperationsMixin('shopping_list_recipe'), mod def get_owner(self): try: - return getattr(self.entries.first(), 'created_by', None) or getattr(self.shoppinglist_set.first(), 'created_by', None) + if not self.entries.exists(): + return 'orphan' + else: + return getattr(self.entries.first(), 'created_by', None) except AttributeError: return None @@ -1192,53 +1194,19 @@ class ShoppingListEntry(ExportModelOperationsMixin('shopping_list_entry'), model space = models.ForeignKey(Space, on_delete=models.CASCADE) objects = ScopedManager(space='space') - @staticmethod - def get_space_key(): - return 'shoppinglist', 'space' - - def get_space(self): - return self.shoppinglist_set.first().space - def __str__(self): return f'Shopping list entry {self.id}' def get_shared(self): - try: - return self.shoppinglist_set.first().shared.all() - except AttributeError: - return self.created_by.userpreference.shopping_share.all() + return self.created_by.userpreference.shopping_share.all() def get_owner(self): try: - return self.created_by or self.shoppinglist_set.first().created_by + return self.created_by except AttributeError: return None -class ShoppingList(ExportModelOperationsMixin('shopping_list'), models.Model, PermissionModelMixin): - uuid = models.UUIDField(default=uuid.uuid4) - note = models.TextField(blank=True, null=True) - recipes = models.ManyToManyField(ShoppingListRecipe, blank=True) - entries = models.ManyToManyField(ShoppingListEntry, blank=True) - shared = models.ManyToManyField(User, blank=True, related_name='list_share') - supermarket = models.ForeignKey(Supermarket, null=True, blank=True, on_delete=models.SET_NULL) - finished = models.BooleanField(default=False) - created_by = models.ForeignKey(User, on_delete=models.CASCADE) - created_at = models.DateTimeField(auto_now_add=True) - - space = models.ForeignKey(Space, on_delete=models.CASCADE) - objects = ScopedManager(space='space') - - def __str__(self): - return f'Shopping list {self.id}' - - def get_shared(self): - try: - return self.shared.all() or self.created_by.userpreference.shopping_share.all() - except AttributeError: - return [] - - class ShareLink(ExportModelOperationsMixin('share_link'), models.Model, PermissionModelMixin): recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE) uuid = models.UUIDField(default=uuid.uuid4) diff --git a/cookbook/serializer.py b/cookbook/serializer.py index 8446e3fb5..06e80129e 100644 --- a/cookbook/serializer.py +++ b/cookbook/serializer.py @@ -1,4 +1,3 @@ -import traceback import uuid from datetime import datetime, timedelta from decimal import Decimal @@ -31,7 +30,7 @@ from cookbook.models import (Automation, BookmarkletImport, Comment, CookLog, Cu ExportLog, Food, FoodInheritField, ImportLog, Ingredient, InviteLink, Keyword, MealPlan, MealType, NutritionInformation, Property, PropertyType, Recipe, RecipeBook, RecipeBookEntry, RecipeImport, - ShareLink, ShoppingList, ShoppingListEntry, ShoppingListRecipe, Space, + ShareLink, ShoppingListEntry, ShoppingListRecipe, Space, Step, Storage, Supermarket, SupermarketCategory, SupermarketCategoryRelation, Sync, SyncLog, Unit, UnitConversion, UserFile, UserPreference, UserSpace, ViewLog) @@ -82,14 +81,12 @@ class ExtendedRecipeMixin(serializers.ModelSerializer): class OpenDataModelMixin(serializers.ModelSerializer): 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 return super().create(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 return super().update(instance, validated_data) @@ -336,8 +333,7 @@ class UserSpaceSerializer(WritableNestedModelSerializer): class Meta: 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') @@ -845,8 +841,7 @@ class UnitConversionSerializer(WritableNestedModelSerializer, OpenDataModelMixin class Meta: 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): @@ -1150,7 +1145,7 @@ class ShoppingListEntrySerializer(WritableNestedModelSerializer): 'recipe_mealplan', 'created_by', 'created_at', 'updated_at', 'completed_at', 'delay_until' ) - read_only_fields = ('id', 'created_by', 'created_at','updated_at',) + read_only_fields = ('id', 'created_by', 'created_at', 'updated_at',) class ShoppingListEntryBulkSerializer(serializers.Serializer): @@ -1165,37 +1160,6 @@ class ShoppingListEntryCheckedSerializer(serializers.ModelSerializer): fields = ('id', 'checked') -# TODO deprecate -class ShoppingListSerializer(WritableNestedModelSerializer): - recipes = ShoppingListRecipeSerializer(many=True, allow_null=True) - entries = ShoppingListEntrySerializer(many=True, allow_null=True) - shared = UserSerializer(many=True) - supermarket = SupermarketSerializer(allow_null=True) - - def create(self, validated_data): - validated_data['space'] = self.context['request'].space - validated_data['created_by'] = self.context['request'].user - return super().create(validated_data) - - class Meta: - model = ShoppingList - fields = ( - 'id', 'uuid', 'note', 'recipes', 'entries', - 'shared', 'finished', 'supermarket', 'created_by', 'created_at' - ) - read_only_fields = ('id', 'created_by',) - - -# TODO deprecate -class ShoppingListAutoSyncSerializer(WritableNestedModelSerializer): - entries = ShoppingListEntryCheckedSerializer(many=True, allow_null=True) - - class Meta: - model = ShoppingList - fields = ('id', 'entries',) - read_only_fields = ('id',) - - class ShareLinkSerializer(SpacedModelSerializer): class Meta: model = ShareLink diff --git a/cookbook/templates/base.html b/cookbook/templates/base.html index 14b3a18f9..ba5b4ecf7 100644 --- a/cookbook/templates/base.html +++ b/cookbook/templates/base.html @@ -114,7 +114,7 @@ class="fas fa-fw fa-calendar"> {% trans 'Meal-Plan' %}