Исправление отображения фото в POS и улучшение обработки изображений
- Исправлен POS для использования миниатюр вместо оригиналов для быстрой загрузки - Убран fallback на оригиналы - показываем миниатюру или ничего (лучше видно ошибки) - Исправлен ImageService - возвращает пустую строку если миниатюра обработанного файла не найдена - Исправлена ошибка JavaScript при массовом удалении фото (insertAdjacentElement на null) - Добавлен контейнер photos-messages-container для надежного отображения сообщений - Улучшено логирование ImageService для отладки путей к файлам - Добавлена проверка exists() с детальным логированием в TenantAwareFileSystemStorage
This commit is contained in:
@@ -164,6 +164,9 @@
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- Контейнер для сообщений об операциях с фото -->
|
||||
<div id="photos-messages-container"></div>
|
||||
|
||||
<!-- Существующие фотографии (только при редактировании) -->
|
||||
{% if object and product_photos %}
|
||||
<div class="mb-4">
|
||||
@@ -342,7 +345,10 @@
|
||||
|
||||
// Скрываем блок если фотографий больше нет
|
||||
if (newCount === 0) {
|
||||
document.querySelector('[id="photos-count"]').closest('.mb-3').parentElement.style.display = 'none';
|
||||
const photosSection = document.querySelector('#photos-grid')?.closest('.mb-4');
|
||||
if (photosSection) {
|
||||
photosSection.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Показываем сообщение об успехе
|
||||
@@ -350,7 +356,14 @@
|
||||
alert.className = 'alert alert-success alert-dismissible fade show';
|
||||
alert.innerHTML = `✓ ${data.deleted} фото успешно удалено!
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>`;
|
||||
document.querySelector('.mb-4.p-3.bg-light.rounded').insertAdjacentElement('beforebegin', alert);
|
||||
|
||||
// Вставляем сообщение в специальный контейнер, который всегда существует
|
||||
const messagesContainer = document.getElementById('photos-messages-container');
|
||||
if (messagesContainer) {
|
||||
// Очищаем предыдущие сообщения и добавляем новое
|
||||
messagesContainer.innerHTML = '';
|
||||
messagesContainer.appendChild(alert);
|
||||
}
|
||||
|
||||
// Скрываем кнопку
|
||||
deleteBtn.style.display = 'none';
|
||||
|
||||
@@ -3,9 +3,12 @@
|
||||
Используется в шаблонах и представлениях для удобного доступа к разным версиям.
|
||||
"""
|
||||
import os
|
||||
import logging
|
||||
from django.conf import settings
|
||||
from django.core.files.storage import default_storage
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ImageService:
|
||||
"""
|
||||
@@ -63,7 +66,7 @@ class ImageService:
|
||||
По умолчанию 'medium'
|
||||
|
||||
Returns:
|
||||
str: URL изображения или пустая строка если нет файла
|
||||
str: URL изображения нужного размера, или оригинал если размер не найден
|
||||
"""
|
||||
if not original_image_path:
|
||||
return ''
|
||||
@@ -80,6 +83,10 @@ class ImageService:
|
||||
if 'temp' in parts:
|
||||
return default_storage.url(path_str)
|
||||
|
||||
# Если запрашивается оригинал, возвращаем его напрямую
|
||||
if size == 'original':
|
||||
return default_storage.url(path_str)
|
||||
|
||||
# Извлекаем base_path, entity_id, photo_id из пути
|
||||
base_path = parts[0] # products, kits, categories
|
||||
entity_id = parts[1] # ID сущности
|
||||
@@ -98,10 +105,25 @@ class ImageService:
|
||||
|
||||
# Используем default_storage.url() для корректной работы с TenantAwareFileSystemStorage
|
||||
# Это гарантирует что URL будет содержать tenant_id если необходимо
|
||||
return default_storage.url(file_path)
|
||||
# Проверяем существование файла - если не найден, возвращаем пустую строку
|
||||
# (для обработанных файлов миниатюра должна существовать)
|
||||
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 ''
|
||||
|
||||
except Exception:
|
||||
return ''
|
||||
except Exception as e:
|
||||
# В случае ошибки возвращаем оригинал
|
||||
logger.warning(f"[ImageService] Error getting {size} URL: {e}, using original as fallback")
|
||||
try:
|
||||
return default_storage.url(str(original_image_path))
|
||||
except Exception:
|
||||
return ''
|
||||
|
||||
@staticmethod
|
||||
def get_thumbnail_url(original_image_path):
|
||||
|
||||
@@ -204,8 +204,15 @@ class TenantAwareFileSystemStorage(FileSystemStorage):
|
||||
|
||||
# Иначе добавляем tenant_id
|
||||
tenant_aware_name = self._get_tenant_path(name)
|
||||
# Получаем полный путь для отладки
|
||||
full_path = super().path(tenant_aware_name)
|
||||
result = super().exists(tenant_aware_name)
|
||||
logger.info(f"[Storage] exists: {name} → {tenant_aware_name} → {result}")
|
||||
logger.info(f"[Storage] exists: {name} → {tenant_aware_name} → {full_path} → {result}")
|
||||
# Дополнительная проверка через os.path.exists для отладки
|
||||
import os
|
||||
os_result = os.path.exists(full_path)
|
||||
if result != os_result:
|
||||
logger.warning(f"[Storage] Mismatch! storage.exists()={result}, os.path.exists()={os_result} for {full_path}")
|
||||
return result
|
||||
|
||||
def url(self, name):
|
||||
|
||||
Reference in New Issue
Block a user