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

View File

@@ -163,6 +163,11 @@ STATIC_ROOT = PUBLIC_DIR.joinpath('staticfiles')
# ============================================================================ # ============================================================================
# Django-Filer & Easy-Thumbnails Configuration # 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 = { FILER_STORAGES = {
'public': { 'public': {
'main': { 'main': {
@@ -197,7 +202,7 @@ THUMBNAIL_PRESERVE_FORMAT = False
THUMBNAIL_FORMAT = 'WEBP' THUMBNAIL_FORMAT = 'WEBP'
THUMBNAIL_DEBUG = DEBUG THUMBNAIL_DEBUG = DEBUG
THUMBNAIL_QUALITY= 80 THUMBNAIL_QUALITY= 80
THUMBNAIL_WEBP_QUALITY = THUMBNAIL_QUALITY THUMBNAIL_WEBP_QUALITY = THUMBNAIL_QUALITY # Наша, кастомная настройка для WebP-сжатия исходных картинок
THUMBNAIL_ENGINE = 'easy_thumbnails.engines.pil_engine.PilEngine' THUMBNAIL_ENGINE = 'easy_thumbnails.engines.pil_engine.PilEngine'
# Список расширений, которые нужно сохранять при генерации миниатюр # Список расширений, которые нужно сохранять при генерации миниатюр