diff --git a/cookbook/migrations/0181_space_image.py b/cookbook/migrations/0181_space_image.py new file mode 100644 index 000000000..5bfdbf5f3 --- /dev/null +++ b/cookbook/migrations/0181_space_image.py @@ -0,0 +1,19 @@ +# Generated by Django 4.0.6 on 2022-07-14 11:14 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('cookbook', '0180_invitelink_reusable'), + ] + + operations = [ + migrations.AddField( + model_name='space', + name='image', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='space_image', to='cookbook.userfile'), + ), + ] diff --git a/cookbook/models.py b/cookbook/models.py index 6114e007c..359fd29ee 100644 --- a/cookbook/models.py +++ b/cookbook/models.py @@ -4,6 +4,7 @@ import re import uuid from datetime import date, timedelta +from PIL import Image from annoying.fields import AutoOneToOneField from django.contrib import auth from django.contrib.auth.models import Group, User @@ -244,6 +245,7 @@ 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, related_name='space_image') 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) @@ -1177,6 +1179,13 @@ class UserFile(ExportModelOperationsMixin('user_files'), models.Model, Permissio objects = ScopedManager(space='space') space = models.ForeignKey(Space, on_delete=models.CASCADE) + def is_image(self): + try: + img = Image.open(self.file.file.file) + return True + except Exception: + return False + def save(self, *args, **kwargs): 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 diff --git a/cookbook/serializer.py b/cookbook/serializer.py index ef3d2d5a9..0de4d0931 100644 --- a/cookbook/serializer.py +++ b/cookbook/serializer.py @@ -173,104 +173,6 @@ class FoodInheritFieldSerializer(UniqueFieldsMixin, WritableNestedModelSerialize read_only_fields = ['id'] -class SpaceSerializer(WritableNestedModelSerializer): - user_count = serializers.SerializerMethodField('get_user_count') - recipe_count = serializers.SerializerMethodField('get_recipe_count') - file_size_mb = serializers.SerializerMethodField('get_file_size_mb') - food_inherit = FoodInheritFieldSerializer(many=True) - - def get_user_count(self, obj): - return UserSpace.objects.filter(space=obj).count() - - def get_recipe_count(self, obj): - return Recipe.objects.filter(space=obj).count() - - def get_file_size_mb(self, obj): - try: - return UserFile.objects.filter(space=obj).aggregate(Sum('file_size_kb'))['file_size_kb__sum'] / 1000 - except TypeError: - return 0 - - def create(self, validated_data): - raise ValidationError('Cannot create using this endpoint') - - class Meta: - model = Space - fields = ('id', 'name', 'created_by', 'created_at', 'message', 'max_recipes', 'max_file_storage_mb', 'max_users', - 'allow_sharing', 'demo', 'food_inherit', 'show_facet_count', 'user_count', 'recipe_count', 'file_size_mb',) - read_only_fields = ('id', 'created_by', 'created_at', 'max_recipes', 'max_file_storage_mb', 'max_users', 'allow_sharing', 'demo',) - - -class UserSpaceSerializer(WritableNestedModelSerializer): - user = UserNameSerializer(read_only=True) - groups = GroupSerializer(many=True) - - def validate(self, data): - if self.instance.user == self.context['request'].space.created_by: # can't change space owner permission - raise serializers.ValidationError(_('Cannot modify Space owner permission.')) - return super().validate(data) - - def create(self, validated_data): - raise ValidationError('Cannot create using this endpoint') - - class Meta: - model = UserSpace - fields = ('id', 'user', 'space', 'groups', 'active', 'created_at', 'updated_at',) - read_only_fields = ('id', 'created_at', 'updated_at', 'space') - - -class SpacedModelSerializer(serializers.ModelSerializer): - def create(self, validated_data): - validated_data['space'] = self.context['request'].space - return super().create(validated_data) - - -class MealTypeSerializer(SpacedModelSerializer, WritableNestedModelSerializer): - - def create(self, validated_data): - validated_data['created_by'] = self.context['request'].user - return super().create(validated_data) - - class Meta: - list_serializer_class = SpaceFilterSerializer - model = MealType - fields = ('id', 'name', 'order', 'icon', 'color', 'default', 'created_by') - read_only_fields = ('created_by',) - - -class UserPreferenceSerializer(WritableNestedModelSerializer): - food_inherit_default = serializers.SerializerMethodField('get_food_inherit_defaults') - plan_share = UserNameSerializer(many=True, allow_null=True, required=False) - shopping_share = UserNameSerializer(many=True, allow_null=True, required=False) - food_children_exist = serializers.SerializerMethodField('get_food_children_exist') - - def get_food_inherit_defaults(self, obj): - return FoodInheritFieldSerializer(obj.user.get_active_space().food_inherit.all(), many=True).data - - def get_food_children_exist(self, obj): - space = getattr(self.context.get('request', None), 'space', None) - return Food.objects.filter(depth__gt=0, space=space).exists() - - def update(self, instance, validated_data): - with scopes_disabled(): - return super().update(instance, validated_data) - - def create(self, validated_data): - raise ValidationError('Cannot create using this endpoint') - - class Meta: - model = UserPreference - fields = ( - 'user', 'theme', 'nav_color', 'default_unit', 'default_page', 'use_fractions', 'use_kj', - 'plan_share', - 'ingredient_decimals', 'comments', 'shopping_auto_sync', 'mealplan_autoadd_shopping', - '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', 'food_children_exist' - ) - - class UserFileSerializer(serializers.ModelSerializer): file = serializers.FileField(write_only=True) file_download = serializers.SerializerMethodField('get_download_link') @@ -347,6 +249,105 @@ class UserFileViewSerializer(serializers.ModelSerializer): read_only_fields = ('id', 'file') +class SpaceSerializer(WritableNestedModelSerializer): + user_count = serializers.SerializerMethodField('get_user_count') + recipe_count = serializers.SerializerMethodField('get_recipe_count') + file_size_mb = serializers.SerializerMethodField('get_file_size_mb') + food_inherit = FoodInheritFieldSerializer(many=True) + image = UserFileViewSerializer(required=False, many=False) + + def get_user_count(self, obj): + return UserSpace.objects.filter(space=obj).count() + + def get_recipe_count(self, obj): + return Recipe.objects.filter(space=obj).count() + + def get_file_size_mb(self, obj): + try: + return UserFile.objects.filter(space=obj).aggregate(Sum('file_size_kb'))['file_size_kb__sum'] / 1000 + except TypeError: + return 0 + + def create(self, validated_data): + raise ValidationError('Cannot create using this endpoint') + + class Meta: + model = Space + fields = ('id', 'name', 'created_by', 'created_at', 'message', 'max_recipes', 'max_file_storage_mb', 'max_users', + 'allow_sharing', 'demo', 'food_inherit', 'show_facet_count', 'user_count', 'recipe_count', 'file_size_mb', 'image',) + read_only_fields = ('id', 'created_by', 'created_at', 'max_recipes', 'max_file_storage_mb', 'max_users', 'allow_sharing', 'demo',) + + +class UserSpaceSerializer(WritableNestedModelSerializer): + user = UserNameSerializer(read_only=True) + groups = GroupSerializer(many=True) + + def validate(self, data): + if self.instance.user == self.context['request'].space.created_by: # can't change space owner permission + raise serializers.ValidationError(_('Cannot modify Space owner permission.')) + return super().validate(data) + + def create(self, validated_data): + raise ValidationError('Cannot create using this endpoint') + + class Meta: + model = UserSpace + fields = ('id', 'user', 'space', 'groups', 'active', 'created_at', 'updated_at',) + read_only_fields = ('id', 'created_at', 'updated_at', 'space') + + +class SpacedModelSerializer(serializers.ModelSerializer): + def create(self, validated_data): + validated_data['space'] = self.context['request'].space + return super().create(validated_data) + + +class MealTypeSerializer(SpacedModelSerializer, WritableNestedModelSerializer): + + def create(self, validated_data): + validated_data['created_by'] = self.context['request'].user + return super().create(validated_data) + + class Meta: + list_serializer_class = SpaceFilterSerializer + model = MealType + fields = ('id', 'name', 'order', 'icon', 'color', 'default', 'created_by') + read_only_fields = ('created_by',) + + +class UserPreferenceSerializer(WritableNestedModelSerializer): + food_inherit_default = serializers.SerializerMethodField('get_food_inherit_defaults') + plan_share = UserNameSerializer(many=True, allow_null=True, required=False) + shopping_share = UserNameSerializer(many=True, allow_null=True, required=False) + food_children_exist = serializers.SerializerMethodField('get_food_children_exist') + + def get_food_inherit_defaults(self, obj): + return FoodInheritFieldSerializer(obj.user.get_active_space().food_inherit.all(), many=True).data + + def get_food_children_exist(self, obj): + space = getattr(self.context.get('request', None), 'space', None) + return Food.objects.filter(depth__gt=0, space=space).exists() + + def update(self, instance, validated_data): + with scopes_disabled(): + return super().update(instance, validated_data) + + def create(self, validated_data): + raise ValidationError('Cannot create using this endpoint') + + class Meta: + model = UserPreference + fields = ( + 'user', 'theme', 'nav_color', 'default_unit', 'default_page', 'use_fractions', 'use_kj', + 'plan_share', + 'ingredient_decimals', 'comments', 'shopping_auto_sync', 'mealplan_autoadd_shopping', + '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', 'food_children_exist' + ) + + class StorageSerializer(SpacedModelSerializer): def create(self, validated_data): diff --git a/cookbook/templates/space_overview.html b/cookbook/templates/space_overview.html index 0966cc76b..a82d43062 100644 --- a/cookbook/templates/space_overview.html +++ b/cookbook/templates/space_overview.html @@ -32,15 +32,23 @@ {% for us in request.user.userspace_set.all %}
{% trans 'Owner' %}: {{ us.space.created_by }}
{% if us.space.created_by != us.user %}
diff --git a/vue/src/apps/SpaceManageView/SpaceManageView.vue b/vue/src/apps/SpaceManageView/SpaceManageView.vue
index e98bab27d..3f03b0dc6 100644
--- a/vue/src/apps/SpaceManageView/SpaceManageView.vue
+++ b/vue/src/apps/SpaceManageView/SpaceManageView.vue
@@ -141,6 +141,13 @@
+