from django.db.models import Q from django.shortcuts import get_object_or_404 from django.utils import timezone from rest_framework import generics, status, permissions from rest_framework.response import Response from rest_framework.views import APIView from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter, OpenApiTypes from apps.events.models import Event, EventRegistration, RegistrationStatus from apps.events.serializers import ( EventListSerializer, EventDetailSerializer, EventCreateUpdateSerializer, EventRegistrationSerializer, EventRegisterInputSerializer, EventCalendarItemSerializer, ) from apps.events.permissions import IsOrganizerAdminOrReadOnly, IsOrganizerOrStaff def get_event_by_id_or_slug(id_or_slug): if str(id_or_slug).isdigit(): return get_object_or_404(Event, pk=int(id_or_slug)) return get_object_or_404(Event, slug=id_or_slug) @extend_schema_view( get=extend_schema( tags=['Events'], summary="List events and programs", description="Filter events by category, date range, institution organizer, location type, and search keywords.", parameters=[ OpenApiParameter('search', OpenApiTypes.STR, description="Search in title, description, or speaker"), OpenApiParameter('category', OpenApiTypes.STR, description="Event category filter"), OpenApiParameter('organizer', OpenApiTypes.INT, description="Organizer institution ID"), OpenApiParameter('location_type', OpenApiTypes.STR, description="in_person, online, or hybrid"), OpenApiParameter('language', OpenApiTypes.STR, description="Event language (fa, ar, en, ur, fr)"), OpenApiParameter('start_date', OpenApiTypes.DATE, description="Filter from date (YYYY-MM-DD)"), OpenApiParameter('end_date', OpenApiTypes.DATE, description="Filter to date (YYYY-MM-DD)"), OpenApiParameter('is_featured', OpenApiTypes.BOOL, description="Filter featured events"), ] ), post=extend_schema( tags=['Events'], summary="Create a new event", description="Create an event on behalf of an institution where the user is an admin/editor.", request=EventCreateUpdateSerializer, responses={201: EventDetailSerializer} ) ) class EventListCreateView(generics.ListCreateAPIView): permission_classes = [permissions.IsAuthenticatedOrReadOnly] def get_serializer_class(self): if self.request.method == 'POST': return EventCreateUpdateSerializer return EventListSerializer def get_queryset(self): qs = Event.objects.select_related('organizer').filter(is_active=True) params = self.request.query_params search = params.get('search') if search: qs = qs.filter( Q(title__icontains=search) | Q(description__icontains=search) | Q(speaker_name__icontains=search) | Q(venue_address__icontains=search) ) category = params.get('category') if category: qs = qs.filter(category=category) organizer = params.get('organizer') if organizer: if str(organizer).isdigit(): qs = qs.filter(organizer_id=int(organizer)) else: qs = qs.filter(organizer__slug=organizer) location_type = params.get('location_type') if location_type: qs = qs.filter(location_type=location_type) language = params.get('language') if language: qs = qs.filter(language=language) start_date = params.get('start_date') if start_date: qs = qs.filter(event_date__gte=start_date) end_date = params.get('end_date') if end_date: qs = qs.filter(event_date__lte=end_date) is_featured = params.get('is_featured') if is_featured is not None: qs = qs.filter(is_featured=is_featured.lower() in ['true', '1']) return qs def perform_create(self, serializer): event = serializer.save() return event def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) event = self.perform_create(serializer) out_serializer = EventDetailSerializer(event, context={'request': request}) return Response(out_serializer.data, status=status.status_code if hasattr(status, 'status_code') else status.HTTP_201_CREATED) @extend_schema_view( get=extend_schema( tags=['Events'], summary="Retrieve event details", description="Get full event details, speaker bio, registration status for current user, and venue/meeting info." ), patch=extend_schema( tags=['Events'], summary="Update event", description="Update event metadata (requires organizer admin/editor permission).", request=EventCreateUpdateSerializer, responses={200: EventDetailSerializer} ), delete=extend_schema( tags=['Events'], summary="Delete event", description="Delete or deactivate an event." ) ) class EventDetailView(generics.RetrieveUpdateDestroyAPIView): permission_classes = [IsOrganizerAdminOrReadOnly] lookup_field = 'id_or_slug' def get_object(self): id_or_slug = self.kwargs.get('id_or_slug') obj = get_event_by_id_or_slug(id_or_slug) self.check_object_permissions(self.request, obj) return obj def get_serializer_class(self): if self.request.method in ['PUT', 'PATCH']: return EventCreateUpdateSerializer return EventDetailSerializer def update(self, request, *args, **kwargs): partial = kwargs.pop('partial', True) instance = self.get_object() serializer = EventCreateUpdateSerializer(instance, data=request.data, partial=partial, context={'request': request}) serializer.is_valid(raise_exception=True) self.perform_update(serializer) out_serializer = EventDetailSerializer(instance, context={'request': request}) return Response(out_serializer.data) @extend_schema_view( post=extend_schema( tags=['Events'], summary="Register for an event", description="Register the current authenticated user for this event.", request=EventRegisterInputSerializer, responses={200: EventDetailSerializer} ) ) class EventRegisterView(APIView): permission_classes = [permissions.IsAuthenticated] def post(self, request, id_or_slug): event = get_event_by_id_or_slug(id_or_slug) if not event.is_active: return Response({"detail": "This event is not active."}, status=status.HTTP_400_BAD_REQUEST) serializer = EventRegisterInputSerializer(data=request.data) serializer.is_valid(raise_exception=True) notes = serializer.validated_data.get('notes', '') # Check existing registration reg, created = EventRegistration.objects.get_or_create( event=event, user=request.user, defaults={'status': RegistrationStatus.REGISTERED, 'notes': notes} ) if not created: if reg.status == RegistrationStatus.REGISTERED: return Response({"detail": "You are already registered for this event."}, status=status.HTTP_400_BAD_REQUEST) # Re-activate cancelled registration reg.status = RegistrationStatus.REGISTERED if notes: reg.notes = notes reg.save() # Check capacity event.update_registration_count() if event.registration_count > event.capacity: reg.status = RegistrationStatus.CANCELLED reg.save() event.update_registration_count() return Response({"detail": "Event capacity has been reached."}, status=status.HTTP_400_BAD_REQUEST) out_serializer = EventDetailSerializer(event, context={'request': request}) return Response(out_serializer.data, status=status.HTTP_200_OK) @extend_schema_view( post=extend_schema( tags=['Events'], summary="Cancel event registration", description="Cancel registration for the current authenticated user.", request=None, responses={200: EventDetailSerializer} ) ) class EventCancelRegistrationView(APIView): permission_classes = [permissions.IsAuthenticated] def post(self, request, id_or_slug): event = get_event_by_id_or_slug(id_or_slug) reg = EventRegistration.objects.filter(event=event, user=request.user).first() if not reg or reg.status == RegistrationStatus.CANCELLED: return Response({"detail": "You do not have an active registration for this event."}, status=status.HTTP_400_BAD_REQUEST) reg.status = RegistrationStatus.CANCELLED reg.save() event.update_registration_count() out_serializer = EventDetailSerializer(event, context={'request': request}) return Response(out_serializer.data, status=status.HTTP_200_OK) @extend_schema_view( get=extend_schema( tags=['Events'], summary="List event attendees", description="View registered participants for an event (organizer admin/editor or staff only).", responses={200: EventRegistrationSerializer(many=True)} ) ) class EventRegistrationsListView(generics.ListAPIView): permission_classes = [IsOrganizerOrStaff] serializer_class = EventRegistrationSerializer queryset = EventRegistration.objects.none() def get_queryset(self): if getattr(self, 'swagger_fake_view', False): return EventRegistration.objects.none() id_or_slug = self.kwargs.get('id_or_slug') event = get_event_by_id_or_slug(id_or_slug) self.check_object_permissions(self.request, event) return event.registrations.select_related('user').all() @extend_schema_view( get=extend_schema( tags=['Events'], summary="Calendar view feed", description="Get streamlined event entries for calendar components filtered by month/year.", parameters=[ OpenApiParameter('year', OpenApiTypes.INT, description="Calendar year (e.g. 2026)"), OpenApiParameter('month', OpenApiTypes.INT, description="Calendar month (1-12)"), OpenApiParameter('category', OpenApiTypes.STR, description="Category filter"), ], responses={200: EventCalendarItemSerializer(many=True)} ) ) class EventCalendarView(generics.ListAPIView): permission_classes = [permissions.AllowAny] serializer_class = EventCalendarItemSerializer def get_queryset(self): qs = Event.objects.select_related('organizer').filter(is_active=True) params = self.request.query_params year = params.get('year') month = params.get('month') if year and str(year).isdigit(): qs = qs.filter(event_date__year=int(year)) if month and str(month).isdigit(): qs = qs.filter(event_date__month=int(month)) category = params.get('category') if category: qs = qs.filter(category=category) return qs