""" Сервис для получения URL изображений разных размеров. Используется в шаблонах и представлениях для удобного доступа к разным версиям. """ import os from django.conf import settings from django.core.files.storage import default_storage class ImageService: """ Сервис для работы с изображениями разных размеров. Динамически строит URL на основе пути к оригинальному файлу. МУЛЬТИТЕНАНТНОСТЬ: На диске файлы хранятся как: tenants/{tenant_id}/products/{entity_id}/{photo_id}/{size}.ext В БД сохраняется путь БЕЗ tenant_id: products/{entity_id}/{photo_id}/{size}.ext TenantAwareFileSystemStorage автоматически добавляет/удаляет tenant_id при работе с файлами. ImageService работает с путями из БД и генерирует URL, при необходимости системе видны файлы только своего тенанта. """ # Константы для маппинга форматов и расширений файлов FORMAT_EXTENSIONS = { 'JPEG': 'jpg', 'WEBP': 'webp', 'PNG': 'png', } @staticmethod def _get_config(): """Получить конфигурацию из settings""" return getattr(settings, 'IMAGE_PROCESSING_CONFIG', {}) @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(image_format): """Получить расширение файла для заданного формата""" return ImageService.FORMAT_EXTENSIONS.get(image_format, 'jpg') @staticmethod def _normalize_size_name(size_key): """Преобразовать 'thumbnail' в 'thumb' для имени файла""" return 'thumb' if size_key == 'thumbnail' else size_key @staticmethod def get_url(original_image_path, size='medium'): """ Получает URL изображения нужного размера. Структура хранения: base_path/entity_id/photo_id/size.ext Пример: products/123/456/medium.webp Args: original_image_path: Путь к оригинальному файлу (из models.image) Пример: products/123/456/original.jpg size: Размер ('original', 'large', 'medium', 'thumbnail') По умолчанию 'medium' Returns: str: URL изображения или пустая строка если нет файла """ if not original_image_path: return '' try: path_str = str(original_image_path) parts = path_str.split('/') if len(parts) < 3: return '' # Извлекаем base_path, entity_id, photo_id из пути base_path = parts[0] # products, kits, categories entity_id = parts[1] # ID сущности photo_id = parts[2] # ID фото # Определяем расширение из конфигурации format_config = ImageService._get_format_config(size) image_format = format_config.get('format', 'JPEG') extension = ImageService._get_file_extension(image_format) # Преобразуем thumbnail в thumb final_size_name = ImageService._normalize_size_name(size) # Создаем путь и возвращаем URL file_path = f"{base_path}/{entity_id}/{photo_id}/{final_size_name}.{extension}" return f"{settings.MEDIA_URL}{file_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), }