diff --git a/myproject/products/models/base.py b/myproject/products/models/base.py index 8901cfb..314fee4 100644 --- a/myproject/products/models/base.py +++ b/myproject/products/models/base.py @@ -193,6 +193,11 @@ class BaseProductEntity(models.Model): """Физическое удаление из БД (необратимо! только для старых товаров)""" super().delete() + @property + def is_active(self): + """Возвращает True если товар активен""" + return self.status == 'active' + def save(self, *args, **kwargs): """ Автогенерация slug из name если не задан. diff --git a/myproject/products/models/photos.py b/myproject/products/models/photos.py index d8c0b25..15aed86 100644 --- a/myproject/products/models/photos.py +++ b/myproject/products/models/photos.py @@ -124,10 +124,14 @@ class BasePhoto(models.Model): Используется только если Celery недоступен. """ from ..utils.image_processor import ImageProcessor + from django.core.files.storage import default_storage entity = self.get_entity() entity_type = self.get_entity_type() + # Сохраняем путь к временному файлу до перезаписи поля image + temp_path = getattr(temp_image, 'name', None) + processed_paths = ImageProcessor.process_image( temp_image, entity_type, @@ -141,10 +145,18 @@ class BasePhoto(models.Model): super().save(update_fields=['image', 'quality_level', 'quality_warning']) + # Удаляем временный файл из temp после успешной обработки + try: + if temp_path and default_storage.exists(temp_path): + default_storage.delete(temp_path) + except Exception: + pass + def delete(self, *args, **kwargs): """Удаляет все версии изображения при удалении фото""" import logging from ..utils.image_processor import ImageProcessor + from django.core.files.storage import default_storage logger = logging.getLogger(__name__) @@ -159,6 +171,15 @@ class BasePhoto(models.Model): entity_id=entity.id, photo_id=self.id ) + + # Если фото так и осталось во временном пути (обработка не завершилась) — удаляем temp файл + try: + if '/temp/' in self.image.name and default_storage.exists(self.image.name): + default_storage.delete(self.image.name) + logger.info(f"[{self.__class__.__name__}.delete] Deleted temp file: {self.image.name}") + except Exception as del_exc: + logger.warning(f"[{self.__class__.__name__}.delete] Could not delete temp file {self.image.name}: {del_exc}") + logger.info(f"[{self.__class__.__name__}.delete] ✓ Все версии изображения удалены") except Exception as e: logger.error( diff --git a/myproject/products/tasks.py b/myproject/products/tasks.py index abece49..0ecb113 100644 --- a/myproject/products/tasks.py +++ b/myproject/products/tasks.py @@ -8,6 +8,7 @@ import logging from celery import shared_task from django.db import connection from django.apps import apps +from django.core.files.storage import default_storage logger = logging.getLogger(__name__) @@ -53,6 +54,9 @@ def process_product_photo_async(self, photo_id, photo_model_class, schema_name): logger.warning(f"[Celery] Photo {photo_id} has no image file") return {'status': 'error', 'reason': 'no_image'} + # Сохраняем путь к временному файлу до перезаписи поля image + temp_path = photo_obj.image.name + # Получаем entity type для правильного пути сохранения entity_type = photo_obj.get_entity_type() @@ -73,6 +77,14 @@ def process_product_photo_async(self, photo_id, photo_model_class, schema_name): photo_obj.quality_warning = processed_paths.get('quality_warning', False) photo_obj.save(update_fields=['image', 'quality_level', 'quality_warning']) + # Удаляем временный файл из temp после успешной обработки + try: + if temp_path and default_storage.exists(temp_path): + default_storage.delete(temp_path) + logger.info(f"[Celery] Deleted temp file: {temp_path}") + except Exception as del_exc: + logger.warning(f"[Celery] Could not delete temp file {temp_path}: {del_exc}") + logger.info(f"[Celery] ✓ Photo {photo_id} processed successfully " f"(quality: {processed_paths.get('quality_level')})") diff --git a/myproject/products/templates/products/product_list.html b/myproject/products/templates/products/product_list.html index 4ce1af1..597b68f 100644 --- a/myproject/products/templates/products/product_list.html +++ b/myproject/products/templates/products/product_list.html @@ -72,10 +72,14 @@ {% endif %}
| Фото | +Название | +Артикул | +Тип | +Категория | +Цена | +В наличии | +Компоненты | +Статус | +Действия | +
|---|---|---|---|---|---|---|---|---|---|
|
+ {% with photo=item.photos.first %}
+
+
+ {% endwith %}
+ |
+ + {{ item.name }} + | +{{ item.sku }} | ++ {% if item.item_type == 'product' %} + Товар + {% else %} + Комплект + {% endif %} + | ++ {% for category in item.categories.all %} + {{ category.name }} + {% empty %} + - + {% endfor %} + | +
+ {% if item.sale_price %}
+ {{ item.price|floatformat:2 }} руб. + {{ item.sale_price|floatformat:2 }} руб. + {% else %} + {{ item.price|floatformat:2 }} руб. + {% endif %} + |
+ + {% if item.item_type == 'product' %} + {% if item.in_stock %} + Да + {% else %} + Нет + {% endif %} + {% else %} + - + {% endif %} + | ++ {% if item.item_type == 'kit' %} + {{ item.get_total_components_count }} шт + {% else %} + - + {% endif %} + | ++ {{ item.get_status_display }} + | +
+
+
+ {% if item.item_type == 'product' and perms.products.change_product %}
+
+ {% elif item.item_type == 'kit' and perms.products.change_productkit %}
+
+ {% endif %}
+ {% if item.item_type == 'product' and perms.products.delete_product %}
+
+ {% elif item.item_type == 'kit' and perms.products.delete_productkit %}
+
+ {% endif %}
+
+ |
+
Товары или комплекты не найдены.
+