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 = 'Текст'