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):