Corp/backend/api/v1/serializers.py
2026-03-31 17:53:29 +03:00

175 lines
6.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import base64
import binascii
from io import BytesIO
from django.core.files.base import ContentFile
from PIL import Image
from rest_framework import serializers
class Base64ImageField(serializers.ImageField):
"""
Двухстороннее поле:
- input: принимает строку base64 или data URI (data:image/...;base64,xxxx)
- output: возвращает data URI (data:image/...;base64,xxxx)
Совместимо с Python 3.13 (без imghdr).
"""
default_ext = "jpg"
def _detect_ext_mime(self, content: bytes) -> tuple[str, str]:
"""Определяем расширение и MIME через Pillow."""
try:
im = Image.open(BytesIO(content))
fmt = (im.format or "").upper()
ext_map = {
"JPEG": "jpg",
"JPG": "jpg",
"PNG": "png",
"WEBP": "webp",
"GIF": "gif",
"BMP": "bmp",
"TIFF": "tiff",
"ICO": "ico",
}
ext = ext_map.get(fmt, self.default_ext)
mime = Image.MIME.get(fmt, "image/jpeg")
return ext, mime
except Exception:
return self.default_ext, "image/jpeg"
def to_internal_value(self, data):
import logging
logger = logging.getLogger(__name__)
logger.info("[Base64ImageField] to_internal_value вызван:")
logger.info(f" - data type: {type(data).__name__}")
logger.info(f" - data repr: {repr(data)[:200]}")
logger.info(f" - data is None: {data is None}")
logger.info(f" - data == '': {data == ''}")
logger.info(f" - data == b'': {data == b''}")
# Принимаем None/пустое
if data in (None, "", b""):
logger.info(
"[Base64ImageField] Пустое значение, передаем в parent"
)
return super().to_internal_value(data)
if isinstance(data, str):
logger.info(
f"[Base64ImageField] Обрабатываем строку, "
f"length: {len(data)}"
)
# Срезаем префикс data:image/...;base64, если есть
if data.startswith("data:image"):
logger.info(
"[Base64ImageField] Найден data URI префикс"
)
try:
_, data = data.split(";base64,", 1)
logger.info(
f"[Base64ImageField] Префикс обрезан, "
f"осталось: {len(data)} символов"
)
except ValueError:
logger.error(
"[Base64ImageField] Некорректный формат data URI"
)
raise serializers.ValidationError(
"Некорректный формат data URI."
)
try:
logger.info("[Base64ImageField] Декодируем base64...")
decoded = base64.b64decode(data)
logger.info(
f"[Base64ImageField] Base64 декодирован, "
f"размер: {len(decoded)} байт"
)
except (TypeError, ValueError, binascii.Error) as e:
logger.error(
f"[Base64ImageField] Ошибка декодирования "
f"base64: {e}"
)
raise serializers.ValidationError("Невалидный base64.")
logger.info(
"[Base64ImageField] Определяем формат изображения..."
)
ext, _ = self._detect_ext_mime(decoded)
logger.info(f"[Base64ImageField] Формат определен: {ext}")
file = ContentFile(decoded, name=f"upload.{ext}")
logger.info(
"[Base64ImageField] ContentFile создан, "
"передаем в parent ImageField"
)
result = super().to_internal_value(file)
logger.info(
f"[Base64ImageField] Parent validation passed, "
f"result type: {type(result)}"
)
return result
# Если это не строка (например, InMemoryUploadedFile)
logger.info(
"[Base64ImageField] Не строка, передаем в parent as-is"
)
# КРИТИЧНО: проверяем, не пустой ли файл
if hasattr(data, 'size') and data.size == 0:
logger.warning(
"[Base64ImageField] Пустой файл (size=0), "
"возвращаем None"
)
return None
if hasattr(data, 'read'):
# Проверяем содержимое файла
try:
content = data.read()
if hasattr(data, 'seek'):
data.seek(0)
if not content or len(content) == 0:
logger.warning(
"[Base64ImageField] Файл пустой (len=0), "
"возвращаем None"
)
return None
except Exception as e:
logger.error(
f"[Base64ImageField] Ошибка чтения файла: {e}"
)
result = super().to_internal_value(data)
logger.info(
f"[Base64ImageField] Parent validation passed, "
f"result type: {type(result)}"
)
return result
return super().to_internal_value(data)
def to_representation(self, value):
if not value:
return ""
# Получаем байты
try:
file_obj = getattr(value, "file", None) or value
content = file_obj.read()
if hasattr(file_obj, "seek"):
file_obj.seek(0)
except Exception:
try:
with open(value.path, "rb") as f:
content = f.read()
except Exception:
return ""
b64 = base64.b64encode(content).decode("ascii")
_, mime = self._detect_ext_mime(content)
return f"data:{mime};base64,{b64}"