diff --git a/myproject/pos/views.py b/myproject/pos/views.py
index e876d3d..51a9a5e 100644
--- a/myproject/pos/views.py
+++ b/myproject/pos/views.py
@@ -99,7 +99,12 @@ def get_showcase_kits_for_pos():
for photo in photos:
if photo.kit_id not in kit_photos:
- kit_photos[photo.kit_id] = photo.get_thumbnail_url()
+ if photo and photo.image:
+ thumbnail_url = photo.get_thumbnail_url()
+ # Если миниатюра не найдена, возвращаем None (без фото)
+ if not thumbnail_url:
+ thumbnail_url = None
+ kit_photos[photo.kit_id] = thumbnail_url
# Формируем результат
showcase_kits = []
@@ -776,7 +781,12 @@ def get_items_api(request):
for p in products_qs:
image_url = None
if hasattr(p, 'first_photo_list') and p.first_photo_list:
- image_url = p.first_photo_list[0].get_thumbnail_url()
+ photo = p.first_photo_list[0]
+ if photo and photo.image:
+ image_url = photo.get_thumbnail_url()
+ # Если миниатюра не найдена, возвращаем None (без фото)
+ if not image_url:
+ image_url = None
available = p.available_qty
reserved = p.reserved_qty
@@ -827,7 +837,12 @@ def get_items_api(request):
for k in kits_qs:
image_url = None
if hasattr(k, 'first_photo_list') and k.first_photo_list:
- image_url = k.first_photo_list[0].get_thumbnail_url()
+ photo = k.first_photo_list[0]
+ if photo and photo.image:
+ image_url = photo.get_thumbnail_url()
+ # Если миниатюра не найдена, возвращаем None (без фото)
+ if not image_url:
+ image_url = None
kits.append({
'id': k.id,
@@ -893,8 +908,15 @@ def get_product_kit_details(request, kit_id):
'price': str(ki.product.actual_price)
} for ki in kit.kit_items.all()]
- # Фото
- photo_url = kit.photos.first().image.url if kit.photos.exists() else None
+ # Фото (используем миниатюру для быстрой загрузки)
+ photo_url = None
+ if kit.photos.exists():
+ first_photo = kit.photos.first()
+ if first_photo and first_photo.image:
+ photo_url = first_photo.get_thumbnail_url()
+ # Если миниатюра не найдена, возвращаем None (без фото)
+ if not photo_url:
+ photo_url = None
return JsonResponse({
'success': True,
diff --git a/myproject/products/templates/products/product_form.html b/myproject/products/templates/products/product_form.html
index 3161e8f..4e9c449 100644
--- a/myproject/products/templates/products/product_form.html
+++ b/myproject/products/templates/products/product_form.html
@@ -164,6 +164,9 @@
+
+
+
{% if object and product_photos %}
@@ -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} фото успешно удалено!
`;
- 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';
diff --git a/myproject/products/utils/image_service.py b/myproject/products/utils/image_service.py
index d69a248..fea132c 100644
--- a/myproject/products/utils/image_service.py
+++ b/myproject/products/utils/image_service.py
@@ -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):
diff --git a/myproject/products/utils/storage.py b/myproject/products/utils/storage.py
index b4ab294..874e80b 100644
--- a/myproject/products/utils/storage.py
+++ b/myproject/products/utils/storage.py
@@ -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):