alternative-backend-service/meetings/views.py

394 lines
14 KiB
Python
Raw Normal View History

from rest_framework import generics, status
from rest_framework.decorators import api_view, permission_classes
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated, AllowAny, IsAdminUser
from django.utils import timezone
from datetime import datetime, timedelta
from .models import AdminWeeklyAvailability, AppointmentRequest, get_admin_availability, set_admin_availability, get_available_slots_for_week, check_date_availability
from .serializers import (
AdminWeeklyAvailabilitySerializer,
AdminWeeklyAvailabilityUpdateSerializer,
AppointmentRequestSerializer,
AppointmentRequestCreateSerializer,
AppointmentScheduleSerializer,
AppointmentRejectSerializer,
AvailabilityCheckSerializer,
AvailabilityResponseSerializer,
WeeklyAvailabilitySerializer,
AdminAvailabilityConfigSerializer
)
from .email_service import EmailService
from users.models import CustomUser
from django.db.models import Count, Q
import hashlib
class AdminAvailabilityView(generics.RetrieveUpdateAPIView):
permission_classes = [IsAuthenticated, IsAdminUser]
def get_serializer_class(self):
if self.request.method == 'GET':
return AdminWeeklyAvailabilitySerializer
return AdminWeeklyAvailabilityUpdateSerializer
def get_object(self):
return get_admin_availability()
def update(self, request, *args, **kwargs):
response = super().update(request, *args, **kwargs)
availability = self.get_object()
full_serializer = AdminWeeklyAvailabilitySerializer(availability)
return Response(full_serializer.data)
class AdminAvailabilityConfigView(generics.GenericAPIView):
permission_classes = [AllowAny]
def get(self, request):
"""Get availability configuration"""
config = AdminAvailabilityConfigSerializer.get_default_config()
return Response(config.data)
class AppointmentRequestListView(generics.ListAPIView):
serializer_class = AppointmentRequestSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
queryset = AppointmentRequest.objects.all()
if self.request.user.is_staff:
return queryset
return queryset.filter(email=self.request.user.email)
class AppointmentRequestCreateView(generics.CreateAPIView):
permission_classes = [IsAuthenticated]
queryset = AppointmentRequest.objects.all()
serializer_class = AppointmentRequestCreateSerializer
def perform_create(self, serializer):
appointment = serializer.save()
if appointment.are_preferences_available():
EmailService.send_admin_notification(appointment)
else:
EmailService.send_admin_notification(appointment, availability_mismatch=True)
class AppointmentRequestDetailView(generics.RetrieveAPIView):
permission_classes = [IsAuthenticated]
queryset = AppointmentRequest.objects.all()
serializer_class = AppointmentRequestSerializer
lookup_field = 'pk'
class ScheduleAppointmentView(generics.GenericAPIView):
permission_classes = [IsAuthenticated, IsAdminUser]
serializer_class = AppointmentScheduleSerializer
queryset = AppointmentRequest.objects.all()
lookup_field = 'pk'
def post(self, request, pk):
appointment = self.get_object()
if appointment.status != 'pending_review':
return Response(
{'error': 'Only pending review appointments can be scheduled.'},
status=status.HTTP_400_BAD_REQUEST
)
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
appointment.schedule_appointment(
datetime_obj=serializer.validated_data['scheduled_datetime'],
duration=serializer.validated_data['scheduled_duration']
)
EmailService.send_appointment_scheduled(appointment)
response_serializer = AppointmentRequestSerializer(appointment)
return Response({
**response_serializer.data,
'message': 'Appointment scheduled successfully. Jitsi meeting created.',
'jitsi_meeting_created': appointment.has_jitsi_meeting
})
class RejectAppointmentView(generics.GenericAPIView):
permission_classes = [IsAuthenticated]
serializer_class = AppointmentRejectSerializer
queryset = AppointmentRequest.objects.all()
lookup_field = 'pk'
def post(self, request, pk):
appointment = self.get_object()
if appointment.status != 'pending_review':
return Response(
{'error': 'Only pending appointments can be rejected'},
status=status.HTTP_400_BAD_REQUEST
)
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
appointment.reject_appointment(
serializer.validated_data.get('rejection_reason', '')
)
EmailService.send_appointment_rejected(appointment)
response_serializer = AppointmentRequestSerializer(appointment)
return Response(response_serializer.data)
class AvailableDatesView(generics.GenericAPIView):
permission_classes = [AllowAny]
def get(self, request):
availability = get_admin_availability()
if not availability or not availability.availability_schedule:
return Response([])
today = timezone.now().date()
available_dates = []
for i in range(1, 31):
date = today + timedelta(days=i)
day_of_week = date.weekday()
available_slots = availability.get_availability_for_day(day_of_week)
if available_slots:
available_dates.append({
'date': date.strftime('%Y-%m-%d'),
'day_name': date.strftime('%A'),
'available_slots': available_slots,
'available_slots_display': [
dict(AdminWeeklyAvailability.TIME_SLOT_CHOICES).get(slot, slot)
for slot in available_slots
]
})
return Response(available_dates)
class CheckDateAvailabilityView(generics.GenericAPIView):
permission_classes = [AllowAny]
serializer_class = AvailabilityCheckSerializer
def post(self, request):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
date_str = serializer.validated_data['date']
available_slots = check_date_availability(date_str)
try:
date_obj = datetime.strptime(date_str, '%Y-%m-%d').date()
day_name = date_obj.strftime('%A')
response_data = {
'date': date_str,
'day_name': day_name,
'available_slots': available_slots,
'available_slots_display': [
dict(AdminWeeklyAvailability.TIME_SLOT_CHOICES).get(slot, slot)
for slot in available_slots
],
'is_available': len(available_slots) > 0
}
return Response(response_data)
except ValueError:
return Response(
{'error': 'Invalid date format'},
status=status.HTTP_400_BAD_REQUEST
)
class WeeklyAvailabilityView(generics.GenericAPIView):
permission_classes = [AllowAny]
def get(self, request):
availability = get_admin_availability()
if not availability:
return Response([])
weekly_availability = []
for day_num, day_name in AdminWeeklyAvailability.DAYS_OF_WEEK:
available_slots = availability.get_availability_for_day(day_num)
weekly_availability.append({
'day_number': day_num,
'day_name': day_name,
'available_slots': available_slots,
'available_slots_display': [
dict(AdminWeeklyAvailability.TIME_SLOT_CHOICES).get(slot, slot)
for slot in available_slots
],
'is_available': len(available_slots) > 0
})
return Response(weekly_availability)
class UserAppointmentsView(generics.ListAPIView):
permission_classes = [IsAuthenticated]
serializer_class = AppointmentRequestSerializer
def get_queryset(self):
user_email = self.request.user.email.lower()
all_appointments = list(AppointmentRequest.objects.all())
matching_appointments = [
apt for apt in all_appointments
if apt.email and apt.email.lower() == user_email
]
appointment_ids = [apt.id for apt in matching_appointments]
return AppointmentRequest.objects.filter(
id__in=appointment_ids
).order_by('-created_at')
class AppointmentStatsView(generics.GenericAPIView):
permission_classes = [IsAuthenticated, IsAdminUser]
def get(self, request):
total = AppointmentRequest.objects.count()
users = CustomUser.objects.filter(is_staff=False).count()
pending = AppointmentRequest.objects.filter(status='pending_review').count()
scheduled = AppointmentRequest.objects.filter(status='scheduled').count()
rejected = AppointmentRequest.objects.filter(status='rejected').count()
completed = AppointmentRequest.objects.filter(status='completed').count()
availability = get_admin_availability()
availability_coverage = 0
if availability and availability.availability_schedule:
days_with_availability = len(availability.availability_schedule)
availability_coverage = round((days_with_availability / 7) * 100, 2)
return Response({
'total_requests': total,
'pending_review': pending,
'scheduled': scheduled,
'rejected': rejected,
'completed': completed,
'users': users,
'completion_rate': round((scheduled / total * 100), 2) if total > 0 else 0,
'availability_coverage': availability_coverage,
'available_days_count': days_with_availability if availability else 0
})
class UserAppointmentStatsView(generics.GenericAPIView):
permission_classes = [IsAuthenticated]
serializer_class = AppointmentRequestSerializer
def get_queryset(self):
user_email = self.request.user.email.lower()
all_appointments = list(AppointmentRequest.objects.all())
matching_appointments = [
apt for apt in all_appointments
if apt.email and apt.email.lower() == user_email
]
appointment_ids = [apt.id for apt in matching_appointments]
return AppointmentRequest.objects.filter(
id__in=appointment_ids
)
def get(self, request, *args, **kwargs):
queryset = self.get_queryset()
stats = {
'total': queryset.count(),
'pending': queryset.filter(status='pending_review').count(),
'scheduled': queryset.filter(status='scheduled').count(),
'rejected': queryset.filter(status='rejected').count(),
'completed': queryset.filter(status='completed').count(),
}
total = stats['total']
scheduled = stats['scheduled']
completion_rate = round((scheduled / total * 100), 2) if total > 0 else 0
return Response({
'total_requests': total,
'pending_review': stats['pending'],
'scheduled': scheduled,
'rejected': stats['rejected'],
'completed': stats['completed'],
'completion_rate': completion_rate,
'email': request.user.email
})
class MatchingAvailabilityView(generics.GenericAPIView):
permission_classes = [IsAuthenticated]
def get(self, request, pk):
try:
appointment = AppointmentRequest.objects.get(pk=pk)
if not request.user.is_staff and appointment.email != request.user.email:
return Response(
{'error': 'You can only view your own appointments'},
status=status.HTTP_403_FORBIDDEN
)
matching_slots = appointment.get_matching_availability()
return Response({
'appointment_id': str(appointment.id),
'preferences_match_availability': appointment.are_preferences_available(),
'matching_slots': matching_slots,
'total_matching_slots': len(matching_slots)
})
except AppointmentRequest.DoesNotExist:
return Response(
{'error': 'Appointment not found'},
status=status.HTTP_404_NOT_FOUND
)
@api_view(['GET'])
@permission_classes([AllowAny])
def availability_overview(request):
availability = get_admin_availability()
if not availability:
return Response({
'available': False,
'message': 'No availability set'
})
all_slots = availability.get_all_available_slots()
return Response({
'available': len(all_slots) > 0,
'total_available_slots': len(all_slots),
'available_days': list(set(slot['day_name'] for slot in all_slots)),
'next_available_dates': get_next_available_dates(7)
})
def get_next_available_dates(days_count=7):
availability = get_admin_availability()
if not availability:
return []
today = timezone.now().date()
next_dates = []
for i in range(1, days_count + 1):
date = today + timedelta(days=i)
day_of_week = date.weekday()
available_slots = availability.get_availability_for_day(day_of_week)
if available_slots:
next_dates.append({
'date': date.strftime('%Y-%m-%d'),
'day_name': date.strftime('%A'),
'available_slots': available_slots
})
return next_dates