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

166 lines
8.3 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.files.storage import FileSystemStorage
from PIL import Image as PILImage
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,
)
# Получаем логгер для текущего модуля
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, которая включает в себя:
# - Кастомное хранилище для миниатюр (ThumbnailFileSystemStorage)
# - Патчинг MultiStorageFieldFile.save() для автоматической конвертации в WebP
#
# Все остальные параметры конфигурации (MIME_TYPE_WHITELIST, FILER_WHITELIST_FOR_PATH_ACCESS и т.д.)
# определены в settings.py и импортируются оттуда.
# ==============================================================================
class CustomFilerConfig(AppConfig):
name = 'filer'
verbose_name = 'Медиафайлы'
# ========================================================================
# Атрибуты конфигурации Django-Filer (импортированы из settings.py)
# Единое место правды - 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.
"""
_, original_ext = os.path.splitext(name)
if original_ext.lower() in [".jpg", ".jpeg", ".png", ".bmp", ".tiff"]:
try:
content.seek(0)
img = PILImage.open(BytesIO(content.read()))
if img.mode == 'CMYK':
img = img.convert('RGB')
buffer = BytesIO()
img.save(buffer, format="WEBP", quality=THUMBNAIL_WEBP_QUALITY)
buffer.seek(0)
new_name = os.path.splitext(name)[0] + ".webp"
logger.info(f"Successfully converted '{name}' to '{new_name}' (WebP).")
return ContentFile(buffer.read()), new_name, True
except Exception:
logger.error(f"Error converting '{name}' to WebP.", exc_info=True)
content.seek(0)
return content, name, False
content.seek(0)
return content, name, False
def ready(self):
"""
Готовимся к работе: патчим MultiStorageFieldFile.save() для WebP конвертации.
"""
from filer.fields.multistorage_file import MultiStorageFieldFile
logger.info("Patching MultiStorageFieldFile.save() for WebP conversion...")
original_save = MultiStorageFieldFile.save
def patched_save(self_instance, name, content, save=True):
# Преобразуем загруженный файл в WebP если требуется
new_content, new_name, converted = CustomFilerConfig._convert_to_webp_if_needed(name, content)
if converted:
# Обновляем свойства файла для WebP версии
self_instance.instance.mime_type = "image/webp"
if hasattr(self_instance.instance, 'original_filename') and self_instance.instance.original_filename:
original_filename, _ = os.path.splitext(self_instance.instance.original_filename)
self_instance.instance.original_filename = f"{original_filename}.webp"
# Сохраняем размер и контрольную сумму WebP файла
new_content.seek(0)
file_bytes = new_content.read()
new_content.seek(0)
self_instance.instance._file_size = len(file_bytes)
self_instance.instance.sha1 = hashlib.sha1(file_bytes).hexdigest()
return original_save(self_instance, new_name, new_content, save)
MultiStorageFieldFile.save = patched_save
logger.info("MultiStorageFieldFile.save() patched successfully.")
# ==============================================================================
# Кастомное хранилище для миниатюр (часть конфигурации CustomFilerConfig)
# Удаляет префикс filer_public_thumbnails/ при сохранении и генерации URLs
# ==============================================================================
class ThumbnailFileSystemStorage(FileSystemStorage):
"""
Кастомное хранилище для миниатюр.
Удаляет префикс 'filer_public_thumbnails/' из пути сохранения и из URLs,
чтобы миниатюры хранились и отображались без этого каталога.
Используется как часть конфигурации CustomFilerConfig.
"""
def get_available_name(self, name, max_length=None):
"""
Переопределяем получение доступного имени файла.
Удаляем префикс 'filer_public_thumbnails/' если он присутствует.
"""
# Удаляем префикс filer_public_thumbnails/ если он есть
if name.startswith('filer_public_thumbnails/'):
name = name.replace('filer_public_thumbnails/', '', 1)
logger.debug(f"[ThumbnailFileSystemStorage.get_available_name] Удалён префикс filer_public_thumbnails/, новый путь: {name}")
return super().get_available_name(name, max_length)
def _save(self, name, content):
"""
Переопределяем сохранение файла.
Удаляем префикс 'filer_public_thumbnails/' перед сохранением на диск.
"""
# Удаляем префикс filer_public_thumbnails/ если он есть
if name.startswith('filer_public_thumbnails/'):
name = name.replace('filer_public_thumbnails/', '', 1)
logger.debug(f"[ThumbnailFileSystemStorage._save] Удалён префикс filer_public_thumbnails/, новый путь: {name}")
return super()._save(name, content)
def url(self, name):
"""
Переопределяем генерацию URL.
Удаляем префикс 'filer_public_thumbnails/' если он присутствует в URL.
Это необходимо потому, что easy_thumbnails может пытаться добавить этот префикс.
"""
# Если имя содержит префикс, удаляем его перед генерацией URL
if name.startswith('filer_public_thumbnails/'):
name = name.replace('filer_public_thumbnails/', '', 1)
logger.debug(f"[ThumbnailFileSystemStorage.url] Удалён префикс filer_public_thumbnails/ из URL, новый путь: {name}")
return super().url(name)