303 lines
9.3 KiB
Python
303 lines
9.3 KiB
Python
"""
|
||
Сериализаторы для учебных материалов.
|
||
"""
|
||
from rest_framework import serializers
|
||
from django.db import models
|
||
from .models import Material, MaterialFolder, MaterialTag, MaterialAccess, StorageQuota
|
||
from apps.users.serializers import UserSerializer
|
||
|
||
|
||
class MaterialTagSerializer(serializers.ModelSerializer):
|
||
"""Сериализатор тега."""
|
||
|
||
class Meta:
|
||
model = MaterialTag
|
||
fields = [
|
||
'id',
|
||
'name',
|
||
'slug',
|
||
'color',
|
||
'materials_count'
|
||
]
|
||
read_only_fields = ['materials_count']
|
||
|
||
|
||
class MaterialFolderSerializer(serializers.ModelSerializer):
|
||
"""Сериализатор папки."""
|
||
|
||
owner = UserSerializer(read_only=True)
|
||
path = serializers.SerializerMethodField()
|
||
|
||
class Meta:
|
||
model = MaterialFolder
|
||
fields = [
|
||
'id',
|
||
'name',
|
||
'description',
|
||
'owner',
|
||
'parent',
|
||
'path',
|
||
'is_public',
|
||
'materials_count',
|
||
'created_at',
|
||
'updated_at'
|
||
]
|
||
read_only_fields = ['owner', 'materials_count', 'created_at', 'updated_at']
|
||
|
||
def get_path(self, obj):
|
||
"""Получить путь папки."""
|
||
return obj.get_path()
|
||
|
||
|
||
class MaterialSerializer(serializers.ModelSerializer):
|
||
"""Сериализатор материала."""
|
||
|
||
owner = UserSerializer(read_only=True)
|
||
folder = MaterialFolderSerializer(read_only=True)
|
||
tags = MaterialTagSerializer(many=True, read_only=True)
|
||
shared_with = UserSerializer(many=True, read_only=True)
|
||
|
||
class Meta:
|
||
model = Material
|
||
fields = [
|
||
'id',
|
||
'title',
|
||
'description',
|
||
'file',
|
||
'file_name',
|
||
'file_size',
|
||
'file_type',
|
||
'url',
|
||
'material_type',
|
||
'owner',
|
||
'folder',
|
||
'tags',
|
||
'lesson',
|
||
'homework',
|
||
'access_type',
|
||
'allow_download',
|
||
'is_featured',
|
||
'shared_with',
|
||
'views_count',
|
||
'downloads_count',
|
||
'created_at',
|
||
'updated_at'
|
||
]
|
||
read_only_fields = [
|
||
'owner',
|
||
'file_name',
|
||
'file_size',
|
||
'file_type',
|
||
'views_count',
|
||
'downloads_count',
|
||
'created_at',
|
||
'updated_at'
|
||
]
|
||
|
||
|
||
class MaterialListSerializer(serializers.ModelSerializer):
|
||
"""Сериализатор списка материалов (упрощенный)."""
|
||
|
||
owner = UserSerializer(read_only=True)
|
||
tags = MaterialTagSerializer(many=True, read_only=True)
|
||
file_url = serializers.SerializerMethodField()
|
||
|
||
class Meta:
|
||
model = Material
|
||
fields = [
|
||
'id',
|
||
'title',
|
||
'description',
|
||
'file',
|
||
'file_url',
|
||
'file_name',
|
||
'file_size',
|
||
'file_type',
|
||
'url',
|
||
'material_type',
|
||
'owner',
|
||
'folder',
|
||
'tags',
|
||
'access_type',
|
||
'is_featured',
|
||
'views_count',
|
||
'created_at'
|
||
]
|
||
|
||
def get_file_url(self, obj):
|
||
"""Полный URL файла для превью (изображения, видео)."""
|
||
if obj.file:
|
||
request = self.context.get('request')
|
||
if request:
|
||
return request.build_absolute_uri(obj.file.url)
|
||
return obj.file.url
|
||
return None
|
||
|
||
|
||
class MaterialCreateSerializer(serializers.ModelSerializer):
|
||
"""Сериализатор создания материала."""
|
||
|
||
folder_id = serializers.IntegerField(required=False, allow_null=True)
|
||
tag_ids = serializers.ListField(
|
||
child=serializers.IntegerField(),
|
||
required=False,
|
||
allow_empty=True
|
||
)
|
||
|
||
class Meta:
|
||
model = Material
|
||
fields = [
|
||
'title',
|
||
'description',
|
||
'file',
|
||
'url',
|
||
'material_type',
|
||
'folder_id',
|
||
'tag_ids',
|
||
'lesson',
|
||
'homework',
|
||
'access_type',
|
||
'allow_download',
|
||
'is_featured'
|
||
]
|
||
|
||
def validate(self, attrs):
|
||
"""Валидация."""
|
||
from .services import StorageService
|
||
|
||
# Проверяем что указан file или url
|
||
if not attrs.get('file') and not attrs.get('url'):
|
||
raise serializers.ValidationError({
|
||
'file': 'Необходимо указать файл или ссылку'
|
||
})
|
||
|
||
# Проверяем квоту если загружается файл
|
||
if attrs.get('file'):
|
||
user = self.context['request'].user
|
||
file_size = attrs['file'].size
|
||
|
||
# Используем сервис для проверки лимита
|
||
can_upload, error_message, warning_message = StorageService.check_storage_limit(user, file_size)
|
||
|
||
if not can_upload:
|
||
raise serializers.ValidationError({
|
||
'file': error_message
|
||
})
|
||
|
||
# Сохраняем предупреждение в контексте для использования в create
|
||
if warning_message:
|
||
self.context['storage_warning'] = warning_message
|
||
|
||
return attrs
|
||
|
||
def create(self, validated_data):
|
||
"""Создание материала."""
|
||
folder_id = validated_data.pop('folder_id', None)
|
||
tag_ids = validated_data.pop('tag_ids', [])
|
||
user = self.context['request'].user
|
||
|
||
# Получаем информацию о файле
|
||
file = validated_data.get('file')
|
||
if file:
|
||
validated_data['file_name'] = file.name
|
||
validated_data['file_size'] = file.size
|
||
validated_data['file_type'] = file.content_type
|
||
|
||
# Создаем материал с owner (owner будет переопределен в perform_create, но нужен для создания)
|
||
material = Material.objects.create(
|
||
owner=user,
|
||
folder_id=folder_id,
|
||
**validated_data
|
||
)
|
||
|
||
# Добавляем теги
|
||
if tag_ids:
|
||
tags = MaterialTag.objects.filter(id__in=tag_ids)
|
||
material.tags.set(tags)
|
||
|
||
# Обновляем квоту если загружен файл
|
||
if file:
|
||
from .services import StorageService
|
||
StorageService.add_file_usage(user, file.size)
|
||
|
||
return material
|
||
|
||
|
||
class StorageQuotaSerializer(serializers.ModelSerializer):
|
||
"""Сериализатор квоты хранилища."""
|
||
|
||
user = UserSerializer(read_only=True)
|
||
used_percentage = serializers.SerializerMethodField()
|
||
available_space = serializers.SerializerMethodField()
|
||
total_quota_mb = serializers.SerializerMethodField()
|
||
used_space_mb = serializers.SerializerMethodField()
|
||
available_space_mb = serializers.SerializerMethodField()
|
||
is_warning = serializers.SerializerMethodField()
|
||
is_critical = serializers.SerializerMethodField()
|
||
|
||
class Meta:
|
||
model = StorageQuota
|
||
fields = [
|
||
'id',
|
||
'user',
|
||
'total_quota',
|
||
'used_space',
|
||
'used_percentage',
|
||
'available_space',
|
||
'total_quota_mb',
|
||
'used_space_mb',
|
||
'available_space_mb',
|
||
'is_warning',
|
||
'is_critical',
|
||
'created_at',
|
||
'updated_at'
|
||
]
|
||
read_only_fields = ['user', 'used_space', 'created_at', 'updated_at']
|
||
|
||
def get_used_percentage(self, obj):
|
||
"""Процент использования."""
|
||
return round(obj.get_used_percentage(), 2)
|
||
|
||
def get_available_space(self, obj):
|
||
"""Доступное пространство."""
|
||
return obj.get_available_space()
|
||
|
||
def get_total_quota_mb(self, obj):
|
||
"""Лимит в МБ."""
|
||
return round(obj.total_quota / (1024 * 1024), 2)
|
||
|
||
def get_used_space_mb(self, obj):
|
||
"""Использовано в МБ."""
|
||
return round(obj.used_space / (1024 * 1024), 2)
|
||
|
||
def get_available_space_mb(self, obj):
|
||
"""Доступно в МБ."""
|
||
return round(obj.get_available_space() / (1024 * 1024), 2)
|
||
|
||
def get_is_warning(self, obj):
|
||
"""Предупреждение (80% и выше)."""
|
||
return obj.get_used_percentage() >= 80
|
||
|
||
def get_is_critical(self, obj):
|
||
"""Критическое состояние (90% и выше)."""
|
||
return obj.get_used_percentage() >= 90
|
||
|
||
|
||
class MaterialAccessSerializer(serializers.ModelSerializer):
|
||
"""Сериализатор лога доступа."""
|
||
|
||
user = UserSerializer(read_only=True)
|
||
material = MaterialListSerializer(read_only=True)
|
||
|
||
class Meta:
|
||
model = MaterialAccess
|
||
fields = [
|
||
'id',
|
||
'material',
|
||
'user',
|
||
'action',
|
||
'ip_address',
|
||
'created_at'
|
||
]
|
||
read_only_fields = ['user', 'created_at']
|