131cb27d16
Django project: employees, locations, visit_manager, it_manager, mattermost_connector and stub connectors (ad, mail, portal).
110 lines
4.0 KiB
Python
110 lines
4.0 KiB
Python
# ---------------------------------------------------------------------------
|
|
# management/commands/cleanup_old_photos.py
|
|
# ---------------------------------------------------------------------------
|
|
# python manage.py cleanup_old_photos --days 90
|
|
# ---------------------------------------------------------------------------
|
|
import os
|
|
from datetime import timedelta
|
|
from django.core.management.base import BaseCommand
|
|
from django.utils import timezone
|
|
from attendance.models import AttendanceLog
|
|
|
|
|
|
class Command(BaseCommand):
|
|
help = 'Удаляет оригиналы фото старше N дней (превью остаются в БД)'
|
|
|
|
def add_arguments(self, parser):
|
|
parser.add_argument('--days', type=int, default=90,
|
|
help='Retention period в днях (default: 90)')
|
|
parser.add_argument('--dry-run', action='store_true',
|
|
help='Только показать, что будет удалено')
|
|
|
|
def handle(self, *args, **options):
|
|
cutoff = timezone.now() - timedelta(days=options['days'])
|
|
qs = AttendanceLog.objects.filter(
|
|
timestamp__lt=cutoff,
|
|
photo__isnull=False,
|
|
).exclude(photo='')
|
|
|
|
self.stdout.write(f'Найдено записей: {qs.count()} (старше {cutoff.date()})')
|
|
|
|
if options['dry_run']:
|
|
self.stdout.write('[dry-run] Удаление не выполняется.')
|
|
return
|
|
|
|
deleted = 0
|
|
for log in qs.iterator(chunk_size=500):
|
|
if log.photo and os.path.isfile(log.photo.path):
|
|
os.remove(log.photo.path)
|
|
log.photo = ''
|
|
log.save(update_fields=['photo'])
|
|
deleted += 1
|
|
|
|
self.stdout.write(self.style.SUCCESS(f'Удалено файлов: {deleted}'))
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# settings snippet
|
|
# ---------------------------------------------------------------------------
|
|
"""
|
|
MEDIA_ROOT = '/var/lib/acore-visits/media'
|
|
MEDIA_URL = '/media/'
|
|
|
|
# Retention (дней хранения оригиналов фото)
|
|
ATTENDANCE_PHOTO_RETENTION_DAYS = 90
|
|
|
|
# Celery beat task (если используется Celery)
|
|
CELERY_BEAT_SCHEDULE = {
|
|
'cleanup-old-photos': {
|
|
'task': 'attendance.tasks.cleanup_old_photos',
|
|
'schedule': crontab(hour=3, minute=0), # каждую ночь в 03:00
|
|
},
|
|
}
|
|
"""
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Структура директорий на сервере
|
|
# ---------------------------------------------------------------------------
|
|
"""
|
|
/var/lib/acore-visits/
|
|
media/
|
|
photos/
|
|
2026/
|
|
04/
|
|
24/
|
|
42/ ← employee_id
|
|
a3f1bc.jpg
|
|
d7e2ff.jpg
|
|
57/
|
|
...
|
|
25/
|
|
...
|
|
|
|
ВАЖНО:
|
|
- Директория принадлежит пользователю www-data (или acore)
|
|
- Монтировать как NFS/samba если несколько app-серверов
|
|
- В PBS делать отдельный job на /var/lib/acore-visits/media/
|
|
- Для PPOS: фото хранятся локально, путь тот же,
|
|
синк на центр — только метаданные AttendanceLog (без photo_thumb)
|
|
"""
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Индексы миграции (добавить в Migration.operations)
|
|
# ---------------------------------------------------------------------------
|
|
"""
|
|
migrations.AddIndex(
|
|
model_name='attendancelog',
|
|
index=models.Index(fields=['employee', 'timestamp'], name='att_emp_ts_idx'),
|
|
),
|
|
migrations.AddIndex(
|
|
model_name='attendancelog',
|
|
index=models.Index(fields=['location', 'timestamp'], name='att_loc_ts_idx'),
|
|
),
|
|
migrations.AddIndex(
|
|
model_name='attendancelog',
|
|
index=models.Index(fields=['synced'], name='att_synced_idx'),
|
|
),
|
|
"""
|