diff --git a/headphones/admin.py b/headphones/admin.py index 8ada4fb..07721ef 100644 --- a/headphones/admin.py +++ b/headphones/admin.py @@ -176,6 +176,23 @@ class CaseColorsInline(admin.TabularInline): 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( + '{}', + obj.file.url, + os.path.basename(obj.file.name) + ) + return "-" + file_preview.short_description = "Файл" + + @admin.register(Headphones) class HeadphonesAdmin(admin.ModelAdmin): form = HeadphonesAdminForm @@ -209,7 +226,7 @@ class HeadphonesAdmin(admin.ModelAdmin): 'fields': [ ('impedance', 'sensitivity'), 'frequency_range', - ('frequency_response_chart', 'frequency_response_chart_author', 'frequency_response_chart_link'), + # ('frequency_response_chart', 'frequency_response_chart_author', 'frequency_response_chart_link'), ('microphone', 'noise_cancellation'), 'ip_rating', ] @@ -228,7 +245,7 @@ class HeadphonesAdmin(admin.ModelAdmin): }), ] - inlines = [HeadphonesDriverInline, HeadphonesImagesInline, CaseColorsInline] + inlines = [HeadphonesDriverInline, HeadphonesImagesInline, CaseColorsInline, FrequencyResponseInline] # Методы для отображения def get_reviews_count(self, obj): @@ -421,7 +438,6 @@ class AudioCodecAdmin(admin.ModelAdmin): # ====================== # Регистрация разъемов и подключений # ====================== - @admin.register(CableConnectionType) class CableConnectionTypeAdmin(admin.ModelAdmin): list_display = ('name', 'is_detachable') @@ -464,6 +480,7 @@ class ChargingInterfaceAdmin(admin.ModelAdmin): # Регистрация отзывов и медиа # ====================== + @admin.register(HeadphonesReviewType) class HeadphonesReviewTypeAdmin(admin.ModelAdmin): list_display = ('name', 'slug') diff --git a/headphones/models.py b/headphones/models.py index eae290f..7ecd6bc 100644 --- a/headphones/models.py +++ b/headphones/models.py @@ -24,7 +24,10 @@ def upload_to_case_image(instance, filename): return f"headphones/{instance.headphones.id}/case_images/{instance.slug}.{ext}" def upload_to_frequency_response(instance, filename): - return f"headphones/{instance.headphones.id}/frequency_response/{filename}" + """Путь для сохранения: headphones//frequency_response/""" + ext = filename.split('.')[-1] + unique_filename = f"{uuid.uuid4().hex}.{ext}" + return f"headphones/{instance.headphones.id}/frequency_response/{unique_filename}" # ====================== # Базовые справочники @@ -639,6 +642,67 @@ class HeadphonesCaseIP(models.Model): return self.code +class FrequencyResponse(models.Model): + headphones = models.ForeignKey( + 'Headphones', + on_delete=models.CASCADE, + related_name='frequency_responses', + verbose_name="Наушники" + ) + file = models.FileField( + upload_to=upload_to_frequency_response, + verbose_name="Файл АЧХ" + ) + author = models.CharField( + max_length=100, + verbose_name="Автор измерения", + blank=True, + null=True + ) + source_link = models.URLField( + verbose_name="Ссылка на источник", + blank=True, + null=True + ) + notes = models.TextField( + verbose_name="Примечания", + blank=True, + null=True + ) + is_primary = models.BooleanField( + default=False, + verbose_name="Основная АЧХ" + ) + + class Meta: + verbose_name = "АЧХ наушников" + verbose_name_plural = "АЧХ наушников" + ordering = ['-is_primary', 'headphones__model'] + + def __str__(self): + return f"АЧХ {self.headphones.model} ({self.author or 'без автора'})" + + def save(self, *args, **kwargs): + if self.is_primary: + # Снимаем флаг is_primary у других АЧХ этих наушников + FrequencyResponse.objects.filter( + headphones=self.headphones + ).exclude( + pk=self.pk + ).update(is_primary=False) + super().save(*args, **kwargs) + + def delete(self, *args, **kwargs): + """Удаление файла при удалении записи""" + if self.file: + try: + if default_storage.exists(self.file.name): + default_storage.delete(self.file.name) + except Exception as e: + print(f"Ошибка при удалении файла АЧХ: {e}") + super().delete(*args, **kwargs) + + # ====================== # Основная модель наушников # ====================== @@ -711,13 +775,13 @@ class Headphones(models.Model): ) # Технические характеристики - frequency_response_chart = models.FileField( - verbose_name="АЧХ (CSV)", - upload_to = upload_to_frequency_response, - null=True, - blank=True, - help_text="Данные в формате [{'frequency': 20, 'amplitude': -2.5}, ...]" - ) + # frequency_response_chart = models.FileField( + # verbose_name="АЧХ (CSV)", + # upload_to = upload_to_frequency_response, + # null=True, + # blank=True, + # help_text="Данные в формате [{'frequency': 20, 'amplitude': -2.5}, ...]" + # ) frequency_response_chart_author = models.CharField( verbose_name="Автор АЧХ",