Compare commits
2 commits
3a1dbab9e2
...
2a83135234
| Author | SHA1 | Date | |
|---|---|---|---|
| 2a83135234 | |||
| f4f7790f81 |
2 changed files with 141 additions and 28 deletions
|
|
@ -146,6 +146,34 @@ class HeadphonesDriverInline(admin.TabularInline):
|
||||||
autocomplete_fields = ['driver']
|
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
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Headphones)
|
@admin.register(Headphones)
|
||||||
class HeadphonesAdmin(admin.ModelAdmin):
|
class HeadphonesAdmin(admin.ModelAdmin):
|
||||||
form = HeadphonesAdminForm
|
form = HeadphonesAdminForm
|
||||||
|
|
@ -154,7 +182,7 @@ class HeadphonesAdmin(admin.ModelAdmin):
|
||||||
list_filter = ('brand', 'connection_type', 'published', 'case_type')
|
list_filter = ('brand', 'connection_type', 'published', 'case_type')
|
||||||
search_fields = ('model', 'brand__name', 'description')
|
search_fields = ('model', 'brand__name', 'description')
|
||||||
prepopulated_fields = {'slug': ('brand', 'model')}
|
prepopulated_fields = {'slug': ('brand', 'model')}
|
||||||
filter_horizontal = ('tags', 'colors')
|
filter_horizontal = ('tags',)
|
||||||
list_editable = ('published',)
|
list_editable = ('published',)
|
||||||
actions = ['make_published', 'make_unpublished']
|
actions = ['make_published', 'make_unpublished']
|
||||||
readonly_fields = (
|
readonly_fields = (
|
||||||
|
|
@ -186,7 +214,7 @@ class HeadphonesAdmin(admin.ModelAdmin):
|
||||||
}),
|
}),
|
||||||
('Внешний вид', {
|
('Внешний вид', {
|
||||||
'fields': [
|
'fields': [
|
||||||
'colors',
|
# 'colors',
|
||||||
('weight', 'release_year')
|
('weight', 'release_year')
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
|
|
@ -198,7 +226,7 @@ class HeadphonesAdmin(admin.ModelAdmin):
|
||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
|
|
||||||
inlines = [HeadphonesDriverInline, HeadphonesImagesInline]
|
inlines = [HeadphonesDriverInline, HeadphonesImagesInline, CaseColorsInline]
|
||||||
|
|
||||||
# Методы для отображения
|
# Методы для отображения
|
||||||
def get_reviews_count(self, obj):
|
def get_reviews_count(self, obj):
|
||||||
|
|
@ -223,10 +251,11 @@ class HeadphonesAdmin(admin.ModelAdmin):
|
||||||
driver_configuration_display.short_description = 'Конфигурация драйверов'
|
driver_configuration_display.short_description = 'Конфигурация драйверов'
|
||||||
|
|
||||||
def get_inline_instances(self, request, obj=None):
|
def get_inline_instances(self, request, obj=None):
|
||||||
inlines = [
|
# inlines = [
|
||||||
HeadphonesDriverInline(self.model, self.admin_site),
|
# HeadphonesDriverInline(self.model, self.admin_site),
|
||||||
HeadphonesImagesInline(self.model, self.admin_site)
|
# HeadphonesImagesInline(self.model, self.admin_site)
|
||||||
]
|
# ]
|
||||||
|
inlines = super().get_inline_instances(request, obj)
|
||||||
|
|
||||||
if obj:
|
if obj:
|
||||||
if obj.connection_type == 'wired':
|
if obj.connection_type == 'wired':
|
||||||
|
|
@ -249,12 +278,27 @@ class BrandAdmin(admin.ModelAdmin):
|
||||||
prepopulated_fields = {'slug': ('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)
|
@admin.register(Driver)
|
||||||
class DriverAdmin(admin.ModelAdmin):
|
class DriverAdmin(admin.ModelAdmin):
|
||||||
list_display = ('driver_type', 'size', 'frequency_range')
|
list_display = ('driver_type', 'driver_model', 'size', 'frequency_range')
|
||||||
list_filter = ('driver_type',)
|
list_filter = ('driver_type',)
|
||||||
search_fields = ('driver_type__name', 'size', 'frequency_range') # Добавлено
|
search_fields = ('driver_type__name', 'driver_model__name', 'size', 'frequency_range') # Добавлено
|
||||||
autocomplete_fields = ['driver_type'] # Если нужно автозаполнение для типа
|
autocomplete_fields = ['driver_type', 'driver_model']
|
||||||
|
|
||||||
def get_queryset(self, request):
|
def get_queryset(self, request):
|
||||||
return super().get_queryset(request).select_related('driver_type')
|
return super().get_queryset(request).select_related('driver_type')
|
||||||
|
|
@ -273,8 +317,28 @@ class BrandCountryAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
@admin.register(CaseColors)
|
@admin.register(CaseColors)
|
||||||
class CaseColorsAdmin(admin.ModelAdmin):
|
class CaseColorsAdmin(admin.ModelAdmin):
|
||||||
list_display = ('name', 'hex_code')
|
list_display = ('name_ru', 'name_en', 'headphones', 'image_preview', 'slug')
|
||||||
search_fields = ('name',)
|
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)
|
@admin.register(CaseType)
|
||||||
|
|
@ -449,9 +513,6 @@ class HeadphonesReviewAuthorInline(admin.StackedInline):
|
||||||
filter_horizontal = ('resources',)
|
filter_horizontal = ('resources',)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(HeadphonesReview)
|
@admin.register(HeadphonesReview)
|
||||||
class HeadphonesReviewAdmin(admin.ModelAdmin):
|
class HeadphonesReviewAdmin(admin.ModelAdmin):
|
||||||
list_display = (
|
list_display = (
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,11 @@ from PIL import Image
|
||||||
def upload_to(instance, filename):
|
def upload_to(instance, filename):
|
||||||
return f"headphones/{instance.product.id}/{filename}"
|
return f"headphones/{instance.product.id}/{filename}"
|
||||||
|
|
||||||
|
def upload_to_case_image(instance, filename):
|
||||||
|
"""Путь для сохранения: headphones/<product_id>/case_images/<slug>.<ext>"""
|
||||||
|
ext = filename.split('.')[-1]
|
||||||
|
return f"headphones/{instance.headphones.id}/case_images/{instance.slug}.{ext}"
|
||||||
|
|
||||||
# ======================
|
# ======================
|
||||||
# Базовые справочники
|
# Базовые справочники
|
||||||
# ======================
|
# ======================
|
||||||
|
|
@ -33,16 +38,45 @@ class BrandCountry(models.Model):
|
||||||
|
|
||||||
|
|
||||||
class CaseColors(models.Model):
|
class CaseColors(models.Model):
|
||||||
name = models.CharField(max_length=50, verbose_name="Название цвета")
|
headphones = models.ForeignKey(
|
||||||
hex_code = models.CharField(max_length=50, verbose_name="HEX-код цвета")
|
'Headphones',
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name='case_colors',
|
||||||
|
verbose_name='Наушники'
|
||||||
|
)
|
||||||
|
name_ru = models.CharField('Название цвета (рус)', max_length=100)
|
||||||
|
name_en = models.CharField('Название цвета (англ)', max_length=100)
|
||||||
|
slug = models.SlugField(max_length=150, unique=True, blank=True, verbose_name="URL-идентификатор")
|
||||||
|
image = models.ImageField('Изображение', upload_to=upload_to_case_image)
|
||||||
|
|
||||||
class Meta:
|
def save(self, *args, **kwargs):
|
||||||
verbose_name = "Цвет корпуса"
|
if not self.slug:
|
||||||
verbose_name_plural = "Цвета корпусов"
|
# Генерируем slug в формате "модельнаушников_цветангл"
|
||||||
|
base_slug = slugify(f"{self.headphones.model}_{self.name_en}")
|
||||||
|
self.slug = base_slug
|
||||||
|
|
||||||
|
# Проверяем уникальность slug
|
||||||
|
counter = 1
|
||||||
|
while CaseColors.objects.filter(slug=self.slug).exists():
|
||||||
|
self.slug = f"{base_slug}-{counter}"
|
||||||
|
counter += 1
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
def image_preview(self):
|
||||||
|
if self.image:
|
||||||
|
return format_html('<img src="{}" width="50" height="50" />', self.image.url)
|
||||||
|
return "Нет изображения"
|
||||||
|
|
||||||
|
image_preview.short_description = 'Превью'
|
||||||
|
image_preview.allow_tags = True
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.name} {self.hex_code}"
|
return f"{self.name_ru} ({self.headphones.model})"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = 'Цвет корпуса'
|
||||||
|
verbose_name_plural = 'Цвета корпусов'
|
||||||
|
ordering = ['headphones__model', 'name_ru']
|
||||||
|
|
||||||
# ======================
|
# ======================
|
||||||
# Основные модели брендов и категорий
|
# Основные модели брендов и категорий
|
||||||
|
|
@ -212,7 +246,15 @@ class Driver(models.Model):
|
||||||
verbose_name_plural = "Драйверы"
|
verbose_name_plural = "Драйверы"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.driver_type.name} {self.size}mm"
|
parts = [self.driver_type.name]
|
||||||
|
|
||||||
|
if self.driver_model:
|
||||||
|
parts.append(self.driver_model.name)
|
||||||
|
|
||||||
|
if self.size:
|
||||||
|
parts.append(f"{self.size}mm")
|
||||||
|
|
||||||
|
return " ".join(parts)
|
||||||
|
|
||||||
|
|
||||||
class NoiseCancellationType(models.Model):
|
class NoiseCancellationType(models.Model):
|
||||||
|
|
@ -701,12 +743,13 @@ class Headphones(models.Model):
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# Внешний вид и физические параметры
|
# # Внешний вид и физические параметры
|
||||||
colors = models.ManyToManyField(
|
# colors = models.ManyToManyField(
|
||||||
CaseColors,
|
# CaseColors,
|
||||||
related_name='headphones',
|
# related_name='headphones',
|
||||||
verbose_name="Цвета"
|
# blank=True,
|
||||||
)
|
# verbose_name="Доступные цвета"
|
||||||
|
# )
|
||||||
weight = models.PositiveSmallIntegerField(
|
weight = models.PositiveSmallIntegerField(
|
||||||
verbose_name="Вес (г)",
|
verbose_name="Вес (г)",
|
||||||
null=True,
|
null=True,
|
||||||
|
|
@ -742,6 +785,15 @@ class Headphones(models.Model):
|
||||||
self.slug = slugify(f"{self.brand.name} {self.model}")
|
self.slug = slugify(f"{self.brand.name} {self.model}")
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
# Обновляем slug для связанных цветов, если изменилась модель
|
||||||
|
if self.pk and hasattr(self, 'case_colors'):
|
||||||
|
for color in self.case_colors.all():
|
||||||
|
new_slug = slugify(f"{self.model}_{color.name_en}")
|
||||||
|
if color.slug != new_slug:
|
||||||
|
color.slug = new_slug
|
||||||
|
color.save()
|
||||||
|
|
||||||
|
|
||||||
class HeadphonesConnector(models.Model):
|
class HeadphonesConnector(models.Model):
|
||||||
headphones = models.ForeignKey(
|
headphones = models.ForeignKey(
|
||||||
'Headphones',
|
'Headphones',
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue