mod: django-filer настройка (08) размещение файлов
This commit is contained in:
@@ -4,14 +4,25 @@ 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 DEBUG, THUMBNAIL_WEBP_QUALITY
|
||||
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'
|
||||
@@ -23,32 +34,35 @@ class FrontendConfig(AppConfig):
|
||||
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, которая читается во время выполнения
|
||||
# Атрибуты конфигурации Django-Filer (импортированы из settings.py)
|
||||
# Единое место правды - settings.py
|
||||
# ========================================================================
|
||||
FILER_ENABLE_PERMISSIONS = DEBUG
|
||||
FILER_MAX_UPLOAD_SIZE = 100 * 1024 * 1024
|
||||
FILER_WHITELIST_FOR_PATH_ACCESS = (
|
||||
'.jpg', '.jpeg', '.png', '.gif', '.svg', '.webp',
|
||||
'.doc', '.docx', '.pdf', '.txt', '.xls', '.xlsx', '.csv',
|
||||
)
|
||||
MIME_TYPE_WHITELIST = (
|
||||
'image/jpeg', 'image/png', 'image/gif', 'image/svg+xml', 'image/webp',
|
||||
'application/pdf', 'application/msword',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'text/plain', 'application/vnd.ms-excel',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'text/csv',
|
||||
)
|
||||
FILE_VALIDATORS = {}
|
||||
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:
|
||||
@@ -70,25 +84,83 @@ class CustomFilerConfig(AppConfig):
|
||||
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)
|
||||
@@ -175,15 +175,16 @@ FILER_STORAGES = {
|
||||
'UPLOAD_TO_PREFIX': '',
|
||||
},
|
||||
'thumbnails': {
|
||||
'ENGINE': 'filer.storage.PublicFileSystemStorage',
|
||||
# Используем кастомное хранилище, которое удаляет префикс filer_public_thumbnails/
|
||||
# Смотримайте класс ThumbnailFileSystemStorage в frontend/apps.py (часть CustomFilerConfig)
|
||||
'ENGINE': 'frontend.apps.ThumbnailFileSystemStorage',
|
||||
'OPTIONS': {
|
||||
'location': MEDIA_ROOT / 'flrm',
|
||||
'base_url': MEDIA_URL + 'flrm/',
|
||||
# 'location': os.path.join(MEDIA_ROOT, 'flrm'),
|
||||
# 'base_url': os.path.join(MEDIA_URL, 'flrm/'),
|
||||
},
|
||||
# 'UPLOAD_TO': 'filer.utils.generate_filename.randomized',
|
||||
# 'UPLOAD_TO_PREFIX': '_',
|
||||
# Используем ту же функцию генерации пути, что и для основных файлов.
|
||||
'UPLOAD_TO': 'filer.utils.generate_filename.randomized',
|
||||
'UPLOAD_TO_PREFIX': '',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -206,6 +207,21 @@ FILER_UPLOADER_MAX_FILES = 3
|
||||
FILER_UPLOADER_MAX_FILE_SIZE = 100 * 1024 * 1024
|
||||
FILER_MAX_IMAGE_PIXELS = 4096 * 4096
|
||||
|
||||
FILER_ENABLE_PERMISSIONS = DEBUG
|
||||
FILER_WHITELIST_FOR_PATH_ACCESS = (
|
||||
'.jpg', '.jpeg', '.png', '.gif', '.svg', '.webp',
|
||||
'.doc', '.docx', '.pdf', '.txt', '.xls', '.xlsx', '.csv',
|
||||
)
|
||||
MIME_TYPE_WHITELIST = (
|
||||
'image/jpeg', 'image/png', 'image/gif', 'image/svg+xml', 'image/webp',
|
||||
'application/pdf', 'application/msword',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'text/plain', 'application/vnd.ms-excel',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'text/csv',
|
||||
)
|
||||
FILE_VALIDATORS = {}
|
||||
|
||||
# Настройки для "умной" обрезки изобращений
|
||||
THUMBNAIL_PROCESSORS = (
|
||||
'easy_thumbnails.processors.colorspace',
|
||||
|
||||
Reference in New Issue
Block a user