Files
links/backend/customization/serializers.py
Andrey K. Choi 2e535513b5 + Приведены все функции приложения в рабочий вид
+ Наведен порядок в файлах проекта
+ Наведен порядок в документации
+ Настроены скрипты установки, развертки и так далее, расширен MakeFile
2025-11-02 06:09:55 +09:00

225 lines
8.8 KiB
Python

from rest_framework import serializers
from .models import DesignSettings
class DesignSettingsSerializer(serializers.ModelSerializer):
"""
Сериализатор для настроек дизайна пользователя
"""
background_image_url = serializers.SerializerMethodField()
class Meta:
model = DesignSettings
fields = [
'id',
'theme_color',
'background_image',
'background_image_url',
'dashboard_layout',
'groups_default_expanded',
'show_group_icons',
'show_link_icons',
'dashboard_background_color',
'font_family',
'custom_css',
'header_text_color',
'group_text_color',
'link_text_color',
'cover_overlay_enabled',
'cover_overlay_color',
'cover_overlay_opacity',
'updated_at'
]
read_only_fields = ['id', 'updated_at', 'background_image_url']
# Делаем background_image необязательным
extra_kwargs = {
'background_image': {'required': False}
}
def get_background_image_url(self, obj):
"""
Возвращает полный URL фонового изображения
"""
if obj.background_image:
request = self.context.get('request')
if request:
return request.build_absolute_uri(obj.background_image.url)
return obj.background_image.url
return None
def update(self, instance, validated_data):
"""
Переопределяем метод update для обработки случая, когда background_image не передается
"""
# Специально обрабатываем background_image
background_image = validated_data.pop('background_image', None)
# Обновляем остальные поля
for attr, value in validated_data.items():
setattr(instance, attr, value)
# Обновляем background_image только если передан новый файл
if background_image is not None:
instance.background_image = background_image
instance.save()
return instance
def validate_background_image(self, value):
"""
Валидация загружаемого изображения
"""
if value:
# Проверяем размер файла (максимум 10MB)
if value.size > 10 * 1024 * 1024:
raise serializers.ValidationError('Размер файла не должен превышать 10MB')
# Проверяем тип файла
if not value.content_type.startswith('image/'):
raise serializers.ValidationError('Файл должен быть изображением')
# Допустимые форматы
allowed_types = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp']
if value.content_type not in allowed_types:
raise serializers.ValidationError('Поддерживаются только форматы: JPEG, PNG, GIF, WebP')
return value
def validate_theme_color(self, value):
"""
Валидация цвета темы (должен быть в формате hex)
"""
if not value.startswith('#') or len(value) != 7:
raise serializers.ValidationError('Цвет должен быть в формате #RRGGBB')
try:
int(value[1:], 16)
except ValueError:
raise serializers.ValidationError('Некорректный hex цвет')
return value
def validate_dashboard_background_color(self, value):
"""
Валидация цвета фона дашборда
"""
if not value.startswith('#') or len(value) != 7:
raise serializers.ValidationError('Цвет должен быть в формате #RRGGBB')
try:
int(value[1:], 16)
except ValueError:
raise serializers.ValidationError('Некорректный hex цвет')
return value
def validate_dashboard_layout(self, value):
"""
Валидация типа макета дашборда
"""
valid_layouts = ['sidebar', 'grid', 'list', 'cards', 'compact', 'masonry', 'timeline', 'magazine']
if value not in valid_layouts:
raise serializers.ValidationError(f'Макет должен быть одним из: {", ".join(valid_layouts)}')
return value
def validate_font_family(self, value):
"""
Валидация шрифта
"""
if len(value) > 100:
raise serializers.ValidationError('Название шрифта слишком длинное')
return value
def validate_custom_css(self, value):
"""
Базовая валидация пользовательского CSS
"""
if len(value) > 10000: # Ограничение на размер CSS
raise serializers.ValidationError('CSS код слишком длинный (максимум 10000 символов)')
# Простая проверка на потенциально опасные директивы
dangerous_patterns = ['@import', 'javascript:', 'expression(']
for pattern in dangerous_patterns:
if pattern in value.lower():
raise serializers.ValidationError(f'Обнаружена потенциально опасная директива: {pattern}')
return value
def validate_header_text_color(self, value):
"""
Валидация цвета заголовков
"""
if not value.startswith('#') or len(value) != 7:
raise serializers.ValidationError('Цвет должен быть в формате #RRGGBB')
try:
int(value[1:], 16)
except ValueError:
raise serializers.ValidationError('Некорректный hex цвет')
return value
def validate_group_text_color(self, value):
"""
Валидация цвета названий групп
"""
if not value.startswith('#') or len(value) != 7:
raise serializers.ValidationError('Цвет должен быть в формате #RRGGBB')
try:
int(value[1:], 16)
except ValueError:
raise serializers.ValidationError('Некорректный hex цвет')
return value
def validate_link_text_color(self, value):
"""
Валидация цвета названий ссылок
"""
if not value.startswith('#') or len(value) != 7:
raise serializers.ValidationError('Цвет должен быть в формате #RRGGBB')
try:
int(value[1:], 16)
except ValueError:
raise serializers.ValidationError('Некорректный hex цвет')
return value
def validate_cover_overlay_color(self, value):
"""
Валидация цвета перекрытия обложки
"""
if not value.startswith('#') or len(value) != 7:
raise serializers.ValidationError('Цвет должен быть в формате #RRGGBB')
try:
int(value[1:], 16)
except ValueError:
raise serializers.ValidationError('Некорректный hex цвет')
return value
def validate_cover_overlay_opacity(self, value):
"""
Валидация прозрачности перекрытия обложки
"""
if not 0.0 <= value <= 1.0:
raise serializers.ValidationError('Прозрачность должна быть между 0.0 и 1.0')
return value
class PublicDesignSettingsSerializer(serializers.ModelSerializer):
"""
Публичный сериализатор для настроек дизайна (только для отображения)
"""
background_image_url = serializers.SerializerMethodField()
class Meta:
model = DesignSettings
fields = [
'theme_color',
'background_image_url',
'font_family',
'custom_css'
]
def get_background_image_url(self, obj):
"""
Возвращает полный URL фонового изображения
"""
if obj.background_image:
request = self.context.get('request')
if request:
return request.build_absolute_uri(obj.background_image.url)
return obj.background_image.url
return None