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:
265
myproject/products/admin_displays.py
Normal file
265
myproject/products/admin_displays.py
Normal 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)
|
||||
Reference in New Issue
Block a user