mod: django-filer настройка (14) fine

This commit is contained in:
2026-06-10 17:19:55 +03:00
parent 71cac55221
commit 0ad98fec9d
2 changed files with 58 additions and 15 deletions

View File

@@ -2,9 +2,10 @@ import os
import hashlib
import logging
from io import BytesIO
from typing import Tuple
from django.apps import AppConfig
from django.core.files.base import ContentFile
from django.core.files.base import ContentFile, File
from django.core.exceptions import ValidationError
from PIL import Image as PILImage
import pillow_heif
@@ -21,6 +22,17 @@ from lpon_site.settings import (
# Регистрируем плагин HEIF в Pillow для поддержки HEIC/HEIF файлов
pillow_heif.register_heif_opener()
# Настройка параллельного декодирования HEIF/HEIC файлов
# DECODE_THREADS: количество потоков для декодирования
# - 0 = использовать количество ядер процессора (рекомендуется)
# - N > 0 = использовать N потоков (больше потоков = быстрее, но больше памяти)
pillow_heif.options.DECODE_THREADS = 0
# Дополнительные опции Pillow для обработки изображений
# LOAD_TRUNCATED_IMAGES: загружать некорректно завершенные изображения
# (полезно для некачественных или поврежденных файлов)
PILImage.LOAD_TRUNCATED_IMAGES = True
logger = logging.getLogger(__name__)
@@ -32,7 +44,8 @@ class FrontendConfig(AppConfig):
verbose_name = 'Сайт lpon.ru'
default_auto_field = 'django.db.models.AutoField'
def ready(self):
def ready(self) -> None:
"""Инициализация Django Admin с кастомным заголовком и названиями."""
from django.contrib import admin
admin.site.site_header = 'Управление LPON'
admin.site.site_title = 'LPON Administrator'
@@ -55,19 +68,35 @@ class CustomFilerConfig(AppConfig):
FILE_VALIDATORS = FILE_VALIDATORS
@staticmethod
def _convert_to_webp_if_needed(name: str, content):
def _convert_to_webp_if_needed(
name: str, content: File
) -> Tuple[File, str, bool, int | None, int | None]:
"""
Преобразует загруженное изображение в WebP формат.
Поддерживает JPEG, PNG, BMP, TIFF и HEIC/HEIF.
Возвращает кортеж: (content, new_name, was_converted, image_width, image_height)
Args:
name: Имя файла с расширением
content: Содержимое файла (Django File object)
Returns:
Кортеж: (content, new_name, was_converted, image_width, image_height)
- content: WebP файл или исходный файл
- new_name: Новое имя файла (.webp)
- was_converted: Был ли произведен конвертирование
- image_width: Ширина изображения в пикселях (None если не конвертирован)
- image_height: Высота изображения в пикселях (None если не конвертирован)
Raises:
ValidationError: Если произошла ошибка при конвертации
Размеры ОБЯЗАТЕЛЬНО получаются из исходного изображения перед конвертацией.
При конвертации в WebP размеры пикселей не меняются, меняется только качество файла.
"""
_, original_ext = os.path.splitext(name)
convertible_extensions = [".jpg", ".jpeg", ".png", ".bmp", ".tiff", ".heic", ".heif"]
# PNG исключено, т.к. их планирую использовать для иконок (может быть, верну позже)
convertible_extensions = [".jpg", ".jpeg", ".bmp", ".tiff", ".heic", ".heif"]
if original_ext.lower() in convertible_extensions:
try:
@@ -102,9 +131,10 @@ class CustomFilerConfig(AppConfig):
content.seek(0)
return content, name, False, None, None
def ready(self):
def ready(self) -> None:
"""
Инициализация: патчинг MultiStorageFieldFile.save() для WebP конвертации.
Добавление поддержки HEIC/HEIF в filer.
"""
from filer.fields.multistorage_file import MultiStorageFieldFile
from filer import settings as filer_settings
@@ -117,25 +147,34 @@ class CustomFilerConfig(AppConfig):
filer_settings.IMAGE_EXTENSIONS = list(set(filer_settings.IMAGE_EXTENSIONS + ['.heic', '.heif']))
filer_settings.IMAGE_MIME_TYPES = list(set(filer_settings.IMAGE_MIME_TYPES + ['heic', 'heif']))
def patched_save(self_instance, name, content, save=True):
def patched_save(
self_instance, name: str, content: File, save: bool = True
) -> str | None:
"""
Патчированная версия MultiStorageFieldFile.save().
Конвертирует изображения в WebP и передает размеры для генерации миниатюр.
Патчированг MultiStorageFieldFile.save().
Конвертирует загруженные картинки в WebP и обновляет метаданные.
Args:
self_instance: Экземпляр MultiStorageFieldFile
name: Имя файла
content: Содержимое файла
save: Нужно ли сохранять в БД (по умолчанию True)
Returns:
Имя сохраненного файла или None
"""
new_content, new_name, converted, img_width, img_height = (
CustomFilerConfig._convert_to_webp_if_needed(name, content)
)
if converted:
# Устанавливаем correct mime_type для WebP
# Переустанавливаем корректный mime_type (всё станет WebP)
self_instance.instance.mime_type = "image/webp"
# Обновляем original_filename если есть
if hasattr(self_instance.instance, 'original_filename'):
if self_instance.instance.original_filename:
original_filename, _ = os.path.splitext(
self_instance.instance.original_filename
)
original_filename, _ = os.path.splitext(self_instance.instance.original_filename)
self_instance.instance.original_filename = f"{original_filename}.webp"
# КРИТИЧНО: Обновляем размер файла и sha1 на основе WebP данных, а не исходных
@@ -158,4 +197,3 @@ class CustomFilerConfig(AppConfig):
return result
MultiStorageFieldFile.save = patched_save

View File

@@ -163,6 +163,11 @@ STATIC_ROOT = PUBLIC_DIR.joinpath('staticfiles')
# ============================================================================
# Django-Filer & Easy-Thumbnails Configuration
# ============================================================================
# Примечание о потоках обработки:
# - Django: количество worker-процессов настраивается через gunicorn/uWSGI (--workers, --threads)
# - Pillow-HEIF: параллелизм настраивается через pillow_heif.options.DECODE_THREADS в apps.py
# - Easy-Thumbnails: использует Pillow напрямую, параллелизм через Pillow
# ============================================================================
FILER_STORAGES = {
'public': {
'main': {
@@ -197,7 +202,7 @@ THUMBNAIL_PRESERVE_FORMAT = False
THUMBNAIL_FORMAT = 'WEBP'
THUMBNAIL_DEBUG = DEBUG
THUMBNAIL_QUALITY= 80
THUMBNAIL_WEBP_QUALITY = THUMBNAIL_QUALITY
THUMBNAIL_WEBP_QUALITY = THUMBNAIL_QUALITY # Наша, кастомная настройка для WebP-сжатия исходных картинок
THUMBNAIL_ENGINE = 'easy_thumbnails.engines.pil_engine.PilEngine'
# Список расширений, которые нужно сохранять при генерации миниатюр