headphones_backend/headphones/admin.py

655 lines
21 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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', 'chip'),
('battery_life', 'charging_time'),
('has_charging_case', 'charging_case_battery_life', 'charging_case_time'),
'charging_interface',
'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('<img src="{}" style="max-height: 100px;"/>', 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('<img src="{}" width="50" height="50" />', 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
class FrequencyResponseInline(admin.StackedInline):
model = FrequencyResponse
extra = 1
fields = ('file', 'author', 'source_link', 'is_primary', 'notes', 'file_preview')
readonly_fields = ('file_preview',)
def file_preview(self, obj):
if obj.file:
return format_html(
'<a href="{}" target="_blank">{}</a>',
obj.file.url,
os.path.basename(obj.file.name)
)
return "-"
file_preview.short_description = "Файл"
@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', 'frequency_response_chart_author', 'frequency_response_chart_link'),
('microphone', 'noise_cancellation'),
'ip_rating',
]
}),
('Внешний вид', {
'fields': [
# 'colors',
('weight', 'release_year')
]
}),
('Метаданные', {
'fields': [
('date_added', 'published')
],
# 'classes': ('collapse',)
}),
]
inlines = [HeadphonesDriverInline, HeadphonesImagesInline, CaseColorsInline, FrequencyResponseInline]
# Методы для отображения
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('<a href="{}">{} обзоров</a>', 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("<br>".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', 'encoding')
search_fields = ('name', 'encoding')
prepopulated_fields = {'slug': ('name',)}
@admin.register(NoiseCancellationType)
class NoiseCancellationTypeAdmin(admin.ModelAdmin):
list_display = ('name', 'slug')
search_fields = ('name',)
prepopulated_fields = {'slug': ('name',)}
@admin.register(HeadphonesMicrophoneType)
class HeadphonesMicrophoneTypeAdmin(admin.ModelAdmin):
list_display = ('type', 'description')
search_fields = ('type',)
prepopulated_fields = {'type': ('description',)}
@admin.register(HeadphonesCaseIP)
class HeadphonesCaseIPAdmin(admin.ModelAdmin):
list_display = ('code', 'description')
search_fields = ('code',)
prepopulated_fields = {'code': ('description',)}
@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'<img src="{obj.image.url}" width="150" />')
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(WirelessHeadphonesChip)
class WWirelessHeadphonesChipAdmin(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('<a href="{}" target="_blank">{}</a>', 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('<a href="{}">{}</a>', 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('<a href="{}">{}</a>', url, obj.resource.name)
resource_link.short_description = "Ресурс"
resource_link.admin_order_field = 'resource__name'
def review_link(self, obj):
return format_html('<a href="{}" target="_blank">Открыть</a>', 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('<a href="{}">{}</a>', 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'<img src="{obj.photo.url}" width="150" />')
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 = 'Текст'