Улучшение системы работы с фото: добавлена команда очистки битых записей и оптимизация обработки изображений
This commit is contained in:
@@ -62,6 +62,17 @@ class ProductCategory(models.Model):
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def main_photo(self):
|
||||
"""
|
||||
Главное фото категории (is_main=True).
|
||||
Используется в карточках, каталоге, превью.
|
||||
|
||||
Returns:
|
||||
ProductCategoryPhoto | None: Главное фото или None если фото нет
|
||||
"""
|
||||
return self.photos.filter(is_main=True).first()
|
||||
|
||||
def clean(self):
|
||||
"""Валидация категории перед сохранением"""
|
||||
# 1. Защита от самоссылки
|
||||
|
||||
@@ -141,6 +141,17 @@ class ProductKit(BaseProductEntity):
|
||||
return self.sale_price
|
||||
return self.price
|
||||
|
||||
@property
|
||||
def main_photo(self):
|
||||
"""
|
||||
Главное фото комплекта (is_main=True).
|
||||
Используется в карточках, каталоге, превью.
|
||||
|
||||
Returns:
|
||||
ProductKitPhoto | None: Главное фото или None если фото нет
|
||||
"""
|
||||
return self.photos.filter(is_main=True).first()
|
||||
|
||||
def recalculate_base_price(self):
|
||||
"""
|
||||
Пересчитать сумму actual_price всех компонентов.
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"""
|
||||
from abc import abstractmethod
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
@@ -73,14 +74,25 @@ class BasePhoto(models.Model):
|
||||
Паттерн: Template Method
|
||||
- Общие методы save(), delete() и get_*_url() определены здесь
|
||||
- Специфичные детали (related entity, upload path) задаются через абстрактные методы
|
||||
|
||||
Главное фото:
|
||||
- is_main=True определяет главное фото (используется в карточках, каталоге, превью)
|
||||
- Constraint уникальности (только одно is_main=True на сущность) реализован в дочерних классах
|
||||
- order используется для сортировки остальных фото
|
||||
"""
|
||||
image = models.ImageField(verbose_name="Оригинальное фото")
|
||||
is_main = models.BooleanField(
|
||||
default=False,
|
||||
db_index=True,
|
||||
verbose_name="Главное фото",
|
||||
help_text="Главное фото отображается в карточках, каталоге и превью. Может быть только одно."
|
||||
)
|
||||
order = models.PositiveIntegerField(default=0, verbose_name="Порядок")
|
||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания")
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
ordering = ['order', '-created_at']
|
||||
ordering = ['-is_main', 'order', '-created_at'] # Главное фото всегда первое
|
||||
|
||||
@abstractmethod
|
||||
def get_entity(self):
|
||||
@@ -319,12 +331,19 @@ class ProductPhoto(BasePhoto):
|
||||
class Meta:
|
||||
verbose_name = "Фото товара"
|
||||
verbose_name_plural = "Фото товаров"
|
||||
ordering = ['order', '-created_at']
|
||||
ordering = ['-is_main', 'order', '-created_at']
|
||||
indexes = [
|
||||
models.Index(fields=['quality_level']),
|
||||
models.Index(fields=['quality_warning']),
|
||||
models.Index(fields=['quality_warning', 'product']), # Для поиска товаров требующих обновления фото
|
||||
]
|
||||
constraints = [
|
||||
models.UniqueConstraint(
|
||||
fields=['product'],
|
||||
condition=Q(is_main=True),
|
||||
name='unique_main_photo_per_product'
|
||||
)
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return f"Фото для {self.product.name} ({self.get_quality_level_display()})"
|
||||
@@ -386,12 +405,19 @@ class ProductKitPhoto(BasePhoto):
|
||||
class Meta:
|
||||
verbose_name = "Фото комплекта"
|
||||
verbose_name_plural = "Фото комплектов"
|
||||
ordering = ['order', '-created_at']
|
||||
ordering = ['-is_main', 'order', '-created_at']
|
||||
indexes = [
|
||||
models.Index(fields=['quality_level']),
|
||||
models.Index(fields=['quality_warning']),
|
||||
models.Index(fields=['quality_warning', 'kit']),
|
||||
]
|
||||
constraints = [
|
||||
models.UniqueConstraint(
|
||||
fields=['kit'],
|
||||
condition=Q(is_main=True),
|
||||
name='unique_main_photo_per_kit'
|
||||
)
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return f"Фото для {self.kit.name} ({self.get_quality_level_display()})"
|
||||
@@ -453,12 +479,19 @@ class ProductCategoryPhoto(BasePhoto):
|
||||
class Meta:
|
||||
verbose_name = "Фото категории"
|
||||
verbose_name_plural = "Фото категорий"
|
||||
ordering = ['order', '-created_at']
|
||||
ordering = ['-is_main', 'order', '-created_at']
|
||||
indexes = [
|
||||
models.Index(fields=['quality_level']),
|
||||
models.Index(fields=['quality_warning']),
|
||||
models.Index(fields=['quality_warning', 'category']),
|
||||
]
|
||||
constraints = [
|
||||
models.UniqueConstraint(
|
||||
fields=['category'],
|
||||
condition=Q(is_main=True),
|
||||
name='unique_main_photo_per_category'
|
||||
)
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return f"Фото для {self.category.name} ({self.get_quality_level_display()})"
|
||||
|
||||
@@ -128,6 +128,17 @@ class Product(BaseProductEntity):
|
||||
"""
|
||||
return self.sale_price if self.sale_price else self.price
|
||||
|
||||
@property
|
||||
def main_photo(self):
|
||||
"""
|
||||
Главное фото товара (is_main=True).
|
||||
Используется в карточках, каталоге, превью.
|
||||
|
||||
Returns:
|
||||
ProductPhoto | None: Главное фото или None если фото нет
|
||||
"""
|
||||
return self.photos.filter(is_main=True).first()
|
||||
|
||||
@property
|
||||
def cost_price_details(self):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user