223 lines
10 KiB
Python
223 lines
10 KiB
Python
"""
|
||
Сервис для получения URL изображений разных размеров.
|
||
Используется в шаблонах и представлениях для удобного доступа к разным версиям.
|
||
"""
|
||
import os
|
||
from django.conf import settings
|
||
from django.core.files.storage import default_storage
|
||
|
||
|
||
class ImageService:
|
||
"""
|
||
Сервис для работы с изображениями разных размеров.
|
||
Динамически строит URL на основе пути к оригинальному файлу.
|
||
"""
|
||
|
||
@staticmethod
|
||
def _get_config():
|
||
"""Получить конфигурацию из settings"""
|
||
return getattr(settings, 'IMAGE_PROCESSING_CONFIG', {})
|
||
|
||
@staticmethod
|
||
def _get_size_folders():
|
||
"""Получить папки для разных размеров из конфигурации"""
|
||
config = ImageService._get_config()
|
||
return config.get('folders', {
|
||
'thumbnail': 'thumbnails',
|
||
'medium': 'medium',
|
||
'large': 'large',
|
||
'original': 'originals',
|
||
})
|
||
|
||
@staticmethod
|
||
def _get_format_config(size_key):
|
||
"""Получить конфигурацию формата для заданного типа изображения"""
|
||
config = ImageService._get_config()
|
||
formats = config.get('formats', {})
|
||
return formats.get(size_key, {'format': 'JPEG', 'quality': 90})
|
||
|
||
@staticmethod
|
||
def _get_file_extension(size_key):
|
||
"""Получить расширение файла для заданного типа изображения"""
|
||
format_config = ImageService._get_format_config(size_key)
|
||
image_format = format_config.get('format', 'JPEG')
|
||
|
||
ext_map = {
|
||
'JPEG': 'jpg',
|
||
'WEBP': 'webp',
|
||
'PNG': 'png',
|
||
}
|
||
return ext_map.get(image_format, 'jpg')
|
||
|
||
@staticmethod
|
||
def get_url(original_image_path, size='medium'):
|
||
"""
|
||
Получает URL изображения нужного размера.
|
||
|
||
Работает с новой структурой:
|
||
- products/<entity_id>/<photo_id>/original.jpg
|
||
- products/<entity_id>/<photo_id>/large.webp
|
||
- products/<entity_id>/<photo_id>/medium.webp
|
||
- products/<entity_id>/<photo_id>/thumb.webp
|
||
|
||
Args:
|
||
original_image_path: Путь к оригинальному файлу (из models.image)
|
||
Обычно это путь к файлу 'original'
|
||
Пример: products/123/456/original.jpg
|
||
size: Размер ('original', 'large', 'medium', 'thumbnail')
|
||
По умолчанию 'medium'
|
||
|
||
Returns:
|
||
str: URL изображения или пустая строка если нет файла
|
||
"""
|
||
if not original_image_path:
|
||
return ''
|
||
|
||
try:
|
||
# Работаем с новой структурой: products/<entity_id>/<photo_id>/original.jpg
|
||
path_str = str(original_image_path)
|
||
parts = path_str.split('/')
|
||
|
||
if len(parts) >= 3:
|
||
# Извлекаем base_path, entity_id, photo_id из пути
|
||
base_path = parts[0] # products, kits, categories
|
||
entity_id = parts[1] # ID сущности
|
||
photo_id = parts[2] # ID фото
|
||
|
||
# Определяем размер в имени файла
|
||
filename = parts[-1] if parts else os.path.basename(path_str)
|
||
|
||
# Проверяем, является ли это новой структурой
|
||
if filename in ['original.jpg', 'large.webp', 'medium.webp', 'thumb.webp']:
|
||
# Это новая структура, заменяем только размер
|
||
ext_map = {
|
||
'original': 'jpg',
|
||
'large': 'webp',
|
||
'medium': 'webp',
|
||
'thumbnail': 'webp',
|
||
}
|
||
target_ext = ext_map.get(size, 'jpg')
|
||
|
||
# Переименовываем thumbnail в thumb
|
||
final_size_name = 'thumb' if size == 'thumbnail' else size
|
||
|
||
# Создаем путь в новой структуре
|
||
new_path = f"{base_path}/{entity_id}/{photo_id}/{final_size_name}.{target_ext}"
|
||
|
||
# Проверяем существование файла
|
||
if default_storage.exists(new_path):
|
||
return f"{settings.MEDIA_URL}{new_path}"
|
||
|
||
# Если файл не найден, пробуем с другим расширением
|
||
# Определяем расширение из конфигурации
|
||
format_config = ImageService._get_format_config(size)
|
||
image_format = format_config.get('format', 'JPEG')
|
||
|
||
ext_map_config = {
|
||
'JPEG': 'jpg',
|
||
'WEBP': 'webp',
|
||
'PNG': 'png',
|
||
}
|
||
target_ext = ext_map_config.get(image_format, 'jpg')
|
||
|
||
final_size_name = 'thumb' if size == 'thumbnail' else size
|
||
fallback_path = f"{base_path}/{entity_id}/{photo_id}/{final_size_name}.{target_ext}"
|
||
|
||
if default_storage.exists(fallback_path):
|
||
return f"{settings.MEDIA_URL}{fallback_path}"
|
||
|
||
return f"{settings.MEDIA_URL}{path_str}"
|
||
|
||
# Старая структура для совместимости
|
||
filename = os.path.basename(path_str)
|
||
|
||
# Определяем базовый путь (products, kits, categories)
|
||
if len(parts) > 0:
|
||
base_path = parts[0]
|
||
else:
|
||
base_path = 'products'
|
||
|
||
# Проверяем старый формат имени файла с расширением
|
||
# Поддерживаем jpg, webp, png расширения
|
||
if filename.endswith(('.jpg', '.webp', '.png')):
|
||
# Определяем расширение файла
|
||
file_ext = os.path.splitext(filename)[1] # .jpg, .webp и т.д.
|
||
filename_without_ext = filename[:-(len(file_ext))] # Имя без расширения
|
||
|
||
# Разделяем по последнему _ для получения base_filename и size_key
|
||
parts_of_name = filename_without_ext.rsplit('_', 1)
|
||
|
||
if len(parts_of_name) == 2:
|
||
base_filename, file_size_key = parts_of_name
|
||
# Это старый формат с явным указанием размера в имени
|
||
|
||
# Получаем расширение для целевого размера
|
||
target_ext = ImageService._get_file_extension(size)
|
||
|
||
# Строим папку
|
||
size_folders = ImageService._get_size_folders()
|
||
folder = size_folders.get(size, 'medium')
|
||
|
||
# Сначала пытаемся с правильным расширением из конфигурации
|
||
filename_new = f"{base_filename}_{size}.{target_ext}"
|
||
new_path_primary = f"{base_path}/{folder}/{filename_new}"
|
||
|
||
# Если файл существует - возвращаем его
|
||
if default_storage.exists(new_path_primary):
|
||
return f"{settings.MEDIA_URL}{new_path_primary}"
|
||
|
||
# Иначе пробуем старый формат (все .jpg) для совместимости
|
||
filename_fallback = f"{base_filename}_{size}.jpg"
|
||
new_path_fallback = f"{base_path}/{folder}/{filename_fallback}"
|
||
|
||
if default_storage.exists(new_path_fallback):
|
||
return f"{settings.MEDIA_URL}{new_path_fallback}"
|
||
|
||
# Если ничего не найдено, возвращаем путь с новым расширением (браузер покажет ошибку)
|
||
return f"{settings.MEDIA_URL}{new_path_primary}"
|
||
|
||
# Строим новый путь (для старых файлов без новой структуры)
|
||
size_folders = ImageService._get_size_folders()
|
||
folder = size_folders.get(size, 'medium')
|
||
new_path = f"{base_path}/{folder}/{filename}"
|
||
|
||
# Возвращаем URL
|
||
return f"{settings.MEDIA_URL}{new_path}"
|
||
except Exception:
|
||
return ''
|
||
|
||
@staticmethod
|
||
def get_thumbnail_url(original_image_path):
|
||
"""Получить URL миниатюры (150x150)"""
|
||
return ImageService.get_url(original_image_path, 'thumbnail')
|
||
|
||
@staticmethod
|
||
def get_medium_url(original_image_path):
|
||
"""Получить URL среднего размера (400x400)"""
|
||
return ImageService.get_url(original_image_path, 'medium')
|
||
|
||
@staticmethod
|
||
def get_large_url(original_image_path):
|
||
"""Получить URL большого размера (800x800)"""
|
||
return ImageService.get_url(original_image_path, 'large')
|
||
|
||
@staticmethod
|
||
def get_original_url(original_image_path):
|
||
"""Получить URL оригинального изображения"""
|
||
return ImageService.get_url(original_image_path, 'original')
|
||
|
||
@staticmethod
|
||
def get_all_urls(original_image_path):
|
||
"""
|
||
Получить все версии изображения.
|
||
|
||
Returns:
|
||
dict: {'original': url, 'thumbnail': url, 'medium': url, 'large': url}
|
||
"""
|
||
return {
|
||
'original': ImageService.get_original_url(original_image_path),
|
||
'thumbnail': ImageService.get_thumbnail_url(original_image_path),
|
||
'medium': ImageService.get_medium_url(original_image_path),
|
||
'large': ImageService.get_large_url(original_image_path),
|
||
}
|