init commit
This commit is contained in:
423
admin_utils.py
Normal file
423
admin_utils.py
Normal file
@@ -0,0 +1,423 @@
|
||||
"""
|
||||
Дополнительные утилиты для админ-панели
|
||||
"""
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, delete, update, func
|
||||
from models import User, Lottery, Participation, Winner
|
||||
from typing import List, Dict, Optional
|
||||
import csv
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class AdminUtils:
|
||||
"""Утилиты для админ-панели"""
|
||||
|
||||
@staticmethod
|
||||
async def get_lottery_statistics(session: AsyncSession, lottery_id: int) -> Dict:
|
||||
"""Получить детальную статистику по розыгрышу"""
|
||||
lottery = await session.get(Lottery, lottery_id)
|
||||
if not lottery:
|
||||
return {}
|
||||
|
||||
# Количество участников
|
||||
participants_count = await session.scalar(
|
||||
select(func.count(Participation.id))
|
||||
.where(Participation.lottery_id == lottery_id)
|
||||
)
|
||||
|
||||
# Победители
|
||||
winners_count = await session.scalar(
|
||||
select(func.count(Winner.id))
|
||||
.where(Winner.lottery_id == lottery_id)
|
||||
)
|
||||
|
||||
# Ручные победители
|
||||
manual_winners_count = await session.scalar(
|
||||
select(func.count(Winner.id))
|
||||
.where(Winner.lottery_id == lottery_id, Winner.is_manual == True)
|
||||
)
|
||||
|
||||
# Участники по дням
|
||||
participants_by_date = await session.execute(
|
||||
select(
|
||||
func.date(Participation.created_at).label('date'),
|
||||
func.count(Participation.id).label('count')
|
||||
)
|
||||
.where(Participation.lottery_id == lottery_id)
|
||||
.group_by(func.date(Participation.created_at))
|
||||
.order_by(func.date(Participation.created_at))
|
||||
)
|
||||
|
||||
return {
|
||||
'lottery': lottery,
|
||||
'participants_count': participants_count,
|
||||
'winners_count': winners_count,
|
||||
'manual_winners_count': manual_winners_count,
|
||||
'random_winners_count': winners_count - manual_winners_count,
|
||||
'participants_by_date': participants_by_date.fetchall()
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
async def export_lottery_data(session: AsyncSession, lottery_id: int) -> Dict:
|
||||
"""Экспорт данных розыгрыша"""
|
||||
lottery = await session.get(Lottery, lottery_id)
|
||||
if not lottery:
|
||||
return {}
|
||||
|
||||
# Участники
|
||||
participants = await session.execute(
|
||||
select(User, Participation)
|
||||
.join(Participation)
|
||||
.where(Participation.lottery_id == lottery_id)
|
||||
.order_by(Participation.created_at)
|
||||
)
|
||||
participants_data = []
|
||||
for user, participation in participants:
|
||||
participants_data.append({
|
||||
'telegram_id': user.telegram_id,
|
||||
'username': user.username,
|
||||
'first_name': user.first_name,
|
||||
'last_name': user.last_name,
|
||||
'joined_at': participation.created_at.isoformat()
|
||||
})
|
||||
|
||||
# Победители
|
||||
winners = await session.execute(
|
||||
select(Winner, User)
|
||||
.join(User)
|
||||
.where(Winner.lottery_id == lottery_id)
|
||||
.order_by(Winner.place)
|
||||
)
|
||||
winners_data = []
|
||||
for winner, user in winners:
|
||||
winners_data.append({
|
||||
'place': winner.place,
|
||||
'telegram_id': user.telegram_id,
|
||||
'username': user.username,
|
||||
'first_name': user.first_name,
|
||||
'prize': winner.prize,
|
||||
'is_manual': winner.is_manual,
|
||||
'won_at': winner.created_at.isoformat()
|
||||
})
|
||||
|
||||
return {
|
||||
'lottery': {
|
||||
'id': lottery.id,
|
||||
'title': lottery.title,
|
||||
'description': lottery.description,
|
||||
'created_at': lottery.created_at.isoformat(),
|
||||
'is_completed': lottery.is_completed,
|
||||
'prizes': lottery.prizes,
|
||||
'manual_winners': lottery.manual_winners
|
||||
},
|
||||
'participants': participants_data,
|
||||
'winners': winners_data,
|
||||
'export_date': datetime.now().isoformat()
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
async def bulk_add_participants(
|
||||
session: AsyncSession,
|
||||
lottery_id: int,
|
||||
telegram_ids: List[int]
|
||||
) -> Dict[str, int]:
|
||||
"""Массовое добавление участников"""
|
||||
added = 0
|
||||
skipped = 0
|
||||
errors = []
|
||||
|
||||
for telegram_id in telegram_ids:
|
||||
try:
|
||||
# Проверяем, есть ли пользователь
|
||||
user = await session.execute(
|
||||
select(User).where(User.telegram_id == telegram_id)
|
||||
)
|
||||
user = user.scalar_one_or_none()
|
||||
|
||||
if not user:
|
||||
errors.append(f"Пользователь {telegram_id} не найден")
|
||||
continue
|
||||
|
||||
# Проверяем, не участвует ли уже
|
||||
existing = await session.execute(
|
||||
select(Participation).where(
|
||||
Participation.lottery_id == lottery_id,
|
||||
Participation.user_id == user.id
|
||||
)
|
||||
)
|
||||
|
||||
if existing.scalar_one_or_none():
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
# Добавляем участника
|
||||
participation = Participation(
|
||||
lottery_id=lottery_id,
|
||||
user_id=user.id
|
||||
)
|
||||
session.add(participation)
|
||||
added += 1
|
||||
|
||||
except Exception as e:
|
||||
errors.append(f"Ошибка с {telegram_id}: {str(e)}")
|
||||
|
||||
if added > 0:
|
||||
await session.commit()
|
||||
|
||||
return {
|
||||
'added': added,
|
||||
'skipped': skipped,
|
||||
'errors': errors
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
async def remove_participant(
|
||||
session: AsyncSession,
|
||||
lottery_id: int,
|
||||
telegram_id: int
|
||||
) -> bool:
|
||||
"""Удалить участника из розыгрыша"""
|
||||
user = await session.execute(
|
||||
select(User).where(User.telegram_id == telegram_id)
|
||||
)
|
||||
user = user.scalar_one_or_none()
|
||||
|
||||
if not user:
|
||||
return False
|
||||
|
||||
result = await session.execute(
|
||||
delete(Participation).where(
|
||||
Participation.lottery_id == lottery_id,
|
||||
Participation.user_id == user.id
|
||||
)
|
||||
)
|
||||
|
||||
await session.commit()
|
||||
return result.rowcount > 0
|
||||
|
||||
@staticmethod
|
||||
async def update_lottery(
|
||||
session: AsyncSession,
|
||||
lottery_id: int,
|
||||
**updates
|
||||
) -> bool:
|
||||
"""Обновить данные розыгрыша"""
|
||||
try:
|
||||
await session.execute(
|
||||
update(Lottery)
|
||||
.where(Lottery.id == lottery_id)
|
||||
.values(**updates)
|
||||
)
|
||||
await session.commit()
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
async def delete_lottery(session: AsyncSession, lottery_id: int) -> bool:
|
||||
"""Удалить розыгрыш (со всеми связанными данными)"""
|
||||
try:
|
||||
# Удаляем победителей
|
||||
await session.execute(
|
||||
delete(Winner).where(Winner.lottery_id == lottery_id)
|
||||
)
|
||||
|
||||
# Удаляем участников
|
||||
await session.execute(
|
||||
delete(Participation).where(Participation.lottery_id == lottery_id)
|
||||
)
|
||||
|
||||
# Удаляем сам розыгрыш
|
||||
await session.execute(
|
||||
delete(Lottery).where(Lottery.id == lottery_id)
|
||||
)
|
||||
|
||||
await session.commit()
|
||||
return True
|
||||
except Exception:
|
||||
await session.rollback()
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
async def get_user_activity(
|
||||
session: AsyncSession,
|
||||
telegram_id: int
|
||||
) -> Dict:
|
||||
"""Получить активность пользователя"""
|
||||
user = await session.execute(
|
||||
select(User).where(User.telegram_id == telegram_id)
|
||||
)
|
||||
user = user.scalar_one_or_none()
|
||||
|
||||
if not user:
|
||||
return {}
|
||||
|
||||
# Участия
|
||||
participations = await session.execute(
|
||||
select(Participation, Lottery)
|
||||
.join(Lottery)
|
||||
.where(Participation.user_id == user.id)
|
||||
.order_by(Participation.created_at.desc())
|
||||
)
|
||||
|
||||
# Выигрыши
|
||||
wins = await session.execute(
|
||||
select(Winner, Lottery)
|
||||
.join(Lottery)
|
||||
.where(Winner.user_id == user.id)
|
||||
.order_by(Winner.created_at.desc())
|
||||
)
|
||||
|
||||
participations_data = []
|
||||
for participation, lottery in participations:
|
||||
participations_data.append({
|
||||
'lottery_title': lottery.title,
|
||||
'lottery_id': lottery.id,
|
||||
'joined_at': participation.created_at,
|
||||
'lottery_completed': lottery.is_completed
|
||||
})
|
||||
|
||||
wins_data = []
|
||||
for win, lottery in wins:
|
||||
wins_data.append({
|
||||
'lottery_title': lottery.title,
|
||||
'lottery_id': lottery.id,
|
||||
'place': win.place,
|
||||
'prize': win.prize,
|
||||
'is_manual': win.is_manual,
|
||||
'won_at': win.created_at
|
||||
})
|
||||
|
||||
return {
|
||||
'user': user,
|
||||
'total_participations': len(participations_data),
|
||||
'total_wins': len(wins_data),
|
||||
'participations': participations_data,
|
||||
'wins': wins_data
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
async def cleanup_old_data(session: AsyncSession, days: int = 30) -> Dict[str, int]:
|
||||
"""Очистка старых данных"""
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
cutoff_date = datetime.now() - timedelta(days=days)
|
||||
|
||||
# Удаляем старые завершенные розыгрыши
|
||||
old_lotteries = await session.execute(
|
||||
select(Lottery.id)
|
||||
.where(
|
||||
Lottery.is_completed == True,
|
||||
Lottery.created_at < cutoff_date
|
||||
)
|
||||
)
|
||||
lottery_ids = [row[0] for row in old_lotteries.fetchall()]
|
||||
|
||||
deleted_winners = 0
|
||||
deleted_participations = 0
|
||||
deleted_lotteries = 0
|
||||
|
||||
for lottery_id in lottery_ids:
|
||||
# Удаляем победителей
|
||||
result = await session.execute(
|
||||
delete(Winner).where(Winner.lottery_id == lottery_id)
|
||||
)
|
||||
deleted_winners += result.rowcount
|
||||
|
||||
# Удаляем участников
|
||||
result = await session.execute(
|
||||
delete(Participation).where(Participation.lottery_id == lottery_id)
|
||||
)
|
||||
deleted_participations += result.rowcount
|
||||
|
||||
# Удаляем розыгрыш
|
||||
result = await session.execute(
|
||||
delete(Lottery).where(Lottery.id == lottery_id)
|
||||
)
|
||||
deleted_lotteries += result.rowcount
|
||||
|
||||
await session.commit()
|
||||
|
||||
return {
|
||||
'deleted_lotteries': deleted_lotteries,
|
||||
'deleted_participations': deleted_participations,
|
||||
'deleted_winners': deleted_winners,
|
||||
'cutoff_date': cutoff_date.isoformat()
|
||||
}
|
||||
|
||||
|
||||
class ReportGenerator:
|
||||
"""Генератор отчетов"""
|
||||
|
||||
@staticmethod
|
||||
async def generate_summary_report(session: AsyncSession) -> str:
|
||||
"""Генерация сводного отчета"""
|
||||
# Общая статистика
|
||||
total_users = await session.scalar(select(func.count(User.id)))
|
||||
total_lotteries = await session.scalar(select(func.count(Lottery.id)))
|
||||
active_lotteries = await session.scalar(
|
||||
select(func.count(Lottery.id))
|
||||
.where(Lottery.is_active == True, Lottery.is_completed == False)
|
||||
)
|
||||
completed_lotteries = await session.scalar(
|
||||
select(func.count(Lottery.id)).where(Lottery.is_completed == True)
|
||||
)
|
||||
total_participations = await session.scalar(select(func.count(Participation.id)))
|
||||
total_winners = await session.scalar(select(func.count(Winner.id)))
|
||||
|
||||
# Топ розыгрыши по участникам
|
||||
top_lotteries = await session.execute(
|
||||
select(
|
||||
Lottery.title,
|
||||
Lottery.created_at,
|
||||
func.count(Participation.id).label('participants')
|
||||
)
|
||||
.join(Participation, isouter=True)
|
||||
.group_by(Lottery.id)
|
||||
.order_by(func.count(Participation.id).desc())
|
||||
.limit(5)
|
||||
)
|
||||
|
||||
# Топ активные пользователи
|
||||
top_users = await session.execute(
|
||||
select(
|
||||
User.first_name,
|
||||
User.username,
|
||||
func.count(Participation.id).label('participations'),
|
||||
func.count(Winner.id).label('wins')
|
||||
)
|
||||
.join(Participation, isouter=True)
|
||||
.join(Winner, isouter=True)
|
||||
.group_by(User.id)
|
||||
.order_by(func.count(Participation.id).desc())
|
||||
.limit(5)
|
||||
)
|
||||
|
||||
report = f"📊 СВОДНЫЙ ОТЧЕТ\n"
|
||||
report += f"Дата: {datetime.now().strftime('%d.%m.%Y %H:%M')}\n\n"
|
||||
|
||||
report += f"📈 ОБЩАЯ СТАТИСТИКА\n"
|
||||
report += f"👥 Пользователей: {total_users}\n"
|
||||
report += f"🎲 Всего розыгрышей: {total_lotteries}\n"
|
||||
report += f"🟢 Активных: {active_lotteries}\n"
|
||||
report += f"✅ Завершенных: {completed_lotteries}\n"
|
||||
report += f"🎫 Всего участий: {total_participations}\n"
|
||||
report += f"🏆 Всего победителей: {total_winners}\n\n"
|
||||
|
||||
if total_lotteries > 0:
|
||||
avg_participation = total_participations / total_lotteries
|
||||
report += f"📊 Среднее участие на розыгрыш: {avg_participation:.1f}\n\n"
|
||||
|
||||
report += f"🏆 ТОП РОЗЫГРЫШИ ПО УЧАСТНИКАМ\n"
|
||||
for i, (title, created_at, participants) in enumerate(top_lotteries.fetchall(), 1):
|
||||
report += f"{i}. {title}\n"
|
||||
report += f" Участников: {participants} | {created_at.strftime('%d.%m.%Y')}\n\n"
|
||||
|
||||
report += f"🔥 ТОП АКТИВНЫЕ ПОЛЬЗОВАТЕЛИ\n"
|
||||
for i, (first_name, username, participations, wins) in enumerate(top_users.fetchall(), 1):
|
||||
name = f"@{username}" if username else first_name
|
||||
report += f"{i}. {name}\n"
|
||||
report += f" Участий: {participations} | Побед: {wins}\n\n"
|
||||
|
||||
return report
|
||||
Reference in New Issue
Block a user