mod: django-filer настройка (14) fine
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|
||||||
# Список расширений, которые нужно сохранять при генерации миниатюр
|
# Список расширений, которые нужно сохранять при генерации миниатюр
|
||||||
|
|||||||
Reference in New Issue
Block a user