From 2a83135234377a54e6f210b56530d15fd1d91dc1 Mon Sep 17 00:00:00 2001 From: apheyhys Date: Sun, 4 May 2025 17:35:18 +0300 Subject: [PATCH] Added image in case color --- headphones/admin.py | 67 ++++++++++++++++++++++++++++++++----- headphones/models.py | 78 ++++++++++++++++++++++++++++++++++++-------- 2 files changed, 123 insertions(+), 22 deletions(-) diff --git a/headphones/admin.py b/headphones/admin.py index 3b5c98c..4e8a8dd 100644 --- a/headphones/admin.py +++ b/headphones/admin.py @@ -146,6 +146,34 @@ class HeadphonesDriverInline(admin.TabularInline): 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 @@ -154,7 +182,7 @@ class HeadphonesAdmin(admin.ModelAdmin): list_filter = ('brand', 'connection_type', 'published', 'case_type') search_fields = ('model', 'brand__name', 'description') prepopulated_fields = {'slug': ('brand', 'model')} - filter_horizontal = ('tags', 'colors') + filter_horizontal = ('tags',) list_editable = ('published',) actions = ['make_published', 'make_unpublished'] readonly_fields = ( @@ -186,7 +214,7 @@ class HeadphonesAdmin(admin.ModelAdmin): }), ('Внешний вид', { 'fields': [ - 'colors', + # 'colors', ('weight', 'release_year') ] }), @@ -198,7 +226,7 @@ class HeadphonesAdmin(admin.ModelAdmin): }), ] - inlines = [HeadphonesDriverInline, HeadphonesImagesInline] + inlines = [HeadphonesDriverInline, HeadphonesImagesInline, CaseColorsInline] # Методы для отображения def get_reviews_count(self, obj): @@ -223,10 +251,11 @@ class HeadphonesAdmin(admin.ModelAdmin): 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 = [ + # 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': @@ -288,8 +317,28 @@ class BrandCountryAdmin(admin.ModelAdmin): @admin.register(CaseColors) class CaseColorsAdmin(admin.ModelAdmin): - list_display = ('name', 'hex_code') - search_fields = ('name',) + 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) diff --git a/headphones/models.py b/headphones/models.py index c8d255f..719fb58 100644 --- a/headphones/models.py +++ b/headphones/models.py @@ -18,6 +18,11 @@ from PIL import Image def upload_to(instance, filename): return f"headphones/{instance.product.id}/{filename}" +def upload_to_case_image(instance, filename): + """Путь для сохранения: headphones//case_images/.""" + 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): - name = models.CharField(max_length=50, verbose_name="Название цвета") - hex_code = models.CharField(max_length=50, verbose_name="HEX-код цвета") + headphones = models.ForeignKey( + '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: - verbose_name = "Цвет корпуса" - verbose_name_plural = "Цвета корпусов" + def save(self, *args, **kwargs): + if not self.slug: + # Генерируем 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('', self.image.url) + return "Нет изображения" + + image_preview.short_description = 'Превью' + image_preview.allow_tags = True 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 = "Драйверы" 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): @@ -701,12 +743,13 @@ class Headphones(models.Model): blank=True ) - # Внешний вид и физические параметры - colors = models.ManyToManyField( - CaseColors, - related_name='headphones', - verbose_name="Цвета" - ) + # # Внешний вид и физические параметры + # colors = models.ManyToManyField( + # CaseColors, + # related_name='headphones', + # blank=True, + # verbose_name="Доступные цвета" + # ) weight = models.PositiveSmallIntegerField( verbose_name="Вес (г)", null=True, @@ -742,6 +785,15 @@ class Headphones(models.Model): self.slug = slugify(f"{self.brand.name} {self.model}") 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): headphones = models.ForeignKey( 'Headphones',