Улучшение системы работы с фото: добавлена команда очистки битых записей и оптимизация обработки изображений

This commit is contained in:
2026-01-06 09:25:37 +03:00
parent 0f19542ac9
commit 288716deba
14 changed files with 535 additions and 122 deletions

View File

@@ -159,15 +159,15 @@ class ImageProcessor:
def _resize_image(img, size):
"""
Изменяет размер изображения с сохранением пропорций.
Если исходное изображение меньше целевого размера, добавляет белый фон.
Если больше - уменьшает с сохранением пропорций.
НЕ увеличивает маленькие изображения (сохраняет качество).
Создает адаптивный квадрат по размеру реального изображения.
Args:
img: PIL Image object
size: Кортеж (width, height)
size: Кортеж (width, height) - максимальный целевой размер
Returns:
PIL Image object с новым размером
PIL Image object - квадратное изображение с минимальным белым фоном
"""
# Копируем изображение, чтобы не модифицировать оригинал
img_copy = img.copy()
@@ -190,12 +190,14 @@ class ImageProcessor:
if img_copy.width > new_width or img_copy.height > new_height:
img_copy = img_copy.resize((new_width, new_height), Image.Resampling.LANCZOS)
# Создаем новое изображение нужного размера с белым фоном
new_img = Image.new('RGB', size, (255, 255, 255))
# Создаем адаптивный квадрат по размеру реального изображения (а не по конфигурации)
# Это позволяет избежать огромных белых полей для маленьких фото
square_size = max(img_copy.width, img_copy.height)
new_img = Image.new('RGB', (square_size, square_size), (255, 255, 255))
# Центрируем исходное изображение на белом фоне
offset_x = (size[0] - img_copy.width) // 2
offset_y = (size[1] - img_copy.height) // 2
offset_x = (square_size - img_copy.width) // 2
offset_y = (square_size - img_copy.height) // 2
new_img.paste(img_copy, (offset_x, offset_y))
return new_img

View File

@@ -105,17 +105,16 @@ class ImageService:
# Используем default_storage.url() для корректной работы с TenantAwareFileSystemStorage
# Это гарантирует что URL будет содержать tenant_id если необходимо
# Проверяем существование файла - если не найден, возвращаем пустую строку
# (для обработанных файлов миниатюра должна существовать)
# Проверяем существование файла - если не найден, возвращаем оригинал как fallback
if default_storage.exists(file_path):
url = default_storage.url(file_path)
logger.debug(f"[ImageService] Returning {size} URL: {file_path} -> {url}")
return url
else:
# Файл нужного размера не найден - возвращаем пустую строку
# (файл обработан, но миниатюра не создана - это ошибка)
logger.warning(f"[ImageService] {size} file not found: {file_path}, file should be processed")
return ''
# Файл нужного размера не найден - возвращаем оригинал как fallback
# (файл может быть загружен до внедрения системы обработки или обработка не завершена)
logger.warning(f"[ImageService] {size} file not found: {file_path}, using original as fallback")
return default_storage.url(str(original_image_path))
except Exception as e:
# В случае ошибки возвращаем оригинал