738 lines
29 KiB
Python
738 lines
29 KiB
Python
"""
|
||
API views для аналитики и отчетов.
|
||
"""
|
||
import logging
|
||
from rest_framework import viewsets, status
|
||
from rest_framework.decorators import action
|
||
from rest_framework.response import Response
|
||
from rest_framework.permissions import IsAuthenticated
|
||
from django.db import models
|
||
from django.db.models import Sum, Avg, Count, Q, F
|
||
from django.db.models.functions import TruncDate
|
||
from django.utils import timezone
|
||
from django.core.cache import cache
|
||
from datetime import timedelta, datetime
|
||
from django.http import HttpResponse
|
||
|
||
from apps.users.models import User, Client
|
||
from apps.schedule.models import Lesson
|
||
from apps.homework.models import Homework, HomeworkSubmission
|
||
from apps.materials.models import Material
|
||
from apps.users.utils import format_datetime_for_user
|
||
from .services import AnalyticsService
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class AnalyticsViewSet(viewsets.ViewSet):
|
||
"""
|
||
ViewSet для аналитики и отчетов ментора.
|
||
"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def _check_mentor(self, request):
|
||
"""Проверка что пользователь - ментор"""
|
||
if request.user.role != 'mentor':
|
||
return False
|
||
return True
|
||
|
||
def _get_period_dates(self, request):
|
||
"""Получить даты начала и конца периода"""
|
||
period = request.query_params.get('period', 'month')
|
||
start_date_str = request.query_params.get('start_date')
|
||
end_date_str = request.query_params.get('end_date')
|
||
|
||
now = timezone.now()
|
||
|
||
if period == 'day':
|
||
start_date = now.replace(hour=0, minute=0, second=0, microsecond=0)
|
||
end_date = now
|
||
elif period == 'week':
|
||
start_date = now - timedelta(days=now.weekday())
|
||
start_date = start_date.replace(hour=0, minute=0, second=0, microsecond=0)
|
||
end_date = now
|
||
elif period == 'month':
|
||
start_date = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
||
end_date = now
|
||
elif period == 'year':
|
||
start_date = now.replace(month=1, day=1, hour=0, minute=0, second=0, microsecond=0)
|
||
end_date = now
|
||
elif period == 'custom' and start_date_str and end_date_str:
|
||
start_date = timezone.make_aware(datetime.strptime(start_date_str, '%Y-%m-%d'))
|
||
end_date = timezone.make_aware(datetime.strptime(end_date_str, '%Y-%m-%d'))
|
||
end_date = end_date.replace(hour=23, minute=59, second=59)
|
||
else:
|
||
# По умолчанию - текущий месяц
|
||
start_date = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
||
end_date = now
|
||
|
||
return start_date, end_date
|
||
|
||
@action(detail=False, methods=['get'])
|
||
def overview(self, request):
|
||
"""
|
||
Общая статистика ментора.
|
||
|
||
GET /api/analytics/overview/?period=month&start_date=2024-01-01&end_date=2024-12-31
|
||
"""
|
||
if not self._check_mentor(request):
|
||
return Response(
|
||
{'error': 'Только для менторов'},
|
||
status=status.HTTP_403_FORBIDDEN
|
||
)
|
||
|
||
mentor = request.user
|
||
start_date, end_date = self._get_period_dates(request)
|
||
period = request.query_params.get('period', 'month')
|
||
|
||
# Кеширование: кеш на 2 минуты для каждого пользователя и периода
|
||
cache_key = f'analytics_overview_{mentor.id}_{period}'
|
||
cached_data = cache.get(cache_key)
|
||
|
||
if cached_data is not None:
|
||
return Response(cached_data)
|
||
|
||
# Занятия - оптимизация: используем only() для ограничения полей
|
||
lessons = Lesson.objects.filter(
|
||
mentor=mentor,
|
||
start_time__gte=start_date,
|
||
start_time__lte=end_date
|
||
).only('id', 'status', 'price', 'mentor_grade', 'start_time')
|
||
|
||
# Оптимизация: один запрос для всех статистик занятий
|
||
lessons_stats = lessons.aggregate(
|
||
total=Count('id'),
|
||
completed=Count('id', filter=Q(status='completed')),
|
||
cancelled=Count('id', filter=Q(status='cancelled')),
|
||
total_revenue=Sum('price', filter=Q(status='completed', price__isnull=False)),
|
||
avg_grade=Avg('mentor_grade', filter=Q(status='completed', mentor_grade__isnull=False))
|
||
)
|
||
total_lessons = lessons_stats['total']
|
||
completed_lessons = lessons_stats['completed']
|
||
cancelled_lessons = lessons_stats['cancelled']
|
||
total_revenue = lessons_stats['total_revenue'] or 0
|
||
average_grade = lessons_stats['avg_grade'] or 0
|
||
|
||
# Ученики - оптимизация: используем exists() вместо count() если нужно только проверить наличие
|
||
active_students = Client.objects.filter(
|
||
mentors=mentor
|
||
).select_related('user').count()
|
||
|
||
# Домашние задания - оптимизация: используем aggregate для подсчета
|
||
homeworks_stats = Homework.objects.filter(
|
||
mentor=mentor,
|
||
created_at__gte=start_date,
|
||
created_at__lte=end_date
|
||
).aggregate(total=Count('id'))
|
||
|
||
total_homeworks = homeworks_stats['total'] or 0
|
||
|
||
# Пending submissions - оптимизация: используем aggregate
|
||
pending_submissions = HomeworkSubmission.objects.filter(
|
||
homework__mentor=mentor,
|
||
status='pending'
|
||
).aggregate(count=Count('id'))['count'] or 0
|
||
|
||
|
||
response_data = {
|
||
'period': {
|
||
'start': format_datetime_for_user(start_date, request.user.timezone) if start_date else None,
|
||
'end': format_datetime_for_user(end_date, request.user.timezone) if end_date else None,
|
||
},
|
||
'lessons': {
|
||
'total': total_lessons,
|
||
'completed': completed_lessons,
|
||
'cancelled': cancelled_lessons,
|
||
},
|
||
'revenue': {
|
||
'total': float(total_revenue),
|
||
'average_per_lesson': float(total_revenue / completed_lessons) if completed_lessons > 0 else 0,
|
||
},
|
||
'students': {
|
||
'active': active_students,
|
||
},
|
||
'homeworks': {
|
||
'total': total_homeworks,
|
||
'pending': pending_submissions,
|
||
},
|
||
'grades': {
|
||
'average': round(average_grade, 1),
|
||
},
|
||
}
|
||
|
||
# Сохраняем в кеш на 5 минут (300 секунд) - аналитика не требует мгновенного обновления
|
||
cache.set(cache_key, response_data, 300)
|
||
|
||
return Response(response_data)
|
||
|
||
@action(detail=False, methods=['get'])
|
||
def students(self, request):
|
||
"""
|
||
Статистика по ученикам.
|
||
|
||
GET /api/analytics/students/
|
||
"""
|
||
if not self._check_mentor(request):
|
||
return Response(
|
||
{'error': 'Только для менторов'},
|
||
status=status.HTTP_403_FORBIDDEN
|
||
)
|
||
|
||
mentor = request.user
|
||
start_date, end_date = self._get_period_dates(request)
|
||
|
||
# Оптимизация: получаем только ID студентов одним запросом
|
||
student_ids = list(Client.objects.filter(mentors=mentor).values_list('id', flat=True))
|
||
|
||
if not student_ids:
|
||
return Response({
|
||
'students': [],
|
||
'total_count': 0,
|
||
})
|
||
|
||
# Оптимизация: batch-запрос для всех статистик студентов
|
||
students_lessons_stats = Lesson.objects.filter(
|
||
mentor=mentor,
|
||
client_id__in=student_ids,
|
||
start_time__gte=start_date,
|
||
start_time__lte=end_date
|
||
).values('client_id').annotate(
|
||
total=Count('id'),
|
||
completed=Count('id', filter=Q(status='completed')),
|
||
avg_grade=Avg('mentor_grade', filter=Q(status='completed', mentor_grade__isnull=False)),
|
||
revenue=Sum('price', filter=Q(status='completed', price__isnull=False))
|
||
)
|
||
lessons_by_student = {item['client_id']: item for item in students_lessons_stats}
|
||
|
||
# Получаем данные студентов только для тех, у кого есть статистика
|
||
students = Client.objects.filter(
|
||
id__in=student_ids
|
||
).select_related('user').only('id', 'user__first_name', 'user__last_name', 'user__email')
|
||
|
||
students_data = []
|
||
for student in students:
|
||
stats = lessons_by_student.get(student.id, {
|
||
'total': 0, 'completed': 0, 'avg_grade': 0, 'revenue': 0
|
||
})
|
||
|
||
total = stats['total']
|
||
completed = stats['completed']
|
||
avg_grade = stats['avg_grade'] or 0
|
||
revenue = stats['revenue'] or 0
|
||
|
||
students_data.append({
|
||
'id': student.id,
|
||
'name': student.user.get_full_name(),
|
||
'email': student.user.email,
|
||
'lessons_total': total,
|
||
'lessons_completed': completed,
|
||
'average_grade': round(avg_grade, 1),
|
||
'revenue': float(revenue),
|
||
})
|
||
|
||
# Сортируем по доходу
|
||
students_data.sort(key=lambda x: x['revenue'], reverse=True)
|
||
|
||
return Response({
|
||
'students': students_data,
|
||
'total_count': len(students_data),
|
||
})
|
||
|
||
@action(detail=False, methods=['get'])
|
||
def revenue(self, request):
|
||
"""
|
||
Финансовая аналитика.
|
||
|
||
GET /api/analytics/revenue/
|
||
"""
|
||
if not self._check_mentor(request):
|
||
return Response(
|
||
{'error': 'Только для менторов'},
|
||
status=status.HTTP_403_FORBIDDEN
|
||
)
|
||
|
||
mentor = request.user
|
||
start_date, end_date = self._get_period_dates(request)
|
||
period = request.query_params.get('period', 'month')
|
||
|
||
# Кеширование: кеш на 2 минуты для каждого пользователя и периода
|
||
cache_key = f'analytics_revenue_{mentor.id}_{period}'
|
||
cached_data = cache.get(cache_key)
|
||
|
||
if cached_data is not None:
|
||
return Response(cached_data)
|
||
|
||
# Доходы по дням
|
||
lessons = Lesson.objects.filter(
|
||
mentor=mentor,
|
||
start_time__gte=start_date,
|
||
start_time__lte=end_date,
|
||
status='completed',
|
||
price__isnull=False
|
||
).select_related('subject').only('start_time', 'price', 'subject__name')
|
||
|
||
# Группируем по дням
|
||
revenue_by_day = lessons.extra(
|
||
select={'day': "DATE(start_time AT TIME ZONE 'UTC')"}
|
||
).values('day').annotate(
|
||
revenue=Sum('price'),
|
||
lessons_count=Count('id')
|
||
).order_by('day')
|
||
|
||
# Доходы по предметам
|
||
revenue_by_subject = lessons.values('subject__name').annotate(
|
||
revenue=Sum('price', filter=Q(price__isnull=False)),
|
||
lessons_count=Count('id')
|
||
).order_by('-revenue')[:5]
|
||
|
||
# Общий доход
|
||
total_revenue = lessons.aggregate(total=Sum('price'))['total'] or 0
|
||
|
||
response_data = {
|
||
'total_revenue': float(total_revenue),
|
||
'by_day': [
|
||
{
|
||
'date': item['day'],
|
||
'revenue': float(item['revenue']),
|
||
'lessons_count': item['lessons_count'],
|
||
}
|
||
for item in revenue_by_day
|
||
],
|
||
'by_subject': [
|
||
{
|
||
'subject': item['subject__name'] or 'Без предмета',
|
||
'revenue': float(item['revenue']),
|
||
'lessons_count': item['lessons_count'],
|
||
}
|
||
for item in revenue_by_subject
|
||
],
|
||
}
|
||
|
||
# Сохраняем в кеш на 5 минут (300 секунд) - аналитика не требует мгновенного обновления
|
||
cache.set(cache_key, response_data, 300)
|
||
|
||
return Response(response_data)
|
||
|
||
@action(detail=False, methods=['get'])
|
||
def grades_by_day(self, request):
|
||
"""
|
||
Средняя оценка по дням за период (успех учеников / продуктивность репетитора).
|
||
|
||
GET /api/analytics/grades_by_day/?period=custom&start_date=2024-01-01&end_date=2024-01-31
|
||
|
||
Возвращает по каждому дню: дата, средняя оценка, количество занятий и оцененных занятий.
|
||
Репетитор видит прогресс и общую продуктивность по оценкам за выбранные дни.
|
||
"""
|
||
if not self._check_mentor(request):
|
||
return Response(
|
||
{'error': 'Только для менторов'},
|
||
status=status.HTTP_403_FORBIDDEN
|
||
)
|
||
|
||
mentor = request.user
|
||
start_date, end_date = self._get_period_dates(request)
|
||
period = request.query_params.get('period', 'month')
|
||
|
||
cache_key = f'analytics_grades_by_day_{mentor.id}_{period}_{start_date.date()}_{end_date.date()}'
|
||
cached_data = cache.get(cache_key)
|
||
if cached_data is not None:
|
||
return Response(cached_data)
|
||
|
||
lessons = Lesson.objects.filter(
|
||
mentor=mentor,
|
||
start_time__gte=start_date,
|
||
start_time__lte=end_date,
|
||
status='completed',
|
||
).only('id', 'start_time', 'mentor_grade')
|
||
|
||
by_day_qs = (
|
||
lessons.annotate(day=TruncDate('start_time'))
|
||
.values('day')
|
||
.annotate(
|
||
average_grade=Avg('mentor_grade', filter=Q(mentor_grade__isnull=False)),
|
||
lessons_count=Count('id'),
|
||
graded_count=Count('id', filter=Q(mentor_grade__isnull=False)),
|
||
)
|
||
.order_by('day')
|
||
)
|
||
stats_by_date = {}
|
||
for item in by_day_qs:
|
||
day = item['day']
|
||
if day is not None:
|
||
stats_by_date[day.strftime('%Y-%m-%d')] = {
|
||
'average_grade': item['average_grade'],
|
||
'lessons_count': item['lessons_count'],
|
||
'graded_count': item['graded_count'],
|
||
}
|
||
|
||
# Одна запись на каждый календарный день в диапазоне — без «склейки» и пропусков
|
||
start_d = start_date.date()
|
||
end_d = end_date.date()
|
||
by_day = []
|
||
d = start_d
|
||
while d <= end_d:
|
||
date_str = d.strftime('%Y-%m-%d')
|
||
st = stats_by_date.get(date_str)
|
||
if st:
|
||
avg = st['average_grade']
|
||
by_day.append({
|
||
'date': date_str,
|
||
'average_grade': round(float(avg), 1) if avg is not None else None,
|
||
'lessons_count': st['lessons_count'],
|
||
'graded_count': st['graded_count'],
|
||
})
|
||
else:
|
||
by_day.append({
|
||
'date': date_str,
|
||
'average_grade': None,
|
||
'lessons_count': 0,
|
||
'graded_count': 0,
|
||
})
|
||
d += timedelta(days=1)
|
||
|
||
summary_agg = lessons.aggregate(
|
||
total_lessons=Count('id'),
|
||
graded_lessons=Count('id', filter=Q(mentor_grade__isnull=False)),
|
||
average_grade=Avg('mentor_grade', filter=Q(mentor_grade__isnull=False)),
|
||
)
|
||
summary = {
|
||
'total_lessons': summary_agg['total_lessons'] or 0,
|
||
'graded_lessons': summary_agg['graded_lessons'] or 0,
|
||
'average_grade': round(float(summary_agg['average_grade'] or 0), 1),
|
||
}
|
||
|
||
response_data = {
|
||
'period': {
|
||
'start': format_datetime_for_user(start_date, request.user.timezone) if start_date else None,
|
||
'end': format_datetime_for_user(end_date, request.user.timezone) if end_date else None,
|
||
},
|
||
'by_day': by_day,
|
||
'summary': summary,
|
||
}
|
||
cache.set(cache_key, response_data, 300)
|
||
return Response(response_data)
|
||
|
||
@action(detail=False, methods=['get'])
|
||
def lessons_stats(self, request):
|
||
"""
|
||
Статистика занятий.
|
||
|
||
GET /api/analytics/lessons_stats/
|
||
"""
|
||
if not self._check_mentor(request):
|
||
return Response(
|
||
{'error': 'Только для менторов'},
|
||
status=status.HTTP_403_FORBIDDEN
|
||
)
|
||
|
||
mentor = request.user
|
||
start_date, end_date = self._get_period_dates(request)
|
||
|
||
lessons = Lesson.objects.filter(
|
||
mentor=mentor,
|
||
start_time__gte=start_date,
|
||
start_time__lte=end_date
|
||
)
|
||
|
||
# По статусам
|
||
by_status = lessons.values('status').annotate(
|
||
count=Count('id')
|
||
)
|
||
|
||
# По предметам
|
||
by_subject = lessons.values('subject__name').annotate(
|
||
count=Count('id')
|
||
).order_by('-count')[:10]
|
||
|
||
# По дням недели
|
||
by_weekday = lessons.extra(
|
||
select={'weekday': "EXTRACT(DOW FROM start_time)"}
|
||
).values('weekday').annotate(
|
||
count=Count('id')
|
||
).order_by('weekday')
|
||
|
||
weekday_names = ['Вс', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб']
|
||
|
||
return Response({
|
||
'by_status': list(by_status),
|
||
'by_subject': [
|
||
{
|
||
'subject': item['subject__name'] or 'Без предмета',
|
||
'count': item['count'],
|
||
}
|
||
for item in by_subject
|
||
],
|
||
'by_weekday': [
|
||
{
|
||
'day': weekday_names[int(item['weekday'])],
|
||
'count': item['count'],
|
||
}
|
||
for item in by_weekday
|
||
],
|
||
})
|
||
|
||
@action(detail=False, methods=['get'])
|
||
def homework_stats(self, request):
|
||
"""
|
||
Статистика по домашним заданиям.
|
||
|
||
GET /api/analytics/homework_stats/
|
||
"""
|
||
if not self._check_mentor(request):
|
||
return Response(
|
||
{'error': 'Только для менторов'},
|
||
status=status.HTTP_403_FORBIDDEN
|
||
)
|
||
|
||
mentor = request.user
|
||
start_date, end_date = self._get_period_dates(request)
|
||
|
||
# Домашние задания
|
||
homeworks = Homework.objects.filter(
|
||
mentor=mentor,
|
||
created_at__gte=start_date,
|
||
created_at__lte=end_date
|
||
)
|
||
|
||
# Сдачи
|
||
submissions = HomeworkSubmission.objects.filter(
|
||
homework__mentor=mentor,
|
||
submitted_at__gte=start_date,
|
||
submitted_at__lte=end_date
|
||
)
|
||
|
||
# Статистика
|
||
total_homeworks = homeworks.count()
|
||
total_submissions = submissions.count()
|
||
graded_submissions = submissions.filter(status='graded').count()
|
||
average_score = submissions.filter(
|
||
status='graded'
|
||
).aggregate(avg=Avg('score'))['avg'] or 0
|
||
|
||
# По статусам
|
||
by_status = submissions.values('status').annotate(
|
||
count=Count('id')
|
||
)
|
||
|
||
return Response({
|
||
'total_homeworks': total_homeworks,
|
||
'total_submissions': total_submissions,
|
||
'graded': graded_submissions,
|
||
'average_score': round(average_score, 1),
|
||
'by_status': list(by_status),
|
||
})
|
||
|
||
@action(detail=False, methods=['get'])
|
||
def detailed_lessons(self, request):
|
||
"""
|
||
Детальная статистика по занятиям.
|
||
|
||
GET /api/analytics/detailed_lessons/?period=month
|
||
"""
|
||
if not self._check_mentor(request):
|
||
return Response(
|
||
{'error': 'Только для менторов'},
|
||
status=status.HTTP_403_FORBIDDEN
|
||
)
|
||
|
||
mentor = request.user
|
||
start_date, end_date = self._get_period_dates(request)
|
||
period = request.query_params.get('period', 'month')
|
||
|
||
# Кеширование: кеш на 2 минуты для каждого пользователя и периода
|
||
cache_key = f'analytics_detailed_lessons_{mentor.id}_{period}'
|
||
cached_data = cache.get(cache_key)
|
||
|
||
if cached_data is not None:
|
||
return Response(cached_data)
|
||
|
||
stats = AnalyticsService.get_detailed_lesson_stats(mentor, start_date, end_date)
|
||
|
||
# Сохраняем в кеш на 2 минуты (120 секунд)
|
||
cache.set(cache_key, stats, 120)
|
||
|
||
return Response(stats)
|
||
|
||
@action(detail=False, methods=['get'])
|
||
def time_series(self, request):
|
||
"""
|
||
Временные ряды для графиков.
|
||
|
||
GET /api/analytics/time_series/?period=month&group_by=day
|
||
"""
|
||
if not self._check_mentor(request):
|
||
return Response(
|
||
{'error': 'Только для менторов'},
|
||
status=status.HTTP_403_FORBIDDEN
|
||
)
|
||
|
||
mentor = request.user
|
||
start_date, end_date = self._get_period_dates(request)
|
||
period = request.query_params.get('period', 'month')
|
||
group_by = request.query_params.get('group_by', 'day')
|
||
|
||
if group_by not in ['day', 'week', 'month']:
|
||
group_by = 'day'
|
||
|
||
# Кеширование: кеш на 2 минуты для каждого пользователя, периода и группировки
|
||
cache_key = f'analytics_time_series_{mentor.id}_{period}_{group_by}'
|
||
cached_data = cache.get(cache_key)
|
||
|
||
if cached_data is not None:
|
||
return Response(cached_data)
|
||
|
||
data = AnalyticsService.get_time_series_data(mentor, start_date, end_date, group_by)
|
||
|
||
response_data = {
|
||
'group_by': group_by,
|
||
'data': data,
|
||
}
|
||
|
||
# Сохраняем в кеш на 5 минут (300 секунд) - аналитика не требует мгновенного обновления
|
||
cache.set(cache_key, response_data, 300)
|
||
|
||
return Response(response_data)
|
||
|
||
@action(detail=False, methods=['get'])
|
||
def comparison(self, request):
|
||
"""
|
||
Сравнение текущего периода с предыдущим.
|
||
|
||
GET /api/analytics/comparison/?period=month
|
||
"""
|
||
if not self._check_mentor(request):
|
||
return Response(
|
||
{'error': 'Только для менторов'},
|
||
status=status.HTTP_403_FORBIDDEN
|
||
)
|
||
|
||
mentor = request.user
|
||
current_start, current_end = self._get_period_dates(request)
|
||
period = request.query_params.get('period', 'month')
|
||
|
||
# Кеширование: кеш на 2 минуты для каждого пользователя и периода
|
||
cache_key = f'analytics_comparison_{mentor.id}_{period}'
|
||
cached_data = cache.get(cache_key)
|
||
|
||
if cached_data is not None:
|
||
return Response(cached_data)
|
||
|
||
# Вычисляем предыдущий период
|
||
now = timezone.now()
|
||
|
||
if period == 'day':
|
||
previous_start = current_start - timedelta(days=1)
|
||
previous_end = current_start
|
||
elif period == 'week':
|
||
previous_start = current_start - timedelta(weeks=1)
|
||
previous_end = current_start
|
||
elif period == 'month':
|
||
# Предыдущий месяц
|
||
if current_start.month == 1:
|
||
previous_start = current_start.replace(year=current_start.year - 1, month=12, day=1)
|
||
else:
|
||
previous_start = current_start.replace(month=current_start.month - 1, day=1)
|
||
previous_end = current_start
|
||
elif period == 'year':
|
||
previous_start = current_start.replace(year=current_start.year - 1, month=1, day=1)
|
||
previous_end = current_start
|
||
else:
|
||
# По умолчанию - предыдущий месяц
|
||
if current_start.month == 1:
|
||
previous_start = current_start.replace(year=current_start.year - 1, month=12, day=1)
|
||
else:
|
||
previous_start = current_start.replace(month=current_start.month - 1, day=1)
|
||
previous_end = current_start
|
||
|
||
comparison = AnalyticsService.get_comparison_data(
|
||
mentor,
|
||
current_start,
|
||
current_end,
|
||
previous_start,
|
||
previous_end
|
||
)
|
||
|
||
# Сохраняем в кеш на 2 минуты (120 секунд)
|
||
cache.set(cache_key, comparison, 120)
|
||
|
||
return Response(comparison)
|
||
|
||
@action(detail=False, methods=['get'])
|
||
def export_pdf(self, request):
|
||
"""
|
||
Экспорт отчета в PDF.
|
||
|
||
GET /api/analytics/export_pdf/?period=month&type=overview
|
||
"""
|
||
if not self._check_mentor(request):
|
||
return Response(
|
||
{'error': 'Только для менторов'},
|
||
status=status.HTTP_403_FORBIDDEN
|
||
)
|
||
|
||
from .exporters import PDFExporter
|
||
|
||
mentor = request.user
|
||
start_date, end_date = self._get_period_dates(request)
|
||
report_type = request.query_params.get('type', 'overview')
|
||
|
||
try:
|
||
pdf_buffer = PDFExporter.generate_report(
|
||
mentor=mentor,
|
||
start_date=start_date,
|
||
end_date=end_date,
|
||
report_type=report_type
|
||
)
|
||
|
||
response = HttpResponse(pdf_buffer.getvalue(), content_type='application/pdf')
|
||
filename = f"analytics_report_{start_date.strftime('%Y%m%d')}_{end_date.strftime('%Y%m%d')}.pdf"
|
||
response['Content-Disposition'] = f'attachment; filename="{filename}"'
|
||
return response
|
||
except Exception as e:
|
||
logger.error(f"Error generating PDF report: {e}", exc_info=True)
|
||
return Response(
|
||
{'error': f'Ошибка генерации PDF: {str(e)}'},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
@action(detail=False, methods=['get'])
|
||
def export_excel(self, request):
|
||
"""
|
||
Экспорт отчета в Excel.
|
||
|
||
GET /api/analytics/export_excel/?period=month&type=overview
|
||
"""
|
||
if not self._check_mentor(request):
|
||
return Response(
|
||
{'error': 'Только для менторов'},
|
||
status=status.HTTP_403_FORBIDDEN
|
||
)
|
||
|
||
from .exporters import ExcelExporter
|
||
|
||
mentor = request.user
|
||
start_date, end_date = self._get_period_dates(request)
|
||
report_type = request.query_params.get('type', 'overview')
|
||
|
||
try:
|
||
excel_buffer = ExcelExporter.generate_report(
|
||
mentor=mentor,
|
||
start_date=start_date,
|
||
end_date=end_date,
|
||
report_type=report_type
|
||
)
|
||
|
||
response = HttpResponse(
|
||
excel_buffer.getvalue(),
|
||
content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||
)
|
||
filename = f"analytics_report_{start_date.strftime('%Y%m%d')}_{end_date.strftime('%Y%m%d')}.xlsx"
|
||
response['Content-Disposition'] = f'attachment; filename="{filename}"'
|
||
return response
|
||
except Exception as e:
|
||
logger.error(f"Error generating Excel report: {e}", exc_info=True)
|
||
return Response(
|
||
{'error': f'Ошибка генерации Excel: {str(e)}'},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|