Изменение структуры хранения изображений и исправление ошибки дублирования ID

This commit is contained in:
2025-10-25 19:21:32 +03:00
parent 2f557f3f9b
commit a55d0405ed
3 changed files with 274 additions and 153 deletions

View File

@@ -909,7 +909,7 @@ class ProductPhoto(models.Model):
"""
product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='photos',
verbose_name="Товар")
image = models.ImageField(upload_to='products/originals/', verbose_name="Оригинальное фото")
image = models.ImageField(upload_to='products/temp/', verbose_name="Оригинальное фото")
order = models.PositiveIntegerField(default=0, verbose_name="Порядок")
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания")
@@ -928,31 +928,45 @@ class ProductPhoto(models.Model):
from .utils.image_processor import ImageProcessor
is_new = not self.pk
old_image_path = None
# Если это обновление существующего объекта, сохраняем старый путь для удаления
if not is_new:
try:
old_obj = ProductPhoto.objects.get(pk=self.pk)
if old_obj.image and old_obj.image != self.image:
old_image_path = old_obj.image.name
except ProductPhoto.DoesNotExist:
pass
# Если было загружено новое изображение
if self.image and (is_new or old_image_path):
# Обрабатываем изображение с использованием slug товара как идентификатора
# slug гарантирует уникальность и читаемость имени файла
identifier = self.product.slug
processed_paths = ImageProcessor.process_image(self.image, 'products', identifier=identifier)
# Сохраняем только путь к оригиналу в поле image
# Если это новый объект с изображением, нужно сначала сохранить без изображения, чтобы получить ID
if is_new and self.image:
# Сохраняем объект без изображения, чтобы получить ID
temp_image = self.image
self.image = None
super().save(*args, **kwargs)
# Теперь обрабатываем изображение с известными ID
processed_paths = ImageProcessor.process_image(temp_image, 'products', entity_id=self.product.id, photo_id=self.id)
self.image = processed_paths['original']
# Обновляем только поле image, чтобы избежать рекурсии и дублирования ID
super().save(update_fields=['image'])
else:
# Проверяем старый путь для удаления, если это обновление
old_image_path = None
if self.pk:
try:
old_obj = ProductPhoto.objects.get(pk=self.pk)
if old_obj.image and old_obj.image != self.image:
old_image_path = old_obj.image.name
except ProductPhoto.DoesNotExist:
pass
# Проверяем, нужно ли обрабатывать изображение
if self.image and old_image_path:
# Обновление существующего изображения
processed_paths = ImageProcessor.process_image(self.image, 'products', entity_id=self.product.id, photo_id=self.id)
self.image = processed_paths['original']
# Удаляем старые версии если это обновление
if old_image_path:
ImageProcessor.delete_all_versions('products', old_image_path)
super().save(*args, **kwargs)
# Удаляем старые версии
ImageProcessor.delete_all_versions('products', old_image_path, entity_id=self.product.id, photo_id=self.id)
# Обновляем только поле image, чтобы избежать рекурсии
super().save(update_fields=['image'])
else:
# Просто сохраняем без обработки изображения
super().save(*args, **kwargs)
def delete(self, *args, **kwargs):
"""Удаляет все версии изображения при удалении фото"""
@@ -964,7 +978,7 @@ class ProductPhoto(models.Model):
if self.image:
try:
logger.info(f"[ProductPhoto.delete] Удаляем изображение: {self.image.name}")
ImageProcessor.delete_all_versions('products', self.image.name)
ImageProcessor.delete_all_versions('products', self.image.name, entity_id=self.product.id, photo_id=self.id)
logger.info(f"[ProductPhoto.delete] ✓ Все версии изображения удалены")
except Exception as e:
logger.error(f"[ProductPhoto.delete] ✗ Ошибка при удалении версий: {str(e)}", exc_info=True)
@@ -999,7 +1013,7 @@ class ProductKitPhoto(models.Model):
"""
kit = models.ForeignKey(ProductKit, on_delete=models.CASCADE, related_name='photos',
verbose_name="Комплект")
image = models.ImageField(upload_to='kits/originals/', verbose_name="Оригинальное фото")
image = models.ImageField(upload_to='kits/temp/', verbose_name="Оригинальное фото")
order = models.PositiveIntegerField(default=0, verbose_name="Порядок")
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания")
@@ -1018,31 +1032,45 @@ class ProductKitPhoto(models.Model):
from .utils.image_processor import ImageProcessor
is_new = not self.pk
old_image_path = None
# Если это обновление существующего объекта, сохраняем старый путь для удаления
if not is_new:
try:
old_obj = ProductKitPhoto.objects.get(pk=self.pk)
if old_obj.image and old_obj.image != self.image:
old_image_path = old_obj.image.name
except ProductKitPhoto.DoesNotExist:
pass
# Если было загружено новое изображение
if self.image and (is_new or old_image_path):
# Обрабатываем изображение с использованием slug комплекта как идентификатора
# slug гарантирует уникальность и читаемость имени файла
identifier = self.kit.slug
processed_paths = ImageProcessor.process_image(self.image, 'kits', identifier=identifier)
# Сохраняем только путь к оригиналу в поле image
# Если это новый объект с изображением, нужно сначала сохранить без изображения, чтобы получить ID
if is_new and self.image:
# Сохраняем объект без изображения, чтобы получить ID
temp_image = self.image
self.image = None
super().save(*args, **kwargs)
# Теперь обрабатываем изображение с известными ID
processed_paths = ImageProcessor.process_image(temp_image, 'kits', entity_id=self.kit.id, photo_id=self.id)
self.image = processed_paths['original']
# Обновляем только поле image, чтобы избежать рекурсии и дублирования ID
super().save(update_fields=['image'])
else:
# Проверяем старый путь для удаления, если это обновление
old_image_path = None
if self.pk:
try:
old_obj = ProductKitPhoto.objects.get(pk=self.pk)
if old_obj.image and old_obj.image != self.image:
old_image_path = old_obj.image.name
except ProductKitPhoto.DoesNotExist:
pass
# Проверяем, нужно ли обрабатывать изображение
if self.image and old_image_path:
# Обновление существующего изображения
processed_paths = ImageProcessor.process_image(self.image, 'kits', entity_id=self.kit.id, photo_id=self.id)
self.image = processed_paths['original']
# Удаляем старые версии если это обновление
if old_image_path:
ImageProcessor.delete_all_versions('kits', old_image_path)
super().save(*args, **kwargs)
# Удаляем старые версии
ImageProcessor.delete_all_versions('kits', old_image_path, entity_id=self.kit.id, photo_id=self.id)
# Обновляем только поле image, чтобы избежать рекурсии
super().save(update_fields=['image'])
else:
# Просто сохраняем без обработки изображения
super().save(*args, **kwargs)
def delete(self, *args, **kwargs):
"""Удаляет все версии изображения при удалении фото"""
@@ -1054,7 +1082,7 @@ class ProductKitPhoto(models.Model):
if self.image:
try:
logger.info(f"[ProductKitPhoto.delete] Удаляем изображение: {self.image.name}")
ImageProcessor.delete_all_versions('kits', self.image.name)
ImageProcessor.delete_all_versions('kits', self.image.name, entity_id=self.kit.id, photo_id=self.id)
logger.info(f"[ProductKitPhoto.delete] ✓ Все версии изображения удалены")
except Exception as e:
logger.error(f"[ProductKitPhoto.delete] ✗ Ошибка при удалении версий: {str(e)}", exc_info=True)
@@ -1089,7 +1117,7 @@ class ProductCategoryPhoto(models.Model):
"""
category = models.ForeignKey(ProductCategory, on_delete=models.CASCADE, related_name='photos',
verbose_name="Категория")
image = models.ImageField(upload_to='categories/originals/', verbose_name="Оригинальное фото")
image = models.ImageField(upload_to='categories/temp/', verbose_name="Оригинальное фото")
order = models.PositiveIntegerField(default=0, verbose_name="Порядок")
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания")
@@ -1108,31 +1136,45 @@ class ProductCategoryPhoto(models.Model):
from .utils.image_processor import ImageProcessor
is_new = not self.pk
old_image_path = None
# Если это обновление существующего объекта, сохраняем старый путь для удаления
if not is_new:
try:
old_obj = ProductCategoryPhoto.objects.get(pk=self.pk)
if old_obj.image and old_obj.image != self.image:
old_image_path = old_obj.image.name
except ProductCategoryPhoto.DoesNotExist:
pass
# Если было загружено новое изображение
if self.image and (is_new or old_image_path):
# Обрабатываем изображение с использованием slug категории как идентификатора
# slug гарантирует уникальность и читаемость имени файла
identifier = self.category.slug
processed_paths = ImageProcessor.process_image(self.image, 'categories', identifier=identifier)
# Сохраняем только путь к оригиналу в поле image
# Если это новый объект с изображением, нужно сначала сохранить без изображения, чтобы получить ID
if is_new and self.image:
# Сохраняем объект без изображения, чтобы получить ID
temp_image = self.image
self.image = None
super().save(*args, **kwargs)
# Теперь обрабатываем изображение с известными ID
processed_paths = ImageProcessor.process_image(temp_image, 'categories', entity_id=self.category.id, photo_id=self.id)
self.image = processed_paths['original']
# Обновляем только поле image, чтобы избежать рекурсии и дублирования ID
super().save(update_fields=['image'])
else:
# Проверяем старый путь для удаления, если это обновление
old_image_path = None
if self.pk:
try:
old_obj = ProductCategoryPhoto.objects.get(pk=self.pk)
if old_obj.image and old_obj.image != self.image:
old_image_path = old_obj.image.name
except ProductCategoryPhoto.DoesNotExist:
pass
# Проверяем, нужно ли обрабатывать изображение
if self.image and old_image_path:
# Обновление существующего изображения
processed_paths = ImageProcessor.process_image(self.image, 'categories', entity_id=self.category.id, photo_id=self.id)
self.image = processed_paths['original']
# Удаляем старые версии если это обновление
if old_image_path:
ImageProcessor.delete_all_versions('categories', old_image_path)
super().save(*args, **kwargs)
# Удаляем старые версии
ImageProcessor.delete_all_versions('categories', old_image_path, entity_id=self.category.id, photo_id=self.id)
# Обновляем только поле image, чтобы избежать рекурсии
super().save(update_fields=['image'])
else:
# Просто сохраняем без обработки изображения
super().save(*args, **kwargs)
def delete(self, *args, **kwargs):
"""Удаляет все версии изображения при удалении фото"""
@@ -1144,7 +1186,7 @@ class ProductCategoryPhoto(models.Model):
if self.image:
try:
logger.info(f"[ProductCategoryPhoto.delete] Удаляем изображение: {self.image.name}")
ImageProcessor.delete_all_versions('categories', self.image.name)
ImageProcessor.delete_all_versions('categories', self.image.name, entity_id=self.category.id, photo_id=self.id)
logger.info(f"[ProductCategoryPhoto.delete] ✓ Все версии изображения удалены")
except Exception as e:
logger.error(f"[ProductCategoryPhoto.delete] ✗ Ошибка при удалении версий: {str(e)}", exc_info=True)