import logging from decimal import Decimal from django.db.models import Q, F from django.shortcuts import get_object_or_404 from django.utils.translation import gettext_lazy as _ from rest_framework import status from rest_framework.views import APIView from rest_framework.generics import GenericAPIView from rest_framework.permissions import AllowAny, IsAuthenticated from rest_framework.response import Response from drf_spectacular.utils import extend_schema, OpenApiResponse, OpenApiParameter from apps.donations.models import DonationCampaign, DonationTransaction, PaymentStatus from apps.donations.serializers import ( DonationCampaignListSerializer, DonationCampaignDetailSerializer, DonationTransactionSerializer, DonationContributeRequestSerializer, ) logger = logging.getLogger(__name__) def get_campaign_by_pk_or_slug(pk_or_slug, queryset=None): if queryset is None: queryset = DonationCampaign.objects.all() if str(pk_or_slug).isdigit(): return get_object_or_404(queryset, pk=int(pk_or_slug)) return get_object_or_404(queryset, slug=pk_or_slug) class DonationCampaignListView(GenericAPIView): permission_classes = [AllowAny] serializer_class = DonationCampaignListSerializer queryset = DonationCampaign.objects.filter(is_active=True) @extend_schema( summary="List active donation appeals & campaigns", description="Retrieves charitable campaigns with target, raised amounts, progress percentages, and filters.", parameters=[ OpenApiParameter('category', str, description="Filter by campaign category"), OpenApiParameter('priority', str, description="Filter by priority (urgent, high, medium)"), OpenApiParameter('search', str, description="Search in campaign title or description"), OpenApiParameter('featured', bool, description="Filter featured campaigns"), ], responses={200: DonationCampaignListSerializer(many=True)}, tags=["Donations & Financial Campaigns"], ) def get(self, request, *args, **kwargs): qs = DonationCampaign.objects.all() if not (request.user and request.user.is_authenticated and request.user.is_staff): qs = qs.filter(is_active=True) category = request.query_params.get('category') if category: qs = qs.filter(category=category) priority = request.query_params.get('priority') if priority: qs = qs.filter(priority=priority) featured = request.query_params.get('featured') if featured is not None: qs = qs.filter(is_featured=featured.lower() in ('true', '1')) search = request.query_params.get('search') if search: qs = qs.filter(Q(title__icontains=search) | Q(description__icontains=search)) serializer = self.get_serializer(qs, many=True) return Response(serializer.data, status=status.HTTP_200_OK) class DonationCampaignDetailView(GenericAPIView): permission_classes = [AllowAny] serializer_class = DonationCampaignDetailSerializer queryset = DonationCampaign.objects.all() @extend_schema( summary="Retrieve campaign details & recent contributions", description="Returns detailed campaign goal, beneficiary center, progress rate, and recent donors.", responses={ 200: DonationCampaignDetailSerializer, 404: OpenApiResponse(description="Campaign not found"), }, tags=["Donations & Financial Campaigns"], ) def get(self, request, pk_or_slug, *args, **kwargs): campaign = get_campaign_by_pk_or_slug(pk_or_slug, self.get_queryset()) serializer = self.get_serializer(campaign) return Response(serializer.data, status=status.HTTP_200_OK) class DonationContributeView(GenericAPIView): permission_classes = [AllowAny] serializer_class = DonationContributeRequestSerializer queryset = DonationCampaign.objects.all() @extend_schema( summary="Make a charitable contribution to campaign", description="Processes a donation contribution, persists transaction, and increments campaign accumulated funds.", request=DonationContributeRequestSerializer, responses={ 201: DonationTransactionSerializer, 400: OpenApiResponse(description="Validation error"), 404: OpenApiResponse(description="Campaign not found"), }, tags=["Donations & Financial Campaigns"], ) def post(self, request, pk_or_slug, *args, **kwargs): campaign = get_campaign_by_pk_or_slug(pk_or_slug, self.get_queryset()) if not campaign.is_active: return Response({'error': _("This campaign is currently inactive or closed.")}, status=status.HTTP_400_BAD_REQUEST) serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) amount = serializer.validated_data['amount'] donor_name = serializer.validated_data.get('donor_name') or 'Anonymous Donor' donor_email = serializer.validated_data.get('donor_email') user = request.user if (request.user and request.user.is_authenticated) else None if user and not serializer.validated_data.get('donor_name'): donor_name = user.get_full_name() or user.email transaction = DonationTransaction.objects.create( campaign=campaign, donor_user=user, donor_name=donor_name, donor_email=donor_email or (user.email if user else None), amount=amount, currency=campaign.currency, payment_status=PaymentStatus.COMPLETED, ) # Increment campaign raised amount campaign.raised_amount = (campaign.raised_amount or Decimal('0.00')) + amount campaign.save() return Response(DonationTransactionSerializer(transaction).data, status=status.HTTP_201_CREATED)