from django.contrib.auth import get_user_model from django.db.models import Q from django.shortcuts import get_object_or_404 from django.utils import timezone from rest_framework import status, viewsets from rest_framework.decorators import action, api_view, permission_classes from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from .models import BlogPost, Bug, BugStatus, Friendship, FriendshipStatus, Message from .serializers import ( BlogPostSerializer, BugSerializer, FriendRequestSerializer, FriendshipSerializer, MessageCreateSerializer, MessageDetailSerializer, MessageListSerializer, ) User = get_user_model() # ── Member search (for adding friends) ─────────────────────────────────────── @api_view(['GET']) @permission_classes([IsAuthenticated]) def member_search(request): q = request.query_params.get('q', '').strip() if len(q) < 2: return Response([]) users = ( User.objects .exclude(pk=request.user.pk) .filter( Q(username__icontains=q) | Q(first_name__icontains=q) | Q(last_name__icontains=q) )[:20] ) data = [ { 'id': u.id, 'username': u.username, 'display_name': (f'{u.first_name} {u.last_name}'.strip()) or u.username, } for u in users ] return Response(data) # ── Messages ────────────────────────────────────────────────────────────────── class MessageViewSet(viewsets.GenericViewSet): permission_classes = [IsAuthenticated] def list(self, request): """Inbox: messages received by the current user.""" qs = ( Message.objects .filter(recipient=request.user, deleted_by_recipient=False) .select_related('sender', 'recipient') .order_by('-sent_at') ) return Response(MessageListSerializer(qs, many=True).data) def create(self, request): ser = MessageCreateSerializer(data=request.data, context={'request': request}) ser.is_valid(raise_exception=True) msg = ser.save(sender=request.user) return Response(MessageDetailSerializer(msg).data, status=status.HTTP_201_CREATED) def retrieve(self, request, pk=None): user = request.user msg = get_object_or_404( Message, Q(sender=user, deleted_by_sender=False) | Q(recipient=user, deleted_by_recipient=False), pk=pk, ) if msg.recipient == user and msg.read_at is None: msg.read_at = timezone.now() msg.save(update_fields=['read_at']) return Response(MessageDetailSerializer(msg).data) def destroy(self, request, pk=None): user = request.user msg = get_object_or_404( Message, Q(sender=user) | Q(recipient=user), pk=pk, ) if msg.sender == user: msg.deleted_by_sender = True if msg.recipient == user: msg.deleted_by_recipient = True msg.save(update_fields=['deleted_by_sender', 'deleted_by_recipient']) return Response(status=status.HTTP_204_NO_CONTENT) @action(detail=False, methods=['get'], url_path='sent') def sent(self, request): qs = ( Message.objects .filter(sender=request.user, deleted_by_sender=False) .select_related('sender', 'recipient') .order_by('-sent_at') ) return Response(MessageListSerializer(qs, many=True).data) @action(detail=False, methods=['get'], url_path='unread-count') def unread_count(self, request): count = Message.objects.filter( recipient=request.user, read_at__isnull=True, deleted_by_recipient=False ).count() return Response({'unread': count}) # ── BlogPost ────────────────────────────────────────────────────────────────── class BlogPostViewSet(viewsets.ModelViewSet): permission_classes = [IsAuthenticated] serializer_class = BlogPostSerializer search_fields = ['title', 'body'] ordering_fields = ['created_at'] def get_queryset(self): user = self.request.user return ( BlogPost.objects .filter(Q(author=user) | Q(is_public=True)) .select_related('author') ) def perform_create(self, serializer): serializer.save(author=self.request.user) def check_write_permission(self, instance): from rest_framework.exceptions import PermissionDenied if instance.author != self.request.user and not self.request.user.is_staff: raise PermissionDenied def perform_update(self, serializer): self.check_write_permission(self.get_object()) serializer.save() def perform_destroy(self, instance): self.check_write_permission(instance) instance.delete() # ── Bug ─────────────────────────────────────────────────────────────────────── class BugViewSet(viewsets.ModelViewSet): permission_classes = [IsAuthenticated] serializer_class = BugSerializer filterset_fields = ['severity', 'status'] search_fields = ['title', 'description'] ordering_fields = ['created_at', 'severity'] def get_queryset(self): user = self.request.user if user.is_staff: return Bug.objects.select_related('reporter').all() return Bug.objects.filter(reporter=user).select_related('reporter') def perform_create(self, serializer): serializer.save(reporter=self.request.user) @action(detail=True, methods=['post'], url_path='resolve') def resolve(self, request, pk=None): from rest_framework.exceptions import PermissionDenied if not request.user.is_staff: raise PermissionDenied bug = self.get_object() bug.status = BugStatus.RESOLVED bug.resolved_at = timezone.now() bug.save(update_fields=['status', 'resolved_at', 'updated_at']) return Response(BugSerializer(bug).data) # ── Friendship ──────────────────────────────────────────────────────────────── class FriendshipViewSet(viewsets.GenericViewSet): permission_classes = [IsAuthenticated] serializer_class = FriendshipSerializer def list(self, request): """Accepted friends.""" user = request.user qs = ( Friendship.objects .filter(Q(from_user=user) | Q(to_user=user), status=FriendshipStatus.ACCEPTED) .select_related('from_user', 'to_user') ) return Response(FriendshipSerializer(qs, many=True).data) def create(self, request): ser = FriendRequestSerializer(data=request.data, context={'request': request}) ser.is_valid(raise_exception=True) to_user = ser.validated_data['to_user'] user = request.user existing = Friendship.objects.filter( Q(from_user=user, to_user=to_user) | Q(from_user=to_user, to_user=user) ).first() if existing: return Response({'detail': 'A friendship or request already exists.'}, status=status.HTTP_409_CONFLICT) friendship = Friendship.objects.create(from_user=user, to_user=to_user) return Response(FriendshipSerializer(friendship).data, status=status.HTTP_201_CREATED) def destroy(self, request, pk=None): user = request.user friendship = get_object_or_404( Friendship, Q(from_user=user) | Q(to_user=user), pk=pk, ) friendship.delete() return Response(status=status.HTTP_204_NO_CONTENT) @action(detail=False, methods=['get'], url_path='requests') def requests(self, request): """Incoming pending requests.""" qs = ( Friendship.objects .filter(to_user=request.user, status=FriendshipStatus.PENDING) .select_related('from_user', 'to_user') ) return Response(FriendshipSerializer(qs, many=True).data) @action(detail=False, methods=['get'], url_path='sent-requests') def sent_requests(self, request): """Outgoing pending requests.""" qs = ( Friendship.objects .filter(from_user=request.user, status=FriendshipStatus.PENDING) .select_related('from_user', 'to_user') ) return Response(FriendshipSerializer(qs, many=True).data) @action(detail=True, methods=['post'], url_path='accept') def accept(self, request, pk=None): friendship = get_object_or_404( Friendship, pk=pk, to_user=request.user, status=FriendshipStatus.PENDING ) friendship.status = FriendshipStatus.ACCEPTED friendship.save(update_fields=['status', 'updated_at']) return Response(FriendshipSerializer(friendship).data) @action(detail=True, methods=['post'], url_path='decline') def decline(self, request, pk=None): """Decline an incoming request or cancel an outgoing one.""" user = request.user friendship = get_object_or_404( Friendship, Q(from_user=user) | Q(to_user=user), pk=pk, status=FriendshipStatus.PENDING, ) friendship.delete() return Response(status=status.HTTP_204_NO_CONTENT)