263 lines
9.8 KiB
Python
263 lines
9.8 KiB
Python
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)
|