from rest_framework import viewsets, status from rest_framework.decorators import ( action, api_view, authentication_classes, permission_classes, ) from rest_framework.response import Response from rest_framework.permissions import AllowAny, IsAuthenticated, IsAdminUser from rest_framework.parsers import MultiPartParser, FormParser, JSONParser from rest_framework.authtoken.models import Token from django.shortcuts import get_object_or_404 from django.utils import timezone from django.utils.dateparse import parse_datetime from .models import ContactUs, Composition, Campaign, CompositionImage from .serializers import ( ContactUsSerializer, CompositionSerializer, CampaignSerializer, AdminLoginSerializer, ) @api_view(['POST']) @authentication_classes([]) @permission_classes([AllowAny]) def admin_login(request): """ Admin login with username and password. Returns an auth token to use as: Authorization: Token """ serializer = AdminLoginSerializer(data=request.data) serializer.is_valid(raise_exception=True) user = serializer.validated_data['user'] token, _ = Token.objects.get_or_create(user=user) return Response({ 'token': token.key, 'user': { 'id': user.id, 'username': user.username, 'is_staff': user.is_staff, 'is_superuser': user.is_superuser, }, }) @api_view(['POST']) @permission_classes([IsAdminUser]) def admin_logout(request): """Delete the current admin auth token (logout).""" Token.objects.filter(user=request.user).delete() return Response({'detail': 'Logged out successfully.'}) @api_view(['GET']) @permission_classes([IsAdminUser]) def admin_me(request): """Return the currently authenticated admin user.""" user = request.user return Response({ 'id': user.id, 'username': user.username, 'is_staff': user.is_staff, 'is_superuser': user.is_superuser, 'is_active': user.is_active, }) class ContactUsViewSet(viewsets.ModelViewSet): """ Contact Us permissions: - Public: create a message, and view own messages via /mine/ - Admin: list all, retrieve, update (including admin_response), delete """ queryset = ContactUs.objects.all() serializer_class = ContactUsSerializer permission_classes = [IsAdminUser] def get_permissions(self): if self.action in ['create', 'mine']: return [AllowAny()] return [IsAdminUser()] def get_serializer(self, *args, **kwargs): serializer = super().get_serializer(*args, **kwargs) # Public create cannot set admin_response if self.action == 'create' and not ( self.request.user and self.request.user.is_staff ): serializer.fields['admin_response'].read_only = True return serializer @action(detail=False, methods=['get']) def mine(self, request): """ Public: list contacts for a given email_or_phone (own submissions), including admin_response. """ email_or_phone = (request.query_params.get('email_or_phone') or '').strip() if not email_or_phone: return Response( {'error': 'email_or_phone query parameter is required.'}, status=status.HTTP_400_BAD_REQUEST, ) contacts = self.queryset.filter(email_or_phone__iexact=email_or_phone) serializer = self.get_serializer(contacts, many=True) return Response(serializer.data) @action(detail=False, methods=['get']) def by_category(self, request): """Admin: get contacts filtered by category.""" category = request.query_params.get('category', None) if category: contacts = self.queryset.filter(category=category) serializer = self.get_serializer(contacts, many=True) return Response(serializer.data) return Response( {'error': 'Category parameter is required.'}, status=status.HTTP_400_BAD_REQUEST ) class CompositionViewSet(viewsets.ModelViewSet): """ Compositions (مقالات): - Public: list, retrieve, by-created-at - Admin: create, update, delete, manage images """ queryset = Composition.objects.prefetch_related('images').all() serializer_class = CompositionSerializer permission_classes = [IsAdminUser] parser_classes = [MultiPartParser, FormParser, JSONParser] def get_permissions(self): if self.action in ['list', 'retrieve', 'by_created_at']: return [AllowAny()] return [IsAdminUser()] def get_serializer_context(self): context = super().get_serializer_context() context['request'] = self.request return context @action(detail=False, methods=['get'], url_path='by-created-at') def by_created_at(self, request): """Public: compositions filtered by created_at date range.""" from_param = request.query_params.get('from') to_param = request.query_params.get('to') if not from_param and not to_param: return Response( {'error': 'At least one of "from" or "to" query parameters is required.'}, status=status.HTTP_400_BAD_REQUEST, ) compositions = self.queryset if from_param: from_dt = parse_datetime(from_param) if from_dt is None: return Response( {'error': 'Invalid "from" datetime. Use ISO 8601 format.'}, status=status.HTTP_400_BAD_REQUEST, ) if timezone.is_naive(from_dt): from_dt = timezone.make_aware(from_dt) compositions = compositions.filter(created_at__gte=from_dt) if to_param: to_dt = parse_datetime(to_param) if to_dt is None: return Response( {'error': 'Invalid "to" datetime. Use ISO 8601 format.'}, status=status.HTTP_400_BAD_REQUEST, ) if timezone.is_naive(to_dt): to_dt = timezone.make_aware(to_dt) compositions = compositions.filter(created_at__lte=to_dt) serializer = self.get_serializer(compositions, many=True) return Response(serializer.data) def _composition_response(self, composition): composition = Composition.objects.prefetch_related('images').get( pk=composition.pk ) return self.get_serializer(composition).data @action(detail=True, methods=['post'], url_path='add-images') def add_images(self, request, pk=None): """Admin: add one or more images to an existing composition.""" composition = self.get_object() serializer = self.get_serializer( composition, data=request.data, partial=True ) serializer.is_valid(raise_exception=True) composition = serializer.save() return Response(self._composition_response(composition)) @action(detail=True, methods=['post'], url_path='set-main-image') def set_main_image(self, request, pk=None): """Admin: flag one image as the main image.""" composition = self.get_object() image_id = request.data.get('image_id') if image_id is None: return Response( {'error': 'image_id is required.'}, status=status.HTTP_400_BAD_REQUEST ) image = get_object_or_404( CompositionImage, pk=image_id, composition=composition ) image.is_main = True image.save() return Response(self._composition_response(composition)) @action( detail=True, methods=['delete'], url_path='images/(?P[^/.]+)' ) def delete_image(self, request, pk=None, image_id=None): """Admin: delete a single image from the composition.""" composition = self.get_object() image = get_object_or_404( CompositionImage, pk=image_id, composition=composition ) image.delete() return Response(status=status.HTTP_204_NO_CONTENT) class CampaignViewSet(viewsets.ModelViewSet): """ Campaigns: - Public: list, retrieve, active, upcoming, ended - Admin: create, update, delete """ queryset = Campaign.objects.all() serializer_class = CampaignSerializer permission_classes = [IsAdminUser] def get_permissions(self): if self.action in ['list', 'retrieve', 'active', 'upcoming', 'ended']: return [AllowAny()] return [IsAdminUser()] def get_serializer_context(self): context = super().get_serializer_context() context['request'] = self.request return context @action(detail=False, methods=['get']) def active(self, request): now = timezone.now() active_campaigns = self.queryset.filter( start_time__lte=now, end_time__gte=now ) serializer = self.get_serializer(active_campaigns, many=True) return Response(serializer.data) @action(detail=False, methods=['get']) def upcoming(self, request): now = timezone.now() upcoming_campaigns = self.queryset.filter(start_time__gt=now) serializer = self.get_serializer(upcoming_campaigns, many=True) return Response(serializer.data) @action(detail=False, methods=['get']) def ended(self, request): now = timezone.now() ended_campaigns = self.queryset.filter(end_time__lt=now) serializer = self.get_serializer(ended_campaigns, many=True) return Response(serializer.data)