From 58e70c982ea3a5010bb49ab1c1fcc92202d3c54d Mon Sep 17 00:00:00 2001 From: vabene1111 Date: Tue, 21 Nov 2023 22:34:17 +0100 Subject: [PATCH 1/2] WIP --- cookbook/migrations/0204_space_favicon.py | 19 +++++ cookbook/models.py | 82 +++++++++++++------ cookbook/serializer.py | 72 ++++++++++------ cookbook/templates/base.html | 21 +++-- cookbook/templatetags/theming_tags.py | 8 ++ .../apps/SpaceManageView/SpaceManageView.vue | 6 ++ 6 files changed, 149 insertions(+), 59 deletions(-) create mode 100644 cookbook/migrations/0204_space_favicon.py diff --git a/cookbook/migrations/0204_space_favicon.py b/cookbook/migrations/0204_space_favicon.py new file mode 100644 index 000000000..c34bd86f7 --- /dev/null +++ b/cookbook/migrations/0204_space_favicon.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.5 on 2023-11-21 21:18 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('cookbook', '0203_alter_unique_contstraints'), + ] + + operations = [ + migrations.AddField( + model_name='space', + name='favicon', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='space_favicon', to='cookbook.userfile'), + ), + ] diff --git a/cookbook/models.py b/cookbook/models.py index caf84d790..abeaacd98 100644 --- a/cookbook/models.py +++ b/cookbook/models.py @@ -95,7 +95,8 @@ class TreeManager(MP_NodeManager): if defaults: kwargs = {**kwargs, **defaults} # ManyToMany fields can't be set this way, so pop them out to save for later - fields = [field.name for field in self.model._meta.get_fields() if issubclass(type(field), ManyToManyField)] + fields = [field.name for field in self.model._meta.get_fields() if + issubclass(type(field), ManyToManyField)] many_to_many = {field: kwargs.pop(field) for field in list(kwargs) if field in fields} obj = self.model.add_root(**kwargs) for field in many_to_many: @@ -253,11 +254,13 @@ class FoodInheritField(models.Model, PermissionModelMixin): class Space(ExportModelOperationsMixin('space'), models.Model): name = models.CharField(max_length=128, default='Default') image = models.ForeignKey("UserFile", on_delete=models.SET_NULL, null=True, blank=True, related_name='space_image') + favicon = models.ForeignKey("UserFile", on_delete=models.SET_NULL, null=True, blank=True, related_name='space_favicon') created_by = models.ForeignKey(User, on_delete=models.PROTECT, null=True) created_at = models.DateTimeField(auto_now_add=True) message = models.CharField(max_length=512, default='', blank=True) max_recipes = models.IntegerField(default=0) - max_file_storage_mb = models.IntegerField(default=0, help_text=_('Maximum file storage for space in MB. 0 for unlimited, -1 to disable file upload.')) + max_file_storage_mb = models.IntegerField(default=0, help_text=_( + 'Maximum file storage for space in MB. 0 for unlimited, -1 to disable file upload.')) max_users = models.IntegerField(default=0) use_plural = models.BooleanField(default=True) allow_sharing = models.BooleanField(default=True) @@ -471,7 +474,8 @@ class SupermarketCategory(models.Model, PermissionModelMixin): class Meta: constraints = [ models.UniqueConstraint(fields=['space', 'name'], name='smc_unique_name_per_space'), - models.UniqueConstraint(fields=['space', 'open_data_slug'], name='supermarket_category_unique_open_data_slug_per_space') + models.UniqueConstraint(fields=['space', 'open_data_slug'], + name='supermarket_category_unique_open_data_slug_per_space') ] @@ -490,7 +494,8 @@ class Supermarket(models.Model, PermissionModelMixin): class Meta: constraints = [ models.UniqueConstraint(fields=['space', 'name'], name='sm_unique_name_per_space'), - models.UniqueConstraint(fields=['space', 'open_data_slug'], name='supermarket_unique_open_data_slug_per_space') + models.UniqueConstraint(fields=['space', 'open_data_slug'], + name='supermarket_unique_open_data_slug_per_space') ] @@ -575,7 +580,8 @@ class Food(ExportModelOperationsMixin('food'), TreeModel, PermissionModelMixin): plural_name = models.CharField(max_length=128, null=True, blank=True, default=None) recipe = models.ForeignKey('Recipe', null=True, blank=True, on_delete=models.SET_NULL) url = models.CharField(max_length=1024, blank=True, null=True, default='') - supermarket_category = models.ForeignKey(SupermarketCategory, null=True, blank=True, on_delete=models.SET_NULL) # inherited field + supermarket_category = models.ForeignKey(SupermarketCategory, null=True, blank=True, + on_delete=models.SET_NULL) # inherited field ignore_shopping = models.BooleanField(default=False) # inherited field onhand_users = models.ManyToManyField(User, blank=True) description = models.TextField(default='', blank=True) @@ -589,8 +595,10 @@ class Food(ExportModelOperationsMixin('food'), TreeModel, PermissionModelMixin): properties_food_amount = models.DecimalField(default=100, max_digits=16, decimal_places=2, blank=True) properties_food_unit = models.ForeignKey(Unit, on_delete=models.PROTECT, blank=True, null=True) - preferred_unit = models.ForeignKey(Unit, on_delete=models.SET_NULL, null=True, blank=True, default=None, related_name='preferred_unit') - preferred_shopping_unit = models.ForeignKey(Unit, on_delete=models.SET_NULL, null=True, blank=True, default=None, related_name='preferred_shopping_unit') + preferred_unit = models.ForeignKey(Unit, on_delete=models.SET_NULL, null=True, blank=True, default=None, + related_name='preferred_unit') + preferred_shopping_unit = models.ForeignKey(Unit, on_delete=models.SET_NULL, null=True, blank=True, default=None, + related_name='preferred_shopping_unit') fdc_id = models.CharField(max_length=128, null=True, blank=True, default=None) open_data_slug = models.CharField(max_length=128, null=True, blank=True, default=None) @@ -602,7 +610,8 @@ class Food(ExportModelOperationsMixin('food'), TreeModel, PermissionModelMixin): def delete(self): if self.ingredient_set.all().exclude(step=None).count() > 0: - raise ProtectedError(self.name + _(" is part of a recipe step and cannot be deleted"), self.ingredient_set.all().exclude(step=None)) + raise ProtectedError(self.name + _(" is part of a recipe step and cannot be deleted"), + self.ingredient_set.all().exclude(step=None)) else: return super().delete() @@ -652,8 +661,12 @@ class Food(ExportModelOperationsMixin('food'), TreeModel, PermissionModelMixin): food.get_descendants().update(**{f"{field}": False}) else: # get food at root that have children that need updated - Food.include_descendants(queryset=Food.objects.filter(depth=1, numchild__gt=0, **{f"{field}": True}, space=space)).update(**{f"{field}": True}) - Food.include_descendants(queryset=Food.objects.filter(depth=1, numchild__gt=0, **{f"{field}": False}, space=space)).update(**{f"{field}": False}) + Food.include_descendants( + queryset=Food.objects.filter(depth=1, numchild__gt=0, **{f"{field}": True}, + space=space)).update(**{f"{field}": True}) + Food.include_descendants( + queryset=Food.objects.filter(depth=1, numchild__gt=0, **{f"{field}": False}, + space=space)).update(**{f"{field}": False}) if 'supermarket_category' in inherit: # when supermarket_category is null or blank assuming it is not set and not intended to be blank for all descedants @@ -661,7 +674,8 @@ class Food(ExportModelOperationsMixin('food'), TreeModel, PermissionModelMixin): food.get_descendants().update(supermarket_category=food.supermarket_category) elif food is None: # find top node that has category set - category_roots = Food.exclude_descendants(queryset=Food.objects.filter(supermarket_category__isnull=False, numchild__gt=0, space=space)) + category_roots = Food.exclude_descendants( + queryset=Food.objects.filter(supermarket_category__isnull=False, numchild__gt=0, space=space)) for root in category_roots: root.get_descendants().update(supermarket_category=root.supermarket_category) @@ -680,7 +694,8 @@ class UnitConversion(ExportModelOperationsMixin('unit_conversion'), models.Model base_amount = models.DecimalField(default=0, decimal_places=16, max_digits=32) base_unit = models.ForeignKey('Unit', on_delete=models.CASCADE, related_name='unit_conversion_base_relation') converted_amount = models.DecimalField(default=0, decimal_places=16, max_digits=32) - converted_unit = models.ForeignKey('Unit', on_delete=models.CASCADE, related_name='unit_conversion_converted_relation') + converted_unit = models.ForeignKey('Unit', on_delete=models.CASCADE, + related_name='unit_conversion_converted_relation') food = models.ForeignKey('Food', on_delete=models.CASCADE, null=True, blank=True) @@ -697,8 +712,10 @@ class UnitConversion(ExportModelOperationsMixin('unit_conversion'), models.Model class Meta: constraints = [ - models.UniqueConstraint(fields=['space', 'base_unit', 'converted_unit', 'food'], name='f_unique_conversion_per_space'), - models.UniqueConstraint(fields=['space', 'open_data_slug'], name='unit_conversion_unique_open_data_slug_per_space') + models.UniqueConstraint(fields=['space', 'base_unit', 'converted_unit', 'food'], + name='f_unique_conversion_per_space'), + models.UniqueConstraint(fields=['space', 'open_data_slug'], + name='unit_conversion_unique_open_data_slug_per_space') ] @@ -764,7 +781,8 @@ class PropertyType(models.Model, PermissionModelMixin): order = models.IntegerField(default=0) description = models.CharField(max_length=512, blank=True, null=True) category = models.CharField(max_length=64, choices=((NUTRITION, _('Nutrition')), (ALLERGEN, _('Allergen')), - (PRICE, _('Price')), (GOAL, _('Goal')), (OTHER, _('Other'))), null=True, blank=True) + (PRICE, _('Price')), (GOAL, _('Goal')), (OTHER, _('Other'))), + null=True, blank=True) open_data_slug = models.CharField(max_length=128, null=True, blank=True, default=None) # TODO show if empty property? @@ -779,7 +797,8 @@ class PropertyType(models.Model, PermissionModelMixin): class Meta: constraints = [ models.UniqueConstraint(fields=['space', 'name'], name='property_type_unique_name_per_space'), - models.UniqueConstraint(fields=['space', 'open_data_slug'], name='property_type_unique_open_data_slug_per_space') + models.UniqueConstraint(fields=['space', 'open_data_slug'], + name='property_type_unique_open_data_slug_per_space') ] ordering = ('order',) @@ -788,7 +807,8 @@ class Property(models.Model, PermissionModelMixin): property_amount = models.DecimalField(default=0, decimal_places=4, max_digits=32) property_type = models.ForeignKey(PropertyType, on_delete=models.PROTECT) - import_food_id = models.IntegerField(null=True, blank=True) # field to hold food id when importing properties from the open data project + import_food_id = models.IntegerField(null=True, + blank=True) # field to hold food id when importing properties from the open data project space = models.ForeignKey(Space, on_delete=models.CASCADE) objects = ScopedManager(space='space') @@ -798,7 +818,8 @@ class Property(models.Model, PermissionModelMixin): class Meta: constraints = [ - models.UniqueConstraint(fields=['space', 'property_type', 'import_food_id'], name='property_unique_import_food_per_space') + models.UniqueConstraint(fields=['space', 'property_type', 'import_food_id'], + name='property_unique_import_food_per_space') ] @@ -830,7 +851,8 @@ class NutritionInformation(models.Model, PermissionModelMixin): class RecipeManager(models.Manager.from_queryset(models.QuerySet)): def get_queryset(self): - return super(RecipeManager, self).get_queryset().annotate(rating=Avg('cooklog__rating')).annotate(last_cooked=Max('cooklog__created_at')) + return super(RecipeManager, self).get_queryset().annotate(rating=Avg('cooklog__rating')).annotate( + last_cooked=Max('cooklog__created_at')) class Recipe(ExportModelOperationsMixin('recipe'), models.Model, PermissionModelMixin): @@ -873,15 +895,19 @@ class Recipe(ExportModelOperationsMixin('recipe'), models.Model, PermissionModel # recipes for step recipe step_recipes = Q(id__in=self.steps.exclude(step_recipe=None).values_list('step_recipe')) # recipes for foods - food_recipes = Q(id__in=Food.objects.filter(ingredient__step__recipe=self).exclude(recipe=None).values_list('recipe')) + food_recipes = Q( + id__in=Food.objects.filter(ingredient__step__recipe=self).exclude(recipe=None).values_list('recipe')) related_recipes = Recipe.objects.filter(step_recipes | food_recipes) if levels == 1: return related_recipes # this can loop over multiple levels if you update the value of related_recipes at each step (maybe an array?) # for now keeping it at 2 levels max, should be sufficient in 99.9% of scenarios - sub_step_recipes = Q(id__in=Step.objects.filter(recipe__in=related_recipes.values_list('steps')).exclude(step_recipe=None).values_list('step_recipe')) - sub_food_recipes = Q(id__in=Food.objects.filter(ingredient__step__recipe__in=related_recipes).exclude(recipe=None).values_list('recipe')) + sub_step_recipes = Q(id__in=Step.objects.filter(recipe__in=related_recipes.values_list('steps')).exclude( + step_recipe=None).values_list('step_recipe')) + sub_food_recipes = Q( + id__in=Food.objects.filter(ingredient__step__recipe__in=related_recipes).exclude(recipe=None).values_list( + 'recipe')) return Recipe.objects.filter(Q(id__in=related_recipes.values_list('id')) | sub_step_recipes | sub_food_recipes) class Meta(): @@ -1016,7 +1042,8 @@ class MealPlan(ExportModelOperationsMixin('meal_plan'), models.Model, Permission class ShoppingListRecipe(ExportModelOperationsMixin('shopping_list_recipe'), models.Model, PermissionModelMixin): name = models.CharField(max_length=32, blank=True, default='') - recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE, null=True, blank=True) # TODO make required after old shoppinglist deprecated + recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE, null=True, + blank=True) # TODO make required after old shoppinglist deprecated servings = models.DecimalField(default=1, max_digits=8, decimal_places=4) mealplan = models.ForeignKey(MealPlan, on_delete=models.CASCADE, null=True, blank=True) @@ -1034,13 +1061,15 @@ 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) + return getattr(self.entries.first(), 'created_by', None) or getattr(self.shoppinglist_set.first(), + 'created_by', None) except AttributeError: return None class ShoppingListEntry(ExportModelOperationsMixin('shopping_list_entry'), models.Model, PermissionModelMixin): - list_recipe = models.ForeignKey(ShoppingListRecipe, on_delete=models.CASCADE, null=True, blank=True, related_name='entries') + list_recipe = models.ForeignKey(ShoppingListRecipe, on_delete=models.CASCADE, null=True, blank=True, + related_name='entries') food = models.ForeignKey(Food, on_delete=models.CASCADE, related_name='shopping_entries') unit = models.ForeignKey(Unit, on_delete=models.SET_NULL, null=True, blank=True) ingredient = models.ForeignKey(Ingredient, on_delete=models.CASCADE, null=True, blank=True) @@ -1305,7 +1334,8 @@ class UserFile(ExportModelOperationsMixin('user_files'), models.Model, Permissio return False def save(self, *args, **kwargs): - if hasattr(self.file, 'file') and isinstance(self.file.file, UploadedFile) or isinstance(self.file.file, InMemoryUploadedFile): + if hasattr(self.file, 'file') and isinstance(self.file.file, UploadedFile) or isinstance(self.file.file, + InMemoryUploadedFile): self.file.name = f'{uuid.uuid4()}' + pathlib.Path(self.file.name).suffix self.file_size_kb = round(self.file.size / 1000) super(UserFile, self).save(*args, **kwargs) diff --git a/cookbook/serializer.py b/cookbook/serializer.py index 70ba4b1ae..5d3263bd8 100644 --- a/cookbook/serializer.py +++ b/cookbook/serializer.py @@ -56,7 +56,8 @@ class ExtendedRecipeMixin(serializers.ModelSerializer): api_serializer = None # extended values are computationally expensive and not needed in normal circumstances 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 except (AttributeError, KeyError): pass @@ -80,12 +81,14 @@ 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) @@ -121,7 +124,8 @@ class CustomOnHandField(serializers.Field): if not self.context["request"].user.is_authenticated: return [] 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 else: try: @@ -280,6 +284,7 @@ class SpaceSerializer(WritableNestedModelSerializer): file_size_mb = serializers.SerializerMethodField('get_file_size_mb') food_inherit = FoodInheritFieldSerializer(many=True) image = UserFileViewSerializer(required=False, many=False, allow_null=True) + favicon = UserFileViewSerializer(required=False, many=False, allow_null=True) def get_user_count(self, obj): return UserSpace.objects.filter(space=obj).count() @@ -301,7 +306,7 @@ class SpaceSerializer(WritableNestedModelSerializer): fields = ( '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', - 'image', 'use_plural',) + 'image', 'favicon', 'use_plural',) read_only_fields = ( 'id', 'created_by', 'created_at', 'max_recipes', 'max_file_storage_mb', 'max_users', 'allow_sharing', 'demo',) @@ -321,7 +326,8 @@ 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') @@ -337,7 +343,8 @@ class MealTypeSerializer(SpacedModelSerializer, WritableNestedModelSerializer): validated_data['name'] = validated_data['name'].strip() space = validated_data.pop('space', self.context['request'].space) 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 class Meta: @@ -377,7 +384,8 @@ class UserPreferenceSerializer(WritableNestedModelSerializer): 'food_inherit_default', 'default_delay', 'mealplan_autoinclude_related', 'mealplan_autoexclude_onhand', 'shopping_share', 'shopping_recent_days', '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' ) @@ -466,10 +474,13 @@ class UnitSerializer(UniqueFieldsMixin, ExtendedRecipeMixin, OpenDataModelMixin) if x := validated_data.get('name', None): 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 - 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 def update(self, instance, validated_data): @@ -489,7 +500,8 @@ class SupermarketCategorySerializer(UniqueFieldsMixin, WritableNestedModelSerial def create(self, validated_data): validated_data['name'] = validated_data['name'].strip() 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 def update(self, instance, validated_data): @@ -514,7 +526,8 @@ class SupermarketSerializer(UniqueFieldsMixin, SpacedModelSerializer, OpenDataMo def create(self, validated_data): validated_data['name'] = validated_data['name'].strip() 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 class Meta: @@ -528,7 +541,8 @@ class PropertyTypeSerializer(OpenDataModelMixin, WritableNestedModelSerializer, def create(self, validated_data): validated_data['name'] = validated_data['name'].strip() 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 class Meta: @@ -653,12 +667,14 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedR 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) if properties and len(properties) > 0: 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 @@ -690,7 +706,8 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedR 'properties', 'properties_food_amount', 'properties_food_unit', 'food_onhand', 'supermarket_category', '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') @@ -714,7 +731,8 @@ class IngredientSimpleSerializer(WritableNestedModelSerializer): uch = UnitConversionHelper(self.context['request'].space) conversions = [] 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 else: return [] @@ -816,7 +834,8 @@ 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): @@ -886,7 +905,8 @@ class RecipeSerializer(RecipeBaseSerializer): fields = ( 'id', 'name', 'description', 'image', 'keywords', 'steps', 'working_time', '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', 'private', 'shared', ) @@ -965,7 +985,8 @@ class RecipeBookEntrySerializer(serializers.ModelSerializer): def create(self, validated_data): book = validated_data['book'] 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) obj, created = RecipeBookEntry.objects.get_or_create(book=book, recipe=recipe) return obj @@ -1031,10 +1052,10 @@ class ShoppingListRecipeSerializer(serializers.ModelSerializer): 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 + 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): @@ -1265,7 +1286,8 @@ class InviteLinkSerializer(WritableNestedModelSerializer): class Meta: model = InviteLink 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',) diff --git a/cookbook/templates/base.html b/cookbook/templates/base.html index 566027101..82298d110 100644 --- a/cookbook/templates/base.html +++ b/cookbook/templates/base.html @@ -11,15 +11,20 @@ + {% if request.space.favicon %} + + + - - - - - - - + {% else %} + + + + + + + {% endif %} @@ -82,7 +87,7 @@ {% if not request.user.is_authenticated or request.user.userpreference.theme == request.user.userpreference.TANDOOR %} - Logo + Logo {% endif %} {% endif %} diff --git a/cookbook/templatetags/theming_tags.py b/cookbook/templatetags/theming_tags.py index 79175be2f..8ec04c237 100644 --- a/cookbook/templatetags/theming_tags.py +++ b/cookbook/templatetags/theming_tags.py @@ -25,6 +25,14 @@ def theme_url(request): raise AttributeError +@register.simple_tag +def logo_url(request): + if request.user.is_authenticated and request.space.image: + return request.space.image.file.url + else: + return static('assets/brand_logo.png') + + @register.simple_tag def nav_color(request): if not request.user.is_authenticated: diff --git a/vue/src/apps/SpaceManageView/SpaceManageView.vue b/vue/src/apps/SpaceManageView/SpaceManageView.vue index fb8d86b16..b915a1d05 100644 --- a/vue/src/apps/SpaceManageView/SpaceManageView.vue +++ b/vue/src/apps/SpaceManageView/SpaceManageView.vue @@ -147,6 +147,12 @@ :multiple="false" @change="space.image = $event.val;">
+ + +
Facet Count {{ $t('facet_count_info') }}
From da9002a7fd191f62e5839eb6073cdad6eb3f88b4 Mon Sep 17 00:00:00 2001 From: vabene1111 Date: Mon, 27 Nov 2023 20:23:36 +0100 Subject: [PATCH 2/2] Revert "WIP" This reverts commit 58e70c982ea3a5010bb49ab1c1fcc92202d3c54d. --- cookbook/migrations/0204_space_favicon.py | 19 ----- cookbook/models.py | 82 ++++++------------- cookbook/serializer.py | 72 ++++++---------- cookbook/templates/base.html | 21 ++--- cookbook/templatetags/theming_tags.py | 8 -- .../apps/SpaceManageView/SpaceManageView.vue | 6 -- 6 files changed, 59 insertions(+), 149 deletions(-) delete mode 100644 cookbook/migrations/0204_space_favicon.py diff --git a/cookbook/migrations/0204_space_favicon.py b/cookbook/migrations/0204_space_favicon.py deleted file mode 100644 index c34bd86f7..000000000 --- a/cookbook/migrations/0204_space_favicon.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 4.2.5 on 2023-11-21 21:18 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('cookbook', '0203_alter_unique_contstraints'), - ] - - operations = [ - migrations.AddField( - model_name='space', - name='favicon', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='space_favicon', to='cookbook.userfile'), - ), - ] diff --git a/cookbook/models.py b/cookbook/models.py index abeaacd98..caf84d790 100644 --- a/cookbook/models.py +++ b/cookbook/models.py @@ -95,8 +95,7 @@ class TreeManager(MP_NodeManager): if defaults: kwargs = {**kwargs, **defaults} # ManyToMany fields can't be set this way, so pop them out to save for later - fields = [field.name for field in self.model._meta.get_fields() if - issubclass(type(field), ManyToManyField)] + fields = [field.name for field in self.model._meta.get_fields() if issubclass(type(field), ManyToManyField)] many_to_many = {field: kwargs.pop(field) for field in list(kwargs) if field in fields} obj = self.model.add_root(**kwargs) for field in many_to_many: @@ -254,13 +253,11 @@ class FoodInheritField(models.Model, PermissionModelMixin): class Space(ExportModelOperationsMixin('space'), models.Model): name = models.CharField(max_length=128, default='Default') image = models.ForeignKey("UserFile", on_delete=models.SET_NULL, null=True, blank=True, related_name='space_image') - favicon = models.ForeignKey("UserFile", on_delete=models.SET_NULL, null=True, blank=True, related_name='space_favicon') created_by = models.ForeignKey(User, on_delete=models.PROTECT, null=True) created_at = models.DateTimeField(auto_now_add=True) message = models.CharField(max_length=512, default='', blank=True) max_recipes = models.IntegerField(default=0) - max_file_storage_mb = models.IntegerField(default=0, help_text=_( - 'Maximum file storage for space in MB. 0 for unlimited, -1 to disable file upload.')) + max_file_storage_mb = models.IntegerField(default=0, help_text=_('Maximum file storage for space in MB. 0 for unlimited, -1 to disable file upload.')) max_users = models.IntegerField(default=0) use_plural = models.BooleanField(default=True) allow_sharing = models.BooleanField(default=True) @@ -474,8 +471,7 @@ class SupermarketCategory(models.Model, PermissionModelMixin): class Meta: constraints = [ models.UniqueConstraint(fields=['space', 'name'], name='smc_unique_name_per_space'), - models.UniqueConstraint(fields=['space', 'open_data_slug'], - name='supermarket_category_unique_open_data_slug_per_space') + models.UniqueConstraint(fields=['space', 'open_data_slug'], name='supermarket_category_unique_open_data_slug_per_space') ] @@ -494,8 +490,7 @@ class Supermarket(models.Model, PermissionModelMixin): class Meta: constraints = [ models.UniqueConstraint(fields=['space', 'name'], name='sm_unique_name_per_space'), - models.UniqueConstraint(fields=['space', 'open_data_slug'], - name='supermarket_unique_open_data_slug_per_space') + models.UniqueConstraint(fields=['space', 'open_data_slug'], name='supermarket_unique_open_data_slug_per_space') ] @@ -580,8 +575,7 @@ class Food(ExportModelOperationsMixin('food'), TreeModel, PermissionModelMixin): plural_name = models.CharField(max_length=128, null=True, blank=True, default=None) recipe = models.ForeignKey('Recipe', null=True, blank=True, on_delete=models.SET_NULL) url = models.CharField(max_length=1024, blank=True, null=True, default='') - supermarket_category = models.ForeignKey(SupermarketCategory, null=True, blank=True, - on_delete=models.SET_NULL) # inherited field + supermarket_category = models.ForeignKey(SupermarketCategory, null=True, blank=True, on_delete=models.SET_NULL) # inherited field ignore_shopping = models.BooleanField(default=False) # inherited field onhand_users = models.ManyToManyField(User, blank=True) description = models.TextField(default='', blank=True) @@ -595,10 +589,8 @@ class Food(ExportModelOperationsMixin('food'), TreeModel, PermissionModelMixin): properties_food_amount = models.DecimalField(default=100, max_digits=16, decimal_places=2, blank=True) properties_food_unit = models.ForeignKey(Unit, on_delete=models.PROTECT, blank=True, null=True) - preferred_unit = models.ForeignKey(Unit, on_delete=models.SET_NULL, null=True, blank=True, default=None, - related_name='preferred_unit') - preferred_shopping_unit = models.ForeignKey(Unit, on_delete=models.SET_NULL, null=True, blank=True, default=None, - related_name='preferred_shopping_unit') + preferred_unit = models.ForeignKey(Unit, on_delete=models.SET_NULL, null=True, blank=True, default=None, related_name='preferred_unit') + preferred_shopping_unit = models.ForeignKey(Unit, on_delete=models.SET_NULL, null=True, blank=True, default=None, related_name='preferred_shopping_unit') fdc_id = models.CharField(max_length=128, null=True, blank=True, default=None) open_data_slug = models.CharField(max_length=128, null=True, blank=True, default=None) @@ -610,8 +602,7 @@ class Food(ExportModelOperationsMixin('food'), TreeModel, PermissionModelMixin): def delete(self): if self.ingredient_set.all().exclude(step=None).count() > 0: - raise ProtectedError(self.name + _(" is part of a recipe step and cannot be deleted"), - self.ingredient_set.all().exclude(step=None)) + raise ProtectedError(self.name + _(" is part of a recipe step and cannot be deleted"), self.ingredient_set.all().exclude(step=None)) else: return super().delete() @@ -661,12 +652,8 @@ class Food(ExportModelOperationsMixin('food'), TreeModel, PermissionModelMixin): food.get_descendants().update(**{f"{field}": False}) else: # get food at root that have children that need updated - Food.include_descendants( - queryset=Food.objects.filter(depth=1, numchild__gt=0, **{f"{field}": True}, - space=space)).update(**{f"{field}": True}) - Food.include_descendants( - queryset=Food.objects.filter(depth=1, numchild__gt=0, **{f"{field}": False}, - space=space)).update(**{f"{field}": False}) + Food.include_descendants(queryset=Food.objects.filter(depth=1, numchild__gt=0, **{f"{field}": True}, space=space)).update(**{f"{field}": True}) + Food.include_descendants(queryset=Food.objects.filter(depth=1, numchild__gt=0, **{f"{field}": False}, space=space)).update(**{f"{field}": False}) if 'supermarket_category' in inherit: # when supermarket_category is null or blank assuming it is not set and not intended to be blank for all descedants @@ -674,8 +661,7 @@ class Food(ExportModelOperationsMixin('food'), TreeModel, PermissionModelMixin): food.get_descendants().update(supermarket_category=food.supermarket_category) elif food is None: # find top node that has category set - category_roots = Food.exclude_descendants( - queryset=Food.objects.filter(supermarket_category__isnull=False, numchild__gt=0, space=space)) + category_roots = Food.exclude_descendants(queryset=Food.objects.filter(supermarket_category__isnull=False, numchild__gt=0, space=space)) for root in category_roots: root.get_descendants().update(supermarket_category=root.supermarket_category) @@ -694,8 +680,7 @@ class UnitConversion(ExportModelOperationsMixin('unit_conversion'), models.Model base_amount = models.DecimalField(default=0, decimal_places=16, max_digits=32) base_unit = models.ForeignKey('Unit', on_delete=models.CASCADE, related_name='unit_conversion_base_relation') converted_amount = models.DecimalField(default=0, decimal_places=16, max_digits=32) - converted_unit = models.ForeignKey('Unit', on_delete=models.CASCADE, - related_name='unit_conversion_converted_relation') + converted_unit = models.ForeignKey('Unit', on_delete=models.CASCADE, related_name='unit_conversion_converted_relation') food = models.ForeignKey('Food', on_delete=models.CASCADE, null=True, blank=True) @@ -712,10 +697,8 @@ class UnitConversion(ExportModelOperationsMixin('unit_conversion'), models.Model class Meta: constraints = [ - models.UniqueConstraint(fields=['space', 'base_unit', 'converted_unit', 'food'], - name='f_unique_conversion_per_space'), - models.UniqueConstraint(fields=['space', 'open_data_slug'], - name='unit_conversion_unique_open_data_slug_per_space') + models.UniqueConstraint(fields=['space', 'base_unit', 'converted_unit', 'food'], name='f_unique_conversion_per_space'), + models.UniqueConstraint(fields=['space', 'open_data_slug'], name='unit_conversion_unique_open_data_slug_per_space') ] @@ -781,8 +764,7 @@ class PropertyType(models.Model, PermissionModelMixin): order = models.IntegerField(default=0) description = models.CharField(max_length=512, blank=True, null=True) category = models.CharField(max_length=64, choices=((NUTRITION, _('Nutrition')), (ALLERGEN, _('Allergen')), - (PRICE, _('Price')), (GOAL, _('Goal')), (OTHER, _('Other'))), - null=True, blank=True) + (PRICE, _('Price')), (GOAL, _('Goal')), (OTHER, _('Other'))), null=True, blank=True) open_data_slug = models.CharField(max_length=128, null=True, blank=True, default=None) # TODO show if empty property? @@ -797,8 +779,7 @@ class PropertyType(models.Model, PermissionModelMixin): class Meta: constraints = [ models.UniqueConstraint(fields=['space', 'name'], name='property_type_unique_name_per_space'), - models.UniqueConstraint(fields=['space', 'open_data_slug'], - name='property_type_unique_open_data_slug_per_space') + models.UniqueConstraint(fields=['space', 'open_data_slug'], name='property_type_unique_open_data_slug_per_space') ] ordering = ('order',) @@ -807,8 +788,7 @@ class Property(models.Model, PermissionModelMixin): property_amount = models.DecimalField(default=0, decimal_places=4, max_digits=32) property_type = models.ForeignKey(PropertyType, on_delete=models.PROTECT) - import_food_id = models.IntegerField(null=True, - blank=True) # field to hold food id when importing properties from the open data project + import_food_id = models.IntegerField(null=True, blank=True) # field to hold food id when importing properties from the open data project space = models.ForeignKey(Space, on_delete=models.CASCADE) objects = ScopedManager(space='space') @@ -818,8 +798,7 @@ class Property(models.Model, PermissionModelMixin): class Meta: constraints = [ - models.UniqueConstraint(fields=['space', 'property_type', 'import_food_id'], - name='property_unique_import_food_per_space') + models.UniqueConstraint(fields=['space', 'property_type', 'import_food_id'], name='property_unique_import_food_per_space') ] @@ -851,8 +830,7 @@ class NutritionInformation(models.Model, PermissionModelMixin): class RecipeManager(models.Manager.from_queryset(models.QuerySet)): def get_queryset(self): - return super(RecipeManager, self).get_queryset().annotate(rating=Avg('cooklog__rating')).annotate( - last_cooked=Max('cooklog__created_at')) + return super(RecipeManager, self).get_queryset().annotate(rating=Avg('cooklog__rating')).annotate(last_cooked=Max('cooklog__created_at')) class Recipe(ExportModelOperationsMixin('recipe'), models.Model, PermissionModelMixin): @@ -895,19 +873,15 @@ class Recipe(ExportModelOperationsMixin('recipe'), models.Model, PermissionModel # recipes for step recipe step_recipes = Q(id__in=self.steps.exclude(step_recipe=None).values_list('step_recipe')) # recipes for foods - food_recipes = Q( - id__in=Food.objects.filter(ingredient__step__recipe=self).exclude(recipe=None).values_list('recipe')) + food_recipes = Q(id__in=Food.objects.filter(ingredient__step__recipe=self).exclude(recipe=None).values_list('recipe')) related_recipes = Recipe.objects.filter(step_recipes | food_recipes) if levels == 1: return related_recipes # this can loop over multiple levels if you update the value of related_recipes at each step (maybe an array?) # for now keeping it at 2 levels max, should be sufficient in 99.9% of scenarios - sub_step_recipes = Q(id__in=Step.objects.filter(recipe__in=related_recipes.values_list('steps')).exclude( - step_recipe=None).values_list('step_recipe')) - sub_food_recipes = Q( - id__in=Food.objects.filter(ingredient__step__recipe__in=related_recipes).exclude(recipe=None).values_list( - 'recipe')) + sub_step_recipes = Q(id__in=Step.objects.filter(recipe__in=related_recipes.values_list('steps')).exclude(step_recipe=None).values_list('step_recipe')) + sub_food_recipes = Q(id__in=Food.objects.filter(ingredient__step__recipe__in=related_recipes).exclude(recipe=None).values_list('recipe')) return Recipe.objects.filter(Q(id__in=related_recipes.values_list('id')) | sub_step_recipes | sub_food_recipes) class Meta(): @@ -1042,8 +1016,7 @@ class MealPlan(ExportModelOperationsMixin('meal_plan'), models.Model, Permission class ShoppingListRecipe(ExportModelOperationsMixin('shopping_list_recipe'), models.Model, PermissionModelMixin): name = models.CharField(max_length=32, blank=True, default='') - recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE, null=True, - blank=True) # TODO make required after old shoppinglist deprecated + recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE, null=True, blank=True) # TODO make required after old shoppinglist deprecated servings = models.DecimalField(default=1, max_digits=8, decimal_places=4) mealplan = models.ForeignKey(MealPlan, on_delete=models.CASCADE, null=True, blank=True) @@ -1061,15 +1034,13 @@ 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) + return getattr(self.entries.first(), 'created_by', None) or getattr(self.shoppinglist_set.first(), 'created_by', None) except AttributeError: return None class ShoppingListEntry(ExportModelOperationsMixin('shopping_list_entry'), models.Model, PermissionModelMixin): - list_recipe = models.ForeignKey(ShoppingListRecipe, on_delete=models.CASCADE, null=True, blank=True, - related_name='entries') + list_recipe = models.ForeignKey(ShoppingListRecipe, on_delete=models.CASCADE, null=True, blank=True, related_name='entries') food = models.ForeignKey(Food, on_delete=models.CASCADE, related_name='shopping_entries') unit = models.ForeignKey(Unit, on_delete=models.SET_NULL, null=True, blank=True) ingredient = models.ForeignKey(Ingredient, on_delete=models.CASCADE, null=True, blank=True) @@ -1334,8 +1305,7 @@ class UserFile(ExportModelOperationsMixin('user_files'), models.Model, Permissio return False def save(self, *args, **kwargs): - if hasattr(self.file, 'file') and isinstance(self.file.file, UploadedFile) or isinstance(self.file.file, - InMemoryUploadedFile): + if hasattr(self.file, 'file') and isinstance(self.file.file, UploadedFile) or isinstance(self.file.file, InMemoryUploadedFile): self.file.name = f'{uuid.uuid4()}' + pathlib.Path(self.file.name).suffix self.file_size_kb = round(self.file.size / 1000) super(UserFile, self).save(*args, **kwargs) diff --git a/cookbook/serializer.py b/cookbook/serializer.py index 5d3263bd8..70ba4b1ae 100644 --- a/cookbook/serializer.py +++ b/cookbook/serializer.py @@ -56,8 +56,7 @@ class ExtendedRecipeMixin(serializers.ModelSerializer): api_serializer = None # extended values are computationally expensive and not needed in normal circumstances 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 except (AttributeError, KeyError): pass @@ -81,14 +80,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) @@ -124,8 +121,7 @@ class CustomOnHandField(serializers.Field): if not self.context["request"].user.is_authenticated: return [] 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 else: try: @@ -284,7 +280,6 @@ class SpaceSerializer(WritableNestedModelSerializer): file_size_mb = serializers.SerializerMethodField('get_file_size_mb') food_inherit = FoodInheritFieldSerializer(many=True) image = UserFileViewSerializer(required=False, many=False, allow_null=True) - favicon = UserFileViewSerializer(required=False, many=False, allow_null=True) def get_user_count(self, obj): return UserSpace.objects.filter(space=obj).count() @@ -306,7 +301,7 @@ class SpaceSerializer(WritableNestedModelSerializer): fields = ( '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', - 'image', 'favicon', 'use_plural',) + 'image', 'use_plural',) read_only_fields = ( 'id', 'created_by', 'created_at', 'max_recipes', 'max_file_storage_mb', 'max_users', 'allow_sharing', 'demo',) @@ -326,8 +321,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') @@ -343,8 +337,7 @@ class MealTypeSerializer(SpacedModelSerializer, WritableNestedModelSerializer): validated_data['name'] = validated_data['name'].strip() space = validated_data.pop('space', self.context['request'].space) 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 class Meta: @@ -384,8 +377,7 @@ class UserPreferenceSerializer(WritableNestedModelSerializer): 'food_inherit_default', 'default_delay', 'mealplan_autoinclude_related', 'mealplan_autoexclude_onhand', 'shopping_share', 'shopping_recent_days', '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' ) @@ -474,13 +466,10 @@ class UnitSerializer(UniqueFieldsMixin, ExtendedRecipeMixin, OpenDataModelMixin) if x := validated_data.get('name', None): 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 - 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 def update(self, instance, validated_data): @@ -500,8 +489,7 @@ class SupermarketCategorySerializer(UniqueFieldsMixin, WritableNestedModelSerial def create(self, validated_data): validated_data['name'] = validated_data['name'].strip() 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 def update(self, instance, validated_data): @@ -526,8 +514,7 @@ class SupermarketSerializer(UniqueFieldsMixin, SpacedModelSerializer, OpenDataMo def create(self, validated_data): validated_data['name'] = validated_data['name'].strip() 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 class Meta: @@ -541,8 +528,7 @@ class PropertyTypeSerializer(OpenDataModelMixin, WritableNestedModelSerializer, def create(self, validated_data): validated_data['name'] = validated_data['name'].strip() 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 class Meta: @@ -667,14 +653,12 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedR 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) if properties and len(properties) > 0: 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 @@ -706,8 +690,7 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedR 'properties', 'properties_food_amount', 'properties_food_unit', 'food_onhand', 'supermarket_category', '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') @@ -731,8 +714,7 @@ class IngredientSimpleSerializer(WritableNestedModelSerializer): uch = UnitConversionHelper(self.context['request'].space) conversions = [] 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 else: return [] @@ -834,8 +816,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): @@ -905,8 +886,7 @@ class RecipeSerializer(RecipeBaseSerializer): fields = ( 'id', 'name', 'description', 'image', 'keywords', 'steps', 'working_time', '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', 'private', 'shared', ) @@ -985,8 +965,7 @@ class RecipeBookEntrySerializer(serializers.ModelSerializer): def create(self, validated_data): book = validated_data['book'] 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) obj, created = RecipeBookEntry.objects.get_or_create(book=book, recipe=recipe) return obj @@ -1052,10 +1031,10 @@ class ShoppingListRecipeSerializer(serializers.ModelSerializer): 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 + 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): @@ -1286,8 +1265,7 @@ class InviteLinkSerializer(WritableNestedModelSerializer): class Meta: model = InviteLink 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',) diff --git a/cookbook/templates/base.html b/cookbook/templates/base.html index 82298d110..566027101 100644 --- a/cookbook/templates/base.html +++ b/cookbook/templates/base.html @@ -11,20 +11,15 @@ - {% if request.space.favicon %} - - - - {% else %} - - - - + + + + + + + - - - {% endif %} @@ -87,7 +82,7 @@ {% if not request.user.is_authenticated or request.user.userpreference.theme == request.user.userpreference.TANDOOR %} - Logo + Logo {% endif %} {% endif %} diff --git a/cookbook/templatetags/theming_tags.py b/cookbook/templatetags/theming_tags.py index 8ec04c237..79175be2f 100644 --- a/cookbook/templatetags/theming_tags.py +++ b/cookbook/templatetags/theming_tags.py @@ -25,14 +25,6 @@ def theme_url(request): raise AttributeError -@register.simple_tag -def logo_url(request): - if request.user.is_authenticated and request.space.image: - return request.space.image.file.url - else: - return static('assets/brand_logo.png') - - @register.simple_tag def nav_color(request): if not request.user.is_authenticated: diff --git a/vue/src/apps/SpaceManageView/SpaceManageView.vue b/vue/src/apps/SpaceManageView/SpaceManageView.vue index b915a1d05..fb8d86b16 100644 --- a/vue/src/apps/SpaceManageView/SpaceManageView.vue +++ b/vue/src/apps/SpaceManageView/SpaceManageView.vue @@ -147,12 +147,6 @@ :multiple="false" @change="space.image = $event.val;">
- - -
Facet Count {{ $t('facet_count_info') }}