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

176 lines
7.2 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
from io import BytesIO
from django.apps import AppConfig
from django.core.files.storage import FileSystemStorage
from django.core.files.base import ContentFile
from PIL import Image as PILImage
from lpon_site.settings import DEBUG, MEDIA_ROOT
# 0. Кастомная функция генерирования имен файлов специально для WebP
def generate_filename_webp(instance, filename):
"""
Генерирует имя файла для сохранения. Если исходный файл был конвертирован в WebP,
гарантирует что расширение файла будет .webp.
"""
_, ext = os.path.splitext(filename)
if ext.lower() in [".jpg", ".jpeg", ".png", ".bmp", ".tiff"]:
base_path = filename.rsplit(ext, 1)[0]
return f"{base_path}.webp"
return filename
# 0.1. Функция для генерирования путей с префиксом 'flr/' для основных файлов
def generate_upload_path_flr(instance, filename):
"""
Генерирует путь для сохранения файла с префиксом 'flr/'.
"""
from filer.utils.generate_filename import randomized
base_path = randomized(instance, filename)
return f'flr/{base_path}'
# 0.2. Функция для генерирования путей с префиксом 'flrm/' для миниатюр
def generate_upload_path_flrm(instance, filename):
"""
Генерирует путь для сохранения миниатюр с префиксом 'flrm/'.
"""
from filer.utils.generate_filename import randomized
base_path = randomized(instance, filename)
return f'flrm/{base_path}'
# 1.
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'
# 2.
class FilerWebPStorage(FileSystemStorage):
"""
Кастомное хранилище для Filer. Основная логика конвертации перенесена
в патч для MultiStorageFieldFile.save, чтобы обновлять метаданные до сохранения.
"""
def _save(self, name: str, content):
return super()._save(name, content)
# Добавляем кастомный конфиг для filer
class CustomFilerConfig(AppConfig):
name = 'filer'
verbose_name = 'Медиафайлы'
# Конфигурация Django-Filer
FILER_ENABLE_PERMISSIONS = DEBUG
FILER_WHITELIST_FOR_PATH_ACCESS = (
'.jpg', '.jpeg', '.png', '.gif', '.svg', '.webp',
'.doc', '.docx', '.pdf', '.txt', '.xls', '.xlsx', '.csv',
)
FILER_MAX_UPLOAD_SIZE = 100 * 1024 * 1024
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',
)
THUMBNAIL_ALIASES = {
'': {
'admin_thumbnail': {'size': (64, 64), 'crop': True},
'small': {'size': (256, 256), 'crop': True},
'medium': {'size': (512, 512), 'crop': True},
'large': {'size': (1024, 1024), 'crop': 'smart'},
},
}
THUMBNAIL_PRESERVE_FORMAT = False
THUMBNAIL_FORCE_FORMAT = 'WEBP'
THUMBNAIL_WEBP_QUALITY = 80
THUMBNAIL_ENGINE = 'easy_thumbnails.engines.pil_engine.PilEngine'
THUMBNAIL_DEBUG = DEBUG
FILE_VALIDATORS = {}
def ready(self):
import sys
from filer.fields.multistorage_file import MultiStorageFieldFile
print("[CustomFilerConfig.ready] Patching MultiStorageFieldFile.save()...", file=sys.stderr)
original_save = MultiStorageFieldFile.save
webp_converter = WebPConverter()
def patched_save(self, name, content, save=True):
"""
Переопределенный save(), который преобразует изображение в WebP, обновляет
метаданные модели (MIME-тип, размер, SHA1) и затем сохраняет.
"""
new_content, new_name, converted = webp_converter.convert_to_webp_if_needed(name, content)
if converted:
# Обновляем метаданные прямо в инстансе модели
self.instance.mime_type = "image/webp"
if hasattr(self.instance, 'original_filename') and self.instance.original_filename:
original_filename, _ = os.path.splitext(self.instance.original_filename)
self.instance.original_filename = f"{original_filename}.webp"
# Вручную пересчитываем размер и SHA1-хэш для нового файла .webp
new_content.seek(0)
file_bytes = new_content.read()
new_content.seek(0)
self.instance._file_size = len(file_bytes)
self.instance.sha1 = hashlib.sha1(file_bytes).hexdigest()
# Вызываем оригинальный метод save() с (возможно) новым контентом и именем
return original_save(self, new_name, new_content, save)
MultiStorageFieldFile.save = patched_save
print("[CustomFilerConfig.ready] MultiStorageFieldFile.save() patched successfully", file=sys.stderr)
class WebPConverter:
"""
Класс, инкапсулирующий логику преобразования изображений в WebP.
"""
def convert_to_webp_if_needed(self, name: str, content):
"""
Проверяет формат файла и, если это необходимо, преобразует его в WebP.
Возвращает кортеж (новое_содержимое, новое_имя, флагонвертации).
"""
_, 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=80)
buffer.seek(0)
new_name = name.rsplit(original_ext, 1)[0] + ".webp"
return ContentFile(buffer.read()), new_name, True
except Exception as e:
import sys
print(f"[WebPConverter] ERROR converting {name}: {e}", file=sys.stderr)
content.seek(0)
return content, name, False
content.seek(0)
return content, name, False