added ability to set space images
This commit is contained in:
19
cookbook/migrations/0181_space_image.py
Normal file
19
cookbook/migrations/0181_space_image.py
Normal file
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
@ -4,6 +4,7 @@ import re
|
|||||||
import uuid
|
import uuid
|
||||||
from datetime import date, timedelta
|
from datetime import date, timedelta
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
from annoying.fields import AutoOneToOneField
|
from annoying.fields import AutoOneToOneField
|
||||||
from django.contrib import auth
|
from django.contrib import auth
|
||||||
from django.contrib.auth.models import Group, User
|
from django.contrib.auth.models import Group, User
|
||||||
@ -244,6 +245,7 @@ class FoodInheritField(models.Model, PermissionModelMixin):
|
|||||||
|
|
||||||
class Space(ExportModelOperationsMixin('space'), models.Model):
|
class Space(ExportModelOperationsMixin('space'), models.Model):
|
||||||
name = models.CharField(max_length=128, default='Default')
|
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_by = models.ForeignKey(User, on_delete=models.PROTECT, null=True)
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
message = models.CharField(max_length=512, default='', blank=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')
|
objects = ScopedManager(space='space')
|
||||||
space = models.ForeignKey(Space, on_delete=models.CASCADE)
|
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):
|
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.name = f'{uuid.uuid4()}' + pathlib.Path(self.file.name).suffix
|
||||||
|
@ -173,104 +173,6 @@ class FoodInheritFieldSerializer(UniqueFieldsMixin, WritableNestedModelSerialize
|
|||||||
read_only_fields = ['id']
|
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):
|
class UserFileSerializer(serializers.ModelSerializer):
|
||||||
file = serializers.FileField(write_only=True)
|
file = serializers.FileField(write_only=True)
|
||||||
file_download = serializers.SerializerMethodField('get_download_link')
|
file_download = serializers.SerializerMethodField('get_download_link')
|
||||||
@ -347,6 +249,105 @@ class UserFileViewSerializer(serializers.ModelSerializer):
|
|||||||
read_only_fields = ('id', 'file')
|
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):
|
class StorageSerializer(SpacedModelSerializer):
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
|
@ -32,15 +32,23 @@
|
|||||||
{% for us in request.user.userspace_set.all %}
|
{% for us in request.user.userspace_set.all %}
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
|
{% if us.space.image and us.space.image.is_image %}
|
||||||
|
<img style="height: 15vh; object-fit: cover" src="{{ us.space.image.file.url }}"
|
||||||
|
class="card-img-top" alt="Image">
|
||||||
|
{% else %}
|
||||||
|
|
||||||
|
<img style="height: 15vh; object-fit: cover" src="{% static 'assets/recipe_no_image.svg' %}"
|
||||||
|
class="card-img-top" alt="Image">
|
||||||
|
{% endif %}
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5 class="card-title"><a
|
<h5 class="card-title"><a
|
||||||
href="{% url 'view_switch_space' us.space.id %}">{{ us.space.name }}</a>
|
href="{% url 'view_switch_space' us.space.id %}">{{ us.space.name }}</a>
|
||||||
</h5>
|
</h5>
|
||||||
{# {% if us.active %}#}
|
{# {% if us.active %}#}
|
||||||
{# <i class="far fa-dot-circle fa-fw"></i>#}
|
{# <i class="far fa-dot-circle fa-fw"></i>#}
|
||||||
{# {% else %}#}
|
{# {% else %}#}
|
||||||
{# <i class="far fa-circle fa-fw"></i>#}
|
{# <i class="far fa-circle fa-fw"></i>#}
|
||||||
{# {% endif %}#}
|
{# {% endif %}#}
|
||||||
<p class="card-text"><small
|
<p class="card-text"><small
|
||||||
class="text-muted">{% trans 'Owner' %}: {{ us.space.created_by }}</small>
|
class="text-muted">{% trans 'Owner' %}: {{ us.space.created_by }}</small>
|
||||||
{% if us.space.created_by != us.user %}
|
{% if us.space.created_by != us.user %}
|
||||||
|
@ -141,6 +141,13 @@
|
|||||||
<label>{{ $t('Message') }}</label>
|
<label>{{ $t('Message') }}</label>
|
||||||
<b-form-textarea v-model="space.message"></b-form-textarea>
|
<b-form-textarea v-model="space.message"></b-form-textarea>
|
||||||
|
|
||||||
|
<label>{{ $t('Image') }}</label>
|
||||||
|
<generic-multiselect :initial_single_selection="space.image"
|
||||||
|
:model="Models.USERFILE"
|
||||||
|
:multiple="false"
|
||||||
|
@change="space.image = $event.val;"></generic-multiselect>
|
||||||
|
<br/>
|
||||||
|
|
||||||
<b-form-checkbox v-model="space.show_facet_count"> Facet Count</b-form-checkbox>
|
<b-form-checkbox v-model="space.show_facet_count"> Facet Count</b-form-checkbox>
|
||||||
<span class="text-muted small">{{ $t('facet_count_info') }}</span><br/>
|
<span class="text-muted small">{{ $t('facet_count_info') }}</span><br/>
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user