Исправлены 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>
266 lines
8.9 KiB
Python
266 lines
8.9 KiB
Python
"""
|
||
Визуальные компоненты для отображения качества фотографий в 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)
|