refactor: Создать базовый класс BaseProductEntity и реструктурировать Product/ProductKit
Основные изменения: ## Модели (models.py) - Создан абстрактный класс BaseProductEntity с общими полями: * Идентификация: name, sku, slug * Описания: description, short_description (новое поле) * Статус: is_active, timestamps, soft delete * Managers: objects, all_objects, active - Product: * Унаследован от BaseProductEntity * sale_price переименован в price (основная цена) * Добавлено новое поле sale_price (цена со скидкой, nullable) * Добавлено property actual_price - ProductKit: * Унаследован от BaseProductEntity * fixed_price переименован в price (ручная цена) * pricing_method: 'fixed' → 'manual' * Добавлено поле sale_price (цена со скидкой) * Добавлено поле cost_price (nullable) * Добавлены properties: calculated_price, actual_price * Обновлен calculate_price_with_substitutions() ## Forms (forms.py) - ProductForm: добавлено short_description, price, sale_price - ProductKitForm: добавлено short_description, cost_price, price, sale_price ## Admin (admin.py) - ProductAdmin: обновлены list_display и fieldsets с новыми полями - ProductKitAdmin: добавлены fieldsets, метод get_price_display() ## Templates - product_form.html: добавлены поля price, sale_price, short_description - product_detail.html: показывает зачеркнутую цену + скидку + бейджик "Акция" - product_list.html: отображение цен со скидкой и бейджиком "Акция" - all_products_list.html: единообразное отображение цен - productkit_detail.html: отображение скидок с бейджиком "Акция" ## API (api_views.py) - Обновлены все endpoints для использования поля price вместо sale_price Результат: единообразная архитектура с поддержкой скидок, DRY-принцип, логичные названия полей, красивое отображение акций в UI. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -241,7 +241,7 @@ class ProductTagAdmin(admin.ModelAdmin):
|
||||
|
||||
|
||||
class ProductAdmin(admin.ModelAdmin):
|
||||
list_display = ('photo_preview', 'name', 'sku', 'get_categories_display', 'cost_price', 'sale_price', 'get_variant_groups_display', 'is_active', 'get_deleted_status')
|
||||
list_display = ('photo_preview', 'name', 'sku', 'get_categories_display', 'cost_price', 'price', 'sale_price', 'get_variant_groups_display', 'is_active', 'get_deleted_status')
|
||||
list_filter = (DeletedFilter, 'is_active', 'categories', 'tags', 'variant_groups')
|
||||
search_fields = ('name', 'sku', 'description', 'search_keywords')
|
||||
filter_horizontal = ('categories', 'tags', 'variant_groups')
|
||||
@@ -251,10 +251,11 @@ class ProductAdmin(admin.ModelAdmin):
|
||||
|
||||
fieldsets = (
|
||||
('Основная информация', {
|
||||
'fields': ('name', 'sku', 'variant_suffix', 'description', 'categories', 'unit')
|
||||
'fields': ('name', 'sku', 'variant_suffix', 'description', 'short_description', 'categories', 'unit')
|
||||
}),
|
||||
('Цены', {
|
||||
'fields': ('cost_price', 'sale_price')
|
||||
'fields': ('cost_price', 'price', 'sale_price'),
|
||||
'description': 'price - основная цена, sale_price - цена со скидкой (опционально)'
|
||||
}),
|
||||
('Дополнительно', {
|
||||
'fields': ('tags', 'variant_groups', 'is_active')
|
||||
@@ -336,13 +337,43 @@ class ProductAdmin(admin.ModelAdmin):
|
||||
|
||||
|
||||
class ProductKitAdmin(admin.ModelAdmin):
|
||||
list_display = ('photo_preview', 'name', 'slug', 'get_categories_display', 'pricing_method', 'is_active', 'get_deleted_status')
|
||||
list_display = ('photo_preview', 'name', 'slug', 'get_categories_display', 'pricing_method', 'get_price_display', 'is_active', 'get_deleted_status')
|
||||
list_filter = (DeletedFilter, 'is_active', 'pricing_method', 'categories', 'tags')
|
||||
prepopulated_fields = {'slug': ('name',)}
|
||||
filter_horizontal = ('categories', 'tags')
|
||||
readonly_fields = ('photo_preview_large', 'deleted_at', 'deleted_by')
|
||||
actions = [restore_items, delete_selected, hard_delete_selected]
|
||||
|
||||
fieldsets = (
|
||||
('Основная информация', {
|
||||
'fields': ('name', 'sku', 'slug', 'description', 'short_description', 'categories')
|
||||
}),
|
||||
('Ценообразование', {
|
||||
'fields': ('pricing_method', 'cost_price', 'price', 'sale_price', 'markup_percent', 'markup_amount'),
|
||||
'description': 'Метод ценообразования определяет как вычисляется цена комплекта. price используется при методе "Ручная цена".'
|
||||
}),
|
||||
('Дополнительно', {
|
||||
'fields': ('tags', 'is_active')
|
||||
}),
|
||||
('Удаление', {
|
||||
'fields': ('deleted_at', 'deleted_by'),
|
||||
'classes': ('collapse',),
|
||||
'description': 'Информация о мягком удалении комплекта.'
|
||||
}),
|
||||
('Фото', {
|
||||
'fields': ('photo_preview_large',),
|
||||
'classes': ('collapse',),
|
||||
}),
|
||||
)
|
||||
|
||||
def get_price_display(self, obj):
|
||||
"""Отображение финальной цены комплекта"""
|
||||
try:
|
||||
return f"{obj.actual_price} ₽"
|
||||
except Exception:
|
||||
return "-"
|
||||
get_price_display.short_description = "Цена"
|
||||
|
||||
def get_queryset(self, request):
|
||||
"""Переопределяем queryset для доступа ко всем комплектам (включая удаленные)"""
|
||||
qs = ProductKit.all_objects.all()
|
||||
|
||||
Reference in New Issue
Block a user