diff --git a/cookbook/admin.py b/cookbook/admin.py index 96ebad971..402d6f3f4 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, ConnectorConfig) @@ -369,13 +369,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/forms.py b/cookbook/forms.py index 872bfa01e..c0a670613 100644 --- a/cookbook/forms.py +++ b/cookbook/forms.py @@ -160,6 +160,7 @@ class StorageForm(forms.ModelForm): help_texts = {'url': _('Leave empty for dropbox and enter only base url for nextcloud (/remote.php/webdav/ is added automatically)'), } + class ConnectorConfigForm(forms.ModelForm): enabled = forms.BooleanField( help_text="Is the connector enabled", @@ -205,24 +206,6 @@ class ConnectorConfigForm(forms.ModelForm): } -# TODO: Deprecate -# class RecipeBookEntryForm(forms.ModelForm): -# prefix = 'bookmark' - -# def __init__(self, *args, **kwargs): -# space = kwargs.pop('space') -# super().__init__(*args, **kwargs) -# self.fields['book'].queryset = RecipeBook.objects.filter(space=space).all() - -# class Meta: -# model = RecipeBookEntry -# fields = ('book',) - -# field_classes = { -# 'book': SafeModelChoiceField, -# } - - class SyncForm(forms.ModelForm): def __init__(self, *args, **kwargs): @@ -239,7 +222,6 @@ class SyncForm(forms.ModelForm): labels = {'storage': _('Storage'), 'path': _('Path'), 'active': _('Active')} -# TODO deprecate class BatchEditForm(forms.Form): search = forms.CharField(label=_('Search String')) keywords = forms.ModelMultipleChoiceField(queryset=Keyword.objects.none(), required=False, widget=MultiSelectWidget) @@ -373,73 +355,3 @@ class SearchPreferenceForm(forms.ModelForm): 'search': SelectWidget, 'unaccent': MultiSelectWidget, 'icontains': MultiSelectWidget, 'istartswith': MultiSelectWidget, 'trigram': MultiSelectWidget, 'fulltext': MultiSelectWidget, } - - -# class ShoppingPreferenceForm(forms.ModelForm): -# prefix = 'shopping' - -# class Meta: -# model = UserPreference - -# fields = ( -# 'shopping_share', 'shopping_auto_sync', 'mealplan_autoadd_shopping', 'mealplan_autoexclude_onhand', -# 'mealplan_autoinclude_related', 'shopping_add_onhand', 'default_delay', 'filter_to_supermarket', 'shopping_recent_days', 'csv_delim', 'csv_prefix' -# ) - -# help_texts = { -# 'shopping_share': _('Users will see all items you add to your shopping list. They must add you to see items on their list.'), -# 'shopping_auto_sync': _( -# '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 might use a little bit ' -# 'of mobile data. If lower than instance limit it is reset when saving.' -# ), -# 'mealplan_autoadd_shopping': _('Automatically add meal plan ingredients to shopping list.'), -# 'mealplan_autoinclude_related': _('When adding a meal plan to the shopping list (manually or automatically), 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.'), -# 'filter_to_supermarket': _('Filter shopping list to only include supermarket categories.'), -# 'shopping_recent_days': _('Days of recent shopping list entries to display.'), -# 'shopping_add_onhand': _("Mark food 'On Hand' when checked off shopping list."), -# 'csv_delim': _('Delimiter to use for CSV exports.'), -# 'csv_prefix': _('Prefix to add when copying list to the clipboard.'), - -# } -# labels = { -# 'shopping_share': _('Share Shopping List'), -# 'shopping_auto_sync': _('Autosync'), -# 'mealplan_autoadd_shopping': _('Auto Add Meal Plan'), -# 'mealplan_autoexclude_onhand': _('Exclude On Hand'), -# 'mealplan_autoinclude_related': _('Include Related'), -# 'default_delay': _('Default Delay Hours'), -# 'filter_to_supermarket': _('Filter to Supermarket'), -# 'shopping_recent_days': _('Recent Days'), -# 'csv_delim': _('CSV Delimiter'), -# "csv_prefix_label": _("List Prefix"), -# 'shopping_add_onhand': _("Auto On Hand"), -# } - -# widgets = { -# 'shopping_share': MultiSelectWidget -# } - -# class SpacePreferenceForm(forms.ModelForm): -# prefix = 'space' -# reset_food_inherit = forms.BooleanField(label=_("Reset Food Inheritance"), initial=False, required=False, -# help_text=_("Reset all food to inherit the fields configured.")) - -# def __init__(self, *args, **kwargs): -# super().__init__(*args, **kwargs) # populates the post -# self.fields['food_inherit'].queryset = Food.inheritable_fields - -# class Meta: -# model = Space - -# fields = ('food_inherit', 'reset_food_inherit',) - -# help_texts = { -# 'food_inherit': _('Fields on food that should be inherited by default.'), -# 'use_plural': _('Use the plural form for units and food inside this space.'), -# } - -# widgets = { -# 'food_inherit': MultiSelectWidget -# } diff --git a/cookbook/helper/__init__.py b/cookbook/helper/__init__.py index c1cb37885..7a7792027 100644 --- a/cookbook/helper/__init__.py +++ b/cookbook/helper/__init__.py @@ -1,6 +1,4 @@ -import cookbook.helper.dal from cookbook.helper.AllAuthCustomAdapter import AllAuthCustomAdapter __all__ = [ - 'dal', ] diff --git a/cookbook/helper/dal.py b/cookbook/helper/dal.py deleted file mode 100644 index 879279c88..000000000 --- a/cookbook/helper/dal.py +++ /dev/null @@ -1,34 +0,0 @@ -from cookbook.models import Food, Keyword, Recipe, Unit - -from dal import autocomplete - - -class BaseAutocomplete(autocomplete.Select2QuerySetView): - model = None - - def get_queryset(self): - if not self.request.user.is_authenticated: - return self.model.objects.none() - - qs = self.model.objects.filter(space=self.request.space).all() - - if self.q: - qs = qs.filter(name__icontains=self.q) - - return qs - - -class KeywordAutocomplete(BaseAutocomplete): - model = Keyword - - -class IngredientsAutocomplete(BaseAutocomplete): - model = Food - - -class RecipeAutocomplete(BaseAutocomplete): - model = Recipe - - -class UnitAutocomplete(BaseAutocomplete): - model = Unit 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/locale/sv/LC_MESSAGES/django.po b/cookbook/locale/sv/LC_MESSAGES/django.po index 5137fb7eb..6fec1dc0e 100644 --- a/cookbook/locale/sv/LC_MESSAGES/django.po +++ b/cookbook/locale/sv/LC_MESSAGES/django.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-04-11 15:09+0200\n" -"PO-Revision-Date: 2022-04-17 00:31+0000\n" -"Last-Translator: Oskar Stenberg <01ste02@gmail.com>\n" +"PO-Revision-Date: 2024-02-27 12:19+0000\n" +"Last-Translator: Lukas Åteg \n" "Language-Team: Swedish \n" "Language: sv\n" @@ -17,7 +17,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.10.1\n" +"X-Generator: Weblate 4.15\n" #: .\cookbook\filters.py:23 .\cookbook\templates\base.html:91 #: .\cookbook\templates\forms\edit_internal_recipe.html:219 @@ -1812,7 +1812,7 @@ msgstr "Google ld+json info" #: .\cookbook\templates\url_import.html:268 msgid "GitHub Issues" -msgstr "GitHub Issues" +msgstr "GitHub Problem" #: .\cookbook\templates\url_import.html:270 msgid "Recipe Markup Specification" @@ -1852,7 +1852,7 @@ msgstr "Kunde inte tolka korrekt..." msgid "Batch edit done. %(count)d recipe was updated." msgid_plural "Batch edit done. %(count)d Recipes where updated." msgstr[0] "Batchredigering klar. %(count)d recept uppdaterades." -msgstr[1] "Batchredigering klar. %(count)d recept uppdaterades." +msgstr[1] "Batchredigering klar. %(count)d recepten uppdaterades." #: .\cookbook\views\delete.py:72 msgid "Monitor" 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 4b8e51726..aab4ec554 100644 --- a/cookbook/models.py +++ b/cookbook/models.py @@ -369,9 +369,8 @@ class Space(ExportModelOperationsMixin('space'), models.Model): Storage.objects.filter(space=self).delete() ConnectorConfig.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() @@ -1195,7 +1194,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 @@ -1218,53 +1220,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 47e74796e..42cc99357 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, ConnectorConfig) @@ -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') @@ -877,8 +873,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): @@ -1198,37 +1193,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 a20696a38..5a7e1650a 100644 --- a/cookbook/templates/base.html +++ b/cookbook/templates/base.html @@ -114,7 +114,7 @@ class="fas fa-fw fa-calendar"> {% trans 'Meal-Plan' %}