fix: Улучшения системы ценообразования комплектов

Исправлены 4 проблемы:
1. Расчёт цены первого товара - улучшена валидация в getProductPrice и calculateFinalPrice
2. Отображение actual_price в Select2 вместо обычной цены
3. Количество по умолчанию = 1 для новых форм компонентов
4. Auto-select текста при клике на поле количества для удобства редактирования

Изменённые файлы:
- products/forms.py: добавлен __init__ в KitItemForm для quantity.initial = 1
- products/templates/includes/select2-product-init.html: обновлена formatSelectResult
- products/templates/productkit_create.html: добавлен focus handler для auto-select
- products/templates/productkit_edit.html: добавлен focus handler для auto-select

🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-02 19:04:03 +03:00
parent c84a372f98
commit 6c8af5ab2c
120 changed files with 9035 additions and 3036 deletions

View File

@@ -0,0 +1,265 @@
"""
Визуальные компоненты для отображения качества фотографий в Django админке.
Модулю используется для форматирования вывода уровней качества фото
с цветами, иконками и подсказками.
"""
from django.conf import settings
from django.utils.html import format_html
def get_quality_color(quality_level):
"""
Получить цвет Bootstrap для уровня качества.
Args:
quality_level (str): Уровень качества
Returns:
str: CSS цвет (success/info/warning/danger)
"""
labels = getattr(settings, 'IMAGE_QUALITY_LABELS', {})
info = labels.get(quality_level, {})
return info.get('color', 'secondary')
def get_quality_label(quality_level):
"""
Получить человеко-читаемое название уровня качества.
Args:
quality_level (str): Уровень качества
Returns:
str: Название (например, "Отлично")
"""
labels = getattr(settings, 'IMAGE_QUALITY_LABELS', {})
info = labels.get(quality_level, {})
return info.get('label', 'Неизвестно')
def get_quality_icon(quality_level):
"""
Получить иконку для уровня качества.
Args:
quality_level (str): Уровень качества
Returns:
str: Иконка (✓, ◐, ⚠, ✗)
"""
labels = getattr(settings, 'IMAGE_QUALITY_LABELS', {})
info = labels.get(quality_level, {})
return info.get('icon', '?')
def format_quality_badge(quality_level, show_icon=True):
"""
Форматирует уровень качества в виде цветного бэджа Bootstrap.
Пример вывода: [🟢 Отлично] или [🔴 Плохо]
Args:
quality_level (str): Уровень качества (excellent/good/acceptable/poor/very_poor)
show_icon (bool): Показывать ли иконку
Returns:
str: HTML с отформатированным бэджем
"""
labels = getattr(settings, 'IMAGE_QUALITY_LABELS', {})
info = labels.get(quality_level, {})
label_text = info.get('label', 'Неизвестно')
color = info.get('color', 'secondary')
icon = info.get('icon', '?')
description = info.get('description', '')
# Формируем текст бэджа
if show_icon:
badge_text = f"{icon} {label_text}"
else:
badge_text = label_text
# Выбираем CSS класс Bootstrap
badge_class = info.get('badge_class', 'badge-secondary')
# Создаем HTML с tooltip при наведении
html = format_html(
'<span class="badge {} " title="{}" style="font-size: 13px; padding: 6px 10px; cursor: help;">{}</span>',
badge_class,
description,
badge_text
)
return html
def format_quality_badge_with_size(quality_level, width=None, height=None):
"""
Форматирует качество с указанием размеров изображения.
Пример: "🟢 Отлично (2150×2150px)"
Args:
quality_level (str): Уровень качества
width (int): Ширина изображения (опционально)
height (int): Высота изображения (опционально)
Returns:
str: HTML с бэджем и размерами
"""
label_text = get_quality_label(quality_level)
icon = get_quality_icon(quality_level)
color = get_quality_color(quality_level)
size_text = ""
if width and height:
size_text = f" ({width}×{height}px)"
labels = getattr(settings, 'IMAGE_QUALITY_LABELS', {})
info = labels.get(quality_level, {})
badge_class = info.get('badge_class', 'badge-secondary')
html = format_html(
'<span class="badge {}" style="font-size: 13px; padding: 6px 10px;">{} {}{}</span>',
badge_class,
icon,
label_text,
size_text
)
return html
def format_quality_display(quality_level, width=None, height=None, warning=False):
"""
Полное отображение качества с индикатором warning.
Args:
quality_level (str): Уровень качества
width (int): Ширина изображения (опционально)
height (int): Высота изображения (опционально)
warning (bool): Требует ли обновления
Returns:
str: HTML с полной информацией о качестве
"""
badge = format_quality_badge_with_size(quality_level, width, height)
if warning:
# Добавляем индикатор warning
warning_indicator = format_html(
' <span style="color: #ff6b6b; font-weight: bold;" title="Требует обновления перед выгрузкой на сайт">⚠️ Требует обновления</span>'
)
return format_html('{} {}', badge, warning_indicator)
return badge
def format_photo_quality_column(obj, show_size=True):
"""
Для использования в list_display - отображает качество фотографии объекта.
Пример использования:
def photo_quality(self, obj):
return format_photo_quality_column(obj)
photo_quality.short_description = 'Качество фото'
Args:
obj: Product, ProductKit или ProductCategory объект
show_size (bool): Показывать ли размеры
Returns:
str: HTML с качеством первого фото
"""
first_photo = obj.photos.first()
if not first_photo:
return format_html('<span style="color: #999;">Нет фото</span>')
if show_size:
return format_quality_display(
first_photo.quality_level,
width=first_photo.width if hasattr(first_photo, 'width') else None,
height=first_photo.height if hasattr(first_photo, 'height') else None,
warning=first_photo.quality_warning
)
else:
return format_quality_badge(first_photo.quality_level)
def format_photo_inline_quality(photo_obj):
"""
Для использования в inline таблицах - отображает качество фото в строке.
Args:
photo_obj: ProductPhoto, ProductKitPhoto или ProductCategoryPhoto объект
Returns:
str: HTML с качеством фото
"""
if not photo_obj.pk:
# Новый объект еще не сохранён
return format_html('<span style="color: #999;">Сохраните фото</span>')
return format_quality_display(
photo_obj.quality_level,
width=photo_obj.width if hasattr(photo_obj, 'width') else None,
height=photo_obj.height if hasattr(photo_obj, 'height') else None,
warning=photo_obj.quality_warning
)
def format_photo_preview_with_quality(photo_obj, max_width=250, max_height=250):
"""
Превью фотографии с индикатором качества под ней.
Args:
photo_obj: ProductPhoto, ProductKitPhoto или ProductCategoryPhoto объект
max_width (int): Максимальная ширина превью
max_height (int): Максимальная высота превью
Returns:
str: HTML с фото и индикатором качества
"""
if not photo_obj.image:
return format_html('<span style="color: #999;">Нет изображения</span>')
quality_display = format_quality_badge(photo_obj.quality_level)
html = format_html(
'<div style="text-align: center;">'
'<img src="{}" style="max-width: {}px; max-height: {}px; border-radius: 4px; margin-bottom: 8px;" />'
'<div>{}</div>'
'</div>',
photo_obj.get_large_url() if hasattr(photo_obj, 'get_large_url') else photo_obj.image.url,
max_width,
max_height,
quality_display
)
return html
def get_quality_filter_display(value):
"""
Получить описание фильтра для качества фото.
Args:
value (str): Значение фильтра (excellent/good/acceptable/poor/very_poor/warning/no_warning)
Returns:
str: Описание для отображения
"""
filter_descriptions = {
'excellent': '🟢 Отлично',
'good': '🟡 Хорошо',
'acceptable': '🟠 Приемлемо',
'poor': '🔴 Плохо',
'very_poor': '🔴 Очень плохо',
'warning': '⚠️ Требует обновления',
'no_warning': '✓ Готово к выгрузке',
}
return filter_descriptions.get(value, value)