from django.contrib import admin from django import forms from django.urls import reverse from django.utils.safestring import mark_safe from django.utils.html import format_html from .models import * class HeadphonesAdminForm(forms.ModelForm): class Meta: model = Headphones fields = '__all__' def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if self.instance and self.instance.pk: # Скрываем/показываем поля в зависимости от типа подключения if self.instance.connection_type == 'wired': self.fields.pop('wireless_details', None) self.fields.pop('hybrid_details', None) elif self.instance.connection_type == 'wireless': self.fields.pop('wired_details', None) self.fields.pop('hybrid_details', None) elif self.instance.connection_type == 'hybrid': self.fields.pop('wired_details', None) self.fields.pop('wireless_details', None) class HeadphonesConnectorInline(admin.TabularInline): model = HeadphonesConnector extra = 1 fields = ('connector', 'is_primary', 'notes') fk_name = 'wired_headphones' @admin.register(WiredHeadphones) class WiredHeadphonesAdmin(admin.ModelAdmin): inlines = [HeadphonesConnectorInline] list_display = ('headphones', 'cable_connection_type', 'cable_length') list_filter = ('headphones__brand', 'cable_connection_type') search_fields = ('headphones__model',) def get_queryset(self, request): return super().get_queryset(request).select_related('headphones', 'cable_connection_type') class WiredHeadphonesInline(admin.StackedInline): model = WiredHeadphones extra = 0 fieldsets = [ (None, { 'fields': [ 'cable_connection_type', 'cable_length', 'custom_connector_name' ] }) ] class WirelessHeadphonesInline(admin.StackedInline): model = WirelessHeadphones extra = 0 fieldsets = [ (None, { 'fields': [ ('wireless_technology', 'bluetooth_version'), ('battery_life', 'charging_time'), 'charging_interface', ('has_charging_case', 'charging_case_battery_life'), 'supported_codecs' ] }) ] class HybridHeadphonesInline(admin.StackedInline): model = HybridHeadphones extra = 0 fieldsets = [ ('Беспроводная часть', { 'fields': [ ('wireless_technology', 'bluetooth_version'), ('battery_life', 'charging_time'), 'charging_interface', ('has_charging_case', 'charging_case_battery_life'), 'supported_codecs' ] }), ('Проводная часть', { 'fields': [ ('cable_connection_type', 'connector_to_device'), 'cable_length' ] }), ('Режимы работы', { 'fields': [ 'auto_switch_mode', 'simultaneous_connection' ] }) ] class HeadphonesReviewInline(admin.StackedInline): model = HeadphonesReview extra = 1 fields = ['resource', 'author', 'language', 'title', 'url', 'date'] ordering = ['-date'] autocomplete_fields = ['resource', 'author', 'language'] class HeadphonesImagesInline(admin.TabularInline): model = HeadphonesImages extra = 1 fields = ('photo', 'thumbnail_preview', 'admin_image_info', 'caption', 'is_main') readonly_fields = ('thumbnail_preview', 'admin_image_info') def thumbnail_preview(self, obj): if obj.photo: return format_html('', obj.photo.url) return "-" def admin_image_info(self, obj): return obj.image_info admin_image_info.short_description = "Информация о изображении" admin_image_info.allow_tags = True thumbnail_preview.short_description = "Превью" @admin.register(CaseMaterial) class CaseMaterialAdmin(admin.ModelAdmin): list_display = ('name', 'slug') search_fields = ('name',) prepopulated_fields = {'slug': ('name',)} list_per_page = 20 class HeadphonesDriverInline(admin.TabularInline): model = HeadphonesDriver extra = 1 fields = ('driver', 'count') autocomplete_fields = ['driver'] # class HeadphonesColorInline(admin.TabularInline): # model = Headphones.colors.through # Используем автоматическую промежуточную модель # extra = 1 # # autocomplete_fields = ['casecolors'] # Убедитесь, что это правильное имя related_name # verbose_name = "Доступный цвет" # verbose_name_plural = "Доступные цвета" class CaseColorsInline(admin.TabularInline): model = CaseColors extra = 1 fields = ('name_ru', 'name_en', 'image', 'image_preview', 'slug') readonly_fields = ('image_preview', 'slug') def image_preview(self, obj): if obj.image: return format_html('', obj.image.url) return "Нет изображения" image_preview.short_description = "Превью" def get_formset(self, request, obj=None, **kwargs): formset = super().get_formset(request, obj, **kwargs) # Устанавливаем начальное значение для slug, если объект новый if obj and not obj.pk: formset.form.base_fields['slug'].initial = f"{obj.model}_" return formset @admin.register(Headphones) class HeadphonesAdmin(admin.ModelAdmin): form = HeadphonesAdminForm list_display = ( 'model', 'brand', 'connection_type', 'get_reviews_count', 'published') list_filter = ('brand', 'connection_type', 'published', 'case_type') search_fields = ('model', 'brand__name', 'description') prepopulated_fields = {'slug': ('brand', 'model')} filter_horizontal = ('tags',) list_editable = ('published',) actions = ['make_published', 'make_unpublished'] readonly_fields = ( 'get_reviews_count_display', 'date_added', 'driver_configuration_display' ) autocomplete_fields = ['case_material', 'noise_cancellation'] fieldsets = [ ('Основная информация', { 'fields': [ ('brand', 'model'), 'slug', 'description', ('connection_type', 'case_type', 'case_material'), ('headphone_purpose', 'official_price'), 'tags', # Добавляем теги в основную секцию ] }), ('Технические характеристики', { 'fields': [ ('impedance', 'sensitivity'), 'frequency_range', 'frequency_response_chart', ('microphone', 'noise_cancellation'), 'ip_rating', ] }), ('Внешний вид', { 'fields': [ # 'colors', ('weight', 'release_year') ] }), ('Метаданные', { 'fields': [ ('date_added', 'published') ], 'classes': ('collapse',) }), ] inlines = [HeadphonesDriverInline, HeadphonesImagesInline, CaseColorsInline] # Методы для отображения def get_reviews_count(self, obj): return obj.reviews.count() get_reviews_count.short_description = 'Кол-во обзоров' def get_reviews_count_display(self, obj): count = self.get_reviews_count(obj) url = reverse('admin:headphones_headphonesreview_changelist') + f'?headphones__id__exact={obj.id}' return format_html('{} обзоров', url, count) get_reviews_count_display.short_description = 'Обзоры' def driver_configuration_display(self, obj): drivers = obj.headphonesdriver_set.select_related('driver__driver_type').all() return format_html("
".join( f"{d.driver.driver_type.name} {d.driver.size}mm × {d.count}" for d in drivers )) if drivers.exists() else "Не указано" driver_configuration_display.short_description = 'Конфигурация драйверов' def get_inline_instances(self, request, obj=None): # inlines = [ # HeadphonesDriverInline(self.model, self.admin_site), # HeadphonesImagesInline(self.model, self.admin_site) # ] inlines = super().get_inline_instances(request, obj) if obj: if obj.connection_type == 'wired': inlines.append(WiredHeadphonesInline(self.model, self.admin_site)) elif obj.connection_type == 'wireless': inlines.append(WirelessHeadphonesInline(self.model, self.admin_site)) elif obj.connection_type == 'hybrid': inlines.append(HybridHeadphonesInline(self.model, self.admin_site)) inlines.append(HeadphonesReviewInline(self.model, self.admin_site)) return inlines # Регистрация остальных моделей @admin.register(Brand) class BrandAdmin(admin.ModelAdmin): list_display = ('name', 'country', 'year_founded', 'parent_company') list_filter = ('country',) search_fields = ('name',) prepopulated_fields = {'slug': ('name',)} class DriverInline(admin.TabularInline): model = Driver extra = 1 fields = ('driver_type', 'driver_model', 'size', 'frequency_range') autocomplete_fields = ['driver_type', 'driver_model'] @admin.register(DriverModel) class DriverModelAdmin(admin.ModelAdmin): list_display = ('name', 'slug') search_fields = ('name',) prepopulated_fields = {'slug': ('name',)} inlines = [DriverInline] @admin.register(Driver) class DriverAdmin(admin.ModelAdmin): list_display = ('driver_type', 'driver_model', 'size', 'frequency_range') list_filter = ('driver_type',) search_fields = ('driver_type__name', 'driver_model__name', 'size', 'frequency_range') # Добавлено autocomplete_fields = ['driver_type', 'driver_model'] def get_queryset(self, request): return super().get_queryset(request).select_related('driver_type') # ====================== # Регистрация справочников # ====================== @admin.register(BrandCountry) class BrandCountryAdmin(admin.ModelAdmin): list_display = ('name', 'slug') search_fields = ('name',) prepopulated_fields = {'slug': ('name',)} @admin.register(CaseColors) class CaseColorsAdmin(admin.ModelAdmin): list_display = ('name_ru', 'name_en', 'headphones', 'image_preview', 'slug') list_filter = ('headphones__brand',) search_fields = ('name_ru', 'name_en', 'headphones__model') readonly_fields = ('image_preview', 'slug') list_select_related = ('headphones', 'headphones__brand') fieldsets = [ ('Основная информация', { 'fields': ['headphones', 'name_ru', 'name_en', 'slug'] }), ('Изображение', { 'fields': ['image', 'image_preview'] }) ] def get_queryset(self, request): return super().get_queryset(request).select_related('headphones__brand') def save_model(self, request, obj, form, change): if not obj.slug: obj.slug = slugify(f"{obj.headphones.model}_{obj.name_en}") super().save_model(request, obj, form, change) @admin.register(CaseType) class CaseTypeAdmin(admin.ModelAdmin): list_display = ('name', 'slug') search_fields = ('name',) prepopulated_fields = {'slug': ('name',)} @admin.register(HeadphonePurpose) class HeadphonePurposeAdmin(admin.ModelAdmin): list_display = ('name', 'slug') search_fields = ('name',) prepopulated_fields = {'slug': ('name',)} @admin.register(TagCategory) class TagCategoryAdmin(admin.ModelAdmin): list_display = ('name', 'slug') search_fields = ('name',) prepopulated_fields = {'slug': ('name',)} @admin.register(Tags) class TagsAdmin(admin.ModelAdmin): list_display = ('name', 'category', 'slug') list_filter = ('category',) search_fields = ('name', 'category__name') prepopulated_fields = {'slug': ('name',)} # ====================== # Регистрация аудио компонентов # ====================== @admin.register(DriverType) class DriverTypeAdmin(admin.ModelAdmin): list_display = ('name', 'slug') search_fields = ('name',) prepopulated_fields = {'slug': ('name',)} @admin.register(NoiseCancellationType) class NoiseCancellationTypeAdmin(admin.ModelAdmin): list_display = ('name', 'slug') search_fields = ('name',) prepopulated_fields = {'slug': ('name',)} @admin.register(CodecType) class CodecTypeAdmin(admin.ModelAdmin): list_display = ('name',) search_fields = ('name',) @admin.register(AudioCodec) class AudioCodecAdmin(admin.ModelAdmin): list_display = ('name', 'codec_type', 'max_bitrate', 'latency') list_filter = ('codec_type',) search_fields = ('name', 'description') filter_horizontal = () # ====================== # Регистрация разъемов и подключений # ====================== @admin.register(CableConnectionType) class CableConnectionTypeAdmin(admin.ModelAdmin): list_display = ('name', 'is_detachable') search_fields = ('name', 'description') readonly_fields = ('image_preview',) def image_preview(self, obj): if obj.image: return mark_safe(f'') return "Нет изображения" image_preview.short_description = 'Превью' @admin.register(DeviceConnectorType) class DeviceConnectorTypeAdmin(admin.ModelAdmin): list_display = ('name',) search_fields = ('name', 'description') @admin.register(WirelessTechnology) class WirelessTechnologyAdmin(admin.ModelAdmin): list_display = ('name',) search_fields = ('name', 'description') @admin.register(ChargingInterface) class ChargingInterfaceAdmin(admin.ModelAdmin): list_display = ('name',) search_fields = ('name', 'description') # ====================== # Регистрация отзывов и медиа # ====================== @admin.register(HeadphonesReviewType) class HeadphonesReviewTypeAdmin(admin.ModelAdmin): list_display = ('name', 'slug') search_fields = ('name',) prepopulated_fields = {'slug': ('name',)} @admin.register(HeadphonesReviewResource) class HeadphonesReviewResourceAdmin(admin.ModelAdmin): list_display = ('name', 'review_type', 'website_link', 'slug') list_filter = ('type',) search_fields = ('name', 'type__name') def website_link(self, obj): return format_html('{}', obj.url, obj.url) website_link.short_description = "Ссылка на ресурс" def review_type(self, obj): return obj.type.name if obj.type else '-' review_type.short_description = 'Тип обзора' review_type.admin_order_field = 'type__name' @admin.register(HeadphonesConnector) class HeadphonesConnectorAdmin(admin.ModelAdmin): list_display = ('headphones', 'connector_display', 'is_primary', 'notes') list_filter = ('is_primary', 'connector') def connector_display(self, obj): return str(obj.connector) connector_display.short_description = 'Разъем' connector_display.admin_order_field = 'connector__name' @admin.register(HeadphonesReviewLanguage) class HeadphonesReviewLanguageAdmin(admin.ModelAdmin): list_display = ('language',) search_fields = ('language',) @admin.register(HeadphonesReviewAuthor) class HeadphonesReviewAuthorAdmin(admin.ModelAdmin): list_display = ('name', 'nickname', 'reviews_count') search_fields = ('name', 'nickname') filter_horizontal = ('resources',) def reviews_count(self, obj): return obj.headphonesreview_set.count() reviews_count.short_description = 'Кол-во обзоров' # Добавьте инлайн для отображения обзоров автора class ReviewsInline(admin.TabularInline): model = HeadphonesReview extra = 0 fields = ('title', 'headphones', 'date') readonly_fields = ('title', 'headphones', 'date') inlines = [ReviewsInline] class HeadphonesReviewAuthorInline(admin.StackedInline): model = HeadphonesReviewAuthor extra = 1 fields = ('name', 'nickname', 'resources') filter_horizontal = ('resources',) @admin.register(HeadphonesReview) class HeadphonesReviewAdmin(admin.ModelAdmin): list_display = ( 'title', 'headphones_link', 'resource_link', 'date', # 'author_display', 'review_link' ) list_filter = ( 'resource__type', 'headphones__brand', 'date' ) search_fields = ( 'title', 'headphones__model', # 'author', 'resource__name' ) raw_id_fields = ('headphones', 'resource') date_hierarchy = 'date' list_select_related = ('headphones', 'resource') def headphones_link(self, obj): if obj.headphones: url = reverse('admin:headphones_headphones_change', args=[obj.headphones.id]) return format_html('{}', url, obj.headphones.model) return "-" headphones_link.short_description = "Наушники" headphones_link.admin_order_field = 'headphones__model' def resource_link(self, obj): url = reverse('admin:headphones_headphonesreviewresource_change', args=[obj.resource.id]) return format_html('{}', url, obj.resource.name) resource_link.short_description = "Ресурс" resource_link.admin_order_field = 'resource__name' def review_link(self, obj): return format_html('Открыть', obj.url) review_link.short_description = "Ссылка" def author_display(self, obj): if obj.author: url = reverse('admin:headphones_headphonesreviewauthor_change', args=[obj.author.id]) return format_html('{}', url, obj.author.name) return "-" author_display.short_description = "Автор" @admin.register(HeadphonesImages) class HeadphonesImagesAdmin(admin.ModelAdmin): list_display = ('product', 'caption') list_filter = ('product__brand',) search_fields = ('product__model', 'caption') readonly_fields = ('image_preview',) def image_preview(self, obj): if obj.photo: return mark_safe(f'') return "Нет изображения" image_preview.short_description = 'Превью' # ====================== # Регистрация вспомогательных моделей # ====================== @admin.register(HeadphonesDriver) class HeadphonesDriverAdmin(admin.ModelAdmin): list_display = ('headphones', 'driver_type', 'size', 'count') list_filter = ('driver__driver_type',) def driver_type(self, obj): return obj.driver.driver_type.name driver_type.short_description = 'Тип драйвера' def size(self, obj): return f"{obj.driver.size}mm" if obj.driver.size else '-' size.short_description = 'Размер' @admin.register(Comment) class CommentAdmin(admin.ModelAdmin): list_display = ('user', 'post', 'date', 'short_body') list_filter = ('date', 'user') # search_fields = ('body', 'post__model', 'user__username') readonly_fields = ('date',) def short_body(self, obj): return obj.body[:50] + '...' if len(obj.body) > 50 else obj.body short_body.short_description = 'Текст'