Files
2018-lpon-site/lpon_site/frontend/apps.py

162 lines
7.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import os
import hashlib
import logging
from io import BytesIO
from django.apps import AppConfig
from django.core.files.base import ContentFile
from django.core.exceptions import ValidationError
from PIL import Image as PILImage
import pillow_heif
from lpon_site.settings import (
THUMBNAIL_WEBP_QUALITY,
FILER_ENABLE_PERMISSIONS,
FILER_UPLOADER_MAX_FILE_SIZE,
FILER_WHITELIST_FOR_PATH_ACCESS,
MIME_TYPE_WHITELIST,
FILE_VALIDATORS,
)
# Регистрируем плагин HEIF в Pillow для поддержки HEIC/HEIF файлов
pillow_heif.register_heif_opener()
logger = logging.getLogger(__name__)
# ==============================================================================
# Конфигурация приложения для фронтенда lpon
# ==============================================================================
class FrontendConfig(AppConfig):
name = 'frontend'
verbose_name = 'Сайт lpon.ru'
default_auto_field = 'django.db.models.AutoField'
def ready(self):
from django.contrib import admin
admin.site.site_header = 'Управление LPON'
admin.site.site_title = 'LPON Administrator'
admin.site.index_title = 'Добро пожаловать в LPON'
# ==============================================================================
# Кастомная конфигурация для django-filer
# Патчинг MultiStorageFieldFile.save() для автоматической конвертации в WebP
# ==============================================================================
class CustomFilerConfig(AppConfig):
name = 'filer'
verbose_name = 'Медиафайлы'
# Атрибуты конфигурации Django-Filer (импортированы из settings.py)
FILER_ENABLE_PERMISSIONS = FILER_ENABLE_PERMISSIONS
FILER_UPLOADER_MAX_FILE_SIZE = FILER_UPLOADER_MAX_FILE_SIZE
FILER_WHITELIST_FOR_PATH_ACCESS = FILER_WHITELIST_FOR_PATH_ACCESS
MIME_TYPE_WHITELIST = MIME_TYPE_WHITELIST
FILE_VALIDATORS = FILE_VALIDATORS
@staticmethod
def _convert_to_webp_if_needed(name: str, content):
"""
Преобразует загруженное изображение в WebP формат.
Поддерживает JPEG, PNG, BMP, TIFF и HEIC/HEIF.
Возвращает кортеж: (content, new_name, was_converted, image_width, image_height)
Размеры ОБЯЗАТЕЛЬНО получаются из исходного изображения перед конвертацией.
При конвертации в WebP размеры пикселей не меняются, меняется только качество файла.
"""
_, original_ext = os.path.splitext(name)
convertible_extensions = [".jpg", ".jpeg", ".png", ".bmp", ".tiff", ".heic", ".heif"]
if original_ext.lower() in convertible_extensions:
try:
# Открываем исходный файл с помощью PIL
content.seek(0)
img = PILImage.open(BytesIO(content.read()))
# Получаем размеры изображения ДО конвертации (они не меняются при сохранении в WebP)
image_width, image_height = img.size
# Конвертируем CMYK в RGB если требуется
if img.mode == 'CMYK':
img = img.convert('RGB')
# Сохраняем в WebP формат в памяти
webp_buffer = BytesIO()
img.save(webp_buffer, format="WEBP", quality=THUMBNAIL_WEBP_QUALITY)
webp_data = webp_buffer.getvalue()
# Изменяем расширение на .webp
new_name = os.path.splitext(name)[0] + ".webp"
return ContentFile(webp_data), new_name, True, image_width, image_height
except Exception as e:
# Поднимаем ValidationError чтобы она показывалась в админке
error_msg = f"Ошибка при конвертации '{name}' в WebP: {str(e)}"
logger.error(error_msg, exc_info=True)
raise ValidationError(error_msg)
# Если файл не нуждается в конвертации, возвращаем как есть
content.seek(0)
return content, name, False, None, None
def ready(self):
"""
Инициализация: патчинг MultiStorageFieldFile.save() для WebP конвертации.
"""
from filer.fields.multistorage_file import MultiStorageFieldFile
from filer import settings as filer_settings
original_save = MultiStorageFieldFile.save
# Важно: Добавляем поддержку HEIC/HEIF файлов в filer
# IMAGE_EXTENSIONS это глобальные константы в filer, которые нельзя переопределить через settings.py
# Единственный способ - модифицировать после импорта в ready() методе 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):
"""
Патчированная версия MultiStorageFieldFile.save().
Конвертирует изображения в WebP и передает размеры для генерации миниатюр.
"""
new_content, new_name, converted, img_width, img_height = (
CustomFilerConfig._convert_to_webp_if_needed(name, content)
)
if converted:
# Устанавливаем correct 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
)
self_instance.instance.original_filename = f"{original_filename}.webp"
# КРИТИЧНО: Обновляем размер файла и sha1 на основе WebP данных, а не исходных
new_content.seek(0)
webp_bytes = new_content.read()
new_content.seek(0)
self_instance.instance._file_size = len(webp_bytes)
self_instance.instance.sha1 = hashlib.sha1(webp_bytes).hexdigest()
# Вызываем оригинальный save() метод
result = original_save(self_instance, new_name, new_content, save)
# После сохранения гарантируем что mime_type=image/webp в БД
if converted and new_name.lower().endswith('.webp'):
if self_instance.instance.id:
self_instance.instance.__class__.objects.filter(
id=self_instance.instance.id
).update(mime_type='image/webp')
return result
MultiStorageFieldFile.save = patched_save