2026-09-07 19:43:48 +03:00

174 lines
6.7 KiB
Python
Raw Permalink 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.

"""
Management команда для очистки и оптимизации аватаров сотрудников.
Функции:
1. Удаление неиспользуемых файлов аватаров
2. Сжатие существующих аватаров до оптимального размера
"""
import os
from django.conf import settings
from django.core.files.base import ContentFile
from django.core.management.base import BaseCommand
from common.image_utils import compress_avatar
from employees.models import Employee
class Command(BaseCommand):
help = "Очистка неиспользуемых аватаров и оптимизация существующих"
def add_arguments(self, parser):
parser.add_argument(
"--compress",
action="store_true",
help="Сжать существующие аватары",
)
parser.add_argument(
"--cleanup",
action="store_true",
help="Удалить неиспользуемые файлы",
)
parser.add_argument(
"--dry-run",
action="store_true",
help="Показать что будет сделано без реального выполнения",
)
def handle(self, *args, **options):
dry_run = options["dry_run"]
do_compress = options["compress"]
do_cleanup = options["cleanup"]
# Если не указаны опции, делаем всё
if not do_compress and not do_cleanup:
do_compress = True
do_cleanup = True
if dry_run:
self.stdout.write(
self.style.WARNING("РЕЖИМ ПРОСМОТРА - изменения не применяются")
)
# Получаем все используемые файлы аватаров
employees = Employee.objects.exclude(avatar="").exclude(avatar=None)
used_files = set()
for emp in employees:
if emp.avatar:
try:
used_files.add(os.path.basename(emp.avatar.path))
except (OSError, ValueError):
continue
self.stdout.write(f"Найдено {len(used_files)} используемых аватаров")
# Очистка неиспользуемых файлов
if do_cleanup:
self.stdout.write("\n=== ОЧИСТКА НЕИСПОЛЬЗУЕМЫХ ФАЙЛОВ ===")
# Используем абсолютный путь из settings
avatar_dir = os.path.join(settings.MEDIA_ROOT, "users", "avatars")
if not os.path.exists(avatar_dir):
self.stdout.write(
self.style.WARNING(f"Директория {avatar_dir} не найдена")
)
else:
deleted_count = 0
freed_mb = 0
for filename in os.listdir(avatar_dir):
filepath = os.path.join(avatar_dir, filename)
if not os.path.isfile(filepath):
continue
if filename not in used_files:
size_mb = os.path.getsize(filepath) / (1024 * 1024)
freed_mb += size_mb
self.stdout.write(f" Удаление: {filename} ({size_mb:.2f} MB)")
if not dry_run:
try:
os.remove(filepath)
deleted_count += 1
except Exception as e:
self.stdout.write(
self.style.ERROR(f" Ошибка удаления: {e}")
)
self.stdout.write(
self.style.SUCCESS(
f"\nУдалено файлов: {deleted_count}, "
f"освобождено: {freed_mb:.2f} MB"
)
)
# Сжатие существующих аватаров
if do_compress:
self.stdout.write("\n=== СЖАТИЕ СУЩЕСТВУЮЩИХ АВАТАРОВ ===")
compressed_count = 0
saved_mb = 0
for emp in employees:
if not emp.avatar:
continue
try:
original_path = emp.avatar.path
original_size = os.path.getsize(original_path)
# Читаем оригинальный файл
with open(original_path, "rb") as f:
original_data = f.read()
# Сжимаем
compressed_data = compress_avatar(original_data)
compressed_size = len(compressed_data)
# Проверяем, дало ли сжатие выигрыш
if compressed_size < original_size * 0.95: # минимум 5%
size_diff = original_size - compressed_size
saved = size_diff / (1024 * 1024)
saved_mb += saved
self.stdout.write(
f" Сжатие: {emp.get_full_name()} "
f"({original_size / 1024:.1f}KB → "
f"{compressed_size / 1024:.1f}KB, "
f"экономия {saved * 1024:.1f}KB)"
)
if not dry_run:
# Сохраняем сжатый файл
filename = os.path.basename(original_path)
if not filename.lower().endswith(".jpg"):
filename = filename.rsplit(".", 1)[0] + ".jpg"
emp.avatar.save(
filename, ContentFile(compressed_data), save=True
)
compressed_count += 1
except Exception as e:
self.stdout.write(
self.style.ERROR(
f" Ошибка сжатия для {emp.get_full_name()}: {e}"
)
)
self.stdout.write(
self.style.SUCCESS(
f"\nСжато файлов: {compressed_count}, "
f"сэкономлено: {saved_mb:.2f} MB"
)
)
if dry_run:
self.stdout.write(
self.style.WARNING("\nДля применения изменений запустите без --dry-run")
)