from django.contrib import admin from django.utils.html import format_html import nested_admin from .models import ProductCategory, ProductTag, Product, ProductKit, KitItem from .models import ProductPhoto, ProductKitPhoto, ProductCategoryPhoto from .models import ProductVariantGroup, KitItemPriority, SKUCounter @admin.register(ProductVariantGroup) class ProductVariantGroupAdmin(admin.ModelAdmin): list_display = ['name', 'get_products_count', 'created_at'] search_fields = ['name', 'description'] list_filter = ['created_at'] readonly_fields = ['created_at', 'updated_at'] def get_products_count(self, obj): return obj.products.count() get_products_count.short_description = 'Товаров' class ProductCategoryAdmin(admin.ModelAdmin): list_display = ('photo_preview', 'name', 'sku', 'slug', 'parent', 'is_active') list_filter = ('is_active', 'parent') prepopulated_fields = {'slug': ('name',)} search_fields = ('name', 'sku') readonly_fields = ('photo_preview_large',) def photo_preview(self, obj): """Превью фото в списке категорий""" first_photo = obj.photos.first() if first_photo and first_photo.image: return format_html( '', first_photo.image.url ) return "Нет фото" photo_preview.short_description = "Фото" def photo_preview_large(self, obj): """Большое превью фото в форме редактирования""" first_photo = obj.photos.first() if first_photo and first_photo.image: return format_html( '', first_photo.image.url ) return "Нет фото" photo_preview_large.short_description = "Превью основного фото" class ProductTagAdmin(admin.ModelAdmin): list_display = ('name', 'slug') prepopulated_fields = {'slug': ('name',)} search_fields = ('name',) class ProductAdmin(admin.ModelAdmin): list_display = ('photo_preview', 'name', 'sku', 'get_categories_display', 'cost_price', 'sale_price', 'get_variant_groups_display', 'is_active') list_filter = ('is_active', 'categories', 'tags', 'variant_groups') search_fields = ('name', 'sku', 'description', 'search_keywords') filter_horizontal = ('categories', 'tags', 'variant_groups') readonly_fields = ('photo_preview_large',) autocomplete_fields = [] fieldsets = ( ('Основная информация', { 'fields': ('name', 'sku', 'variant_suffix', 'description', 'categories', 'unit') }), ('Цены', { 'fields': ('cost_price', 'sale_price') }), ('Дополнительно', { 'fields': ('tags', 'variant_groups', 'is_active') }), ('Поиск', { 'fields': ('search_keywords',), 'classes': ('collapse',), 'description': 'Поле для улучшенного поиска. Автоматически генерируется при сохранении, но вы можете добавить дополнительные ключевые слова (синонимы, альтернативные названия и т.д.).' }), ('Фото', { 'fields': ('photo_preview_large',), 'classes': ('collapse',), }), ) def get_categories_display(self, obj): categories = obj.categories.all()[:3] if not categories: return "-" result = ", ".join([cat.name for cat in categories]) if obj.categories.count() > 3: result += f" (+{obj.categories.count() - 3})" return result get_categories_display.short_description = 'Категории' def get_variant_groups_display(self, obj): groups = obj.variant_groups.all()[:3] if not groups: return "-" result = ", ".join([g.name for g in groups]) if obj.variant_groups.count() > 3: result += f" (+{obj.variant_groups.count() - 3})" return result get_variant_groups_display.short_description = 'Группы вариантов' def photo_preview(self, obj): """Превью фото в списке товаров""" first_photo = obj.photos.first() if first_photo and first_photo.image: return format_html( '', first_photo.image.url ) return "Нет фото" photo_preview.short_description = "Фото" def photo_preview_large(self, obj): """Большое превью фото в форме редактирования""" first_photo = obj.photos.first() if first_photo and first_photo.image: return format_html( '', first_photo.image.url ) return "Нет фото" photo_preview_large.short_description = "Превью основного фото" class ProductKitAdmin(admin.ModelAdmin): list_display = ('photo_preview', 'name', 'slug', 'get_categories_display', 'pricing_method', 'is_active') list_filter = ('is_active', 'pricing_method', 'categories', 'tags') prepopulated_fields = {'slug': ('name',)} filter_horizontal = ('categories', 'tags') readonly_fields = ('photo_preview_large',) def get_categories_display(self, obj): categories = obj.categories.all()[:3] if not categories: return "-" result = ", ".join([cat.name for cat in categories]) if obj.categories.count() > 3: result += f" (+{obj.categories.count() - 3})" return result get_categories_display.short_description = 'Категории' def photo_preview(self, obj): """Превью фото в списке комплектов""" first_photo = obj.photos.first() if first_photo and first_photo.image: return format_html( '', first_photo.image.url ) return "Нет фото" photo_preview.short_description = "Фото" def photo_preview_large(self, obj): """Большое превью фото в форме редактирования""" first_photo = obj.photos.first() if first_photo and first_photo.image: return format_html( '', first_photo.image.url ) return "Нет фото" photo_preview_large.short_description = "Превью основного фото" class KitItemPriorityInline(nested_admin.NestedTabularInline): model = KitItemPriority extra = 0 # Не показывать пустые формы fields = ['product', 'priority'] autocomplete_fields = ['product'] def get_queryset(self, request): qs = super().get_queryset(request) return qs.select_related('product') def formfield_for_foreignkey(self, db_field, request, **kwargs): """Показывать только товары из выбранной группы вариантов""" if db_field.name == "product": # Получаем kit_item из родительского объекта через request # Это будет работать автоматически с nested_admin pass return super().formfield_for_foreignkey(db_field, request, **kwargs) class KitItemInline(nested_admin.NestedStackedInline): model = KitItem extra = 0 # Не показывать пустые формы fields = ['product', 'variant_group', 'quantity', 'notes'] autocomplete_fields = ['product'] inlines = [KitItemPriorityInline] class Media: css = { 'all': ('admin/css/custom_nested.css',) } class ProductPhotoInline(admin.TabularInline): model = ProductPhoto extra = 1 readonly_fields = ('image_preview',) fields = ('image', 'image_preview', 'order') def image_preview(self, obj): """Превью загруженного фото""" if obj.image: return format_html( '', obj.image.url ) return "Нет изображения" image_preview.short_description = "Превью" class ProductKitPhotoInline(nested_admin.NestedTabularInline): model = ProductKitPhoto extra = 0 # Не показывать пустые формы readonly_fields = ('image_preview',) fields = ('image', 'image_preview', 'order') def image_preview(self, obj): """Превью загруженного фото""" if obj.image: return format_html( '', obj.image.url ) return "Нет изображения" image_preview.short_description = "Превью" class ProductCategoryPhotoInline(admin.TabularInline): model = ProductCategoryPhoto extra = 1 readonly_fields = ('image_preview',) fields = ('image', 'image_preview', 'order') def image_preview(self, obj): """Превью загруженного фото""" if obj.image: return format_html( '', obj.image.url ) return "Нет изображения" image_preview.short_description = "Превью" class ProductKitAdminWithItems(ProductKitAdmin): inlines = [KitItemInline] # Update admin classes to include photo inlines class ProductAdminWithPhotos(ProductAdmin): inlines = [ProductPhotoInline] class ProductKitAdminWithItemsAndPhotos(nested_admin.NestedModelAdmin, ProductKitAdmin): inlines = [KitItemInline, ProductKitPhotoInline] class ProductCategoryAdminWithPhotos(ProductCategoryAdmin): inlines = [ProductCategoryPhotoInline] @admin.register(KitItem) class KitItemAdmin(admin.ModelAdmin): list_display = ['__str__', 'kit', 'get_type', 'quantity', 'has_priorities'] list_filter = ['kit'] list_select_related = ['kit', 'product', 'variant_group'] inlines = [KitItemPriorityInline] fields = ['kit', 'product', 'variant_group', 'quantity', 'notes'] def get_type(self, obj): if obj.variant_group: return format_html('Группа: {}', obj.variant_group.name) return f"Товар: {obj.product.name if obj.product else '-'}" get_type.short_description = 'Тип' def has_priorities(self, obj): return obj.priorities.exists() has_priorities.boolean = True has_priorities.short_description = 'Приоритеты настроены' @admin.register(SKUCounter) class SKUCounterAdmin(admin.ModelAdmin): list_display = ['counter_type', 'current_value', 'get_next_preview'] list_filter = ['counter_type'] readonly_fields = ['get_next_preview'] def get_next_preview(self, obj): """Показывает, каким будет следующий артикул""" next_val = obj.current_value + 1 if obj.counter_type == 'product': return format_html('PROD-{:06d}', next_val) elif obj.counter_type == 'kit': return format_html('KIT-{:06d}', next_val) elif obj.counter_type == 'category': return format_html('CAT-{:04d}', next_val) return str(next_val) get_next_preview.short_description = 'Следующий артикул' def has_delete_permission(self, request, obj=None): # Запрещаем удаление счетчиков return False admin.site.register(ProductCategory, ProductCategoryAdminWithPhotos) admin.site.register(ProductTag, ProductTagAdmin) admin.site.register(Product, ProductAdminWithPhotos) admin.site.register(ProductKit, ProductKitAdminWithItemsAndPhotos)