-
Notifications
You must be signed in to change notification settings - Fork 0
Friend requests backend functionality #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
bf0863d
bf4abc1
6432b61
eedaf69
b3ba417
48df740
45524a9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,169 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from django.db.models import Q | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from django.shortcuts import get_object_or_404 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from rest_framework import viewsets, status | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from rest_framework.response import Response | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from rest_framework.decorators import action | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from rest_framework.exceptions import ValidationError | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from django.db import IntegrityError | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from api.models import Friendship, StatusTypes | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from rest_framework import serializers, permissions | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| class FriendshipSerializer(serializers.ModelSerializer): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| class Meta: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| model = Friendship | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fields = ('id', 'requesting', 'requested', 'created_at', 'updated_at', 'status') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| read_only_fields = ('requesting', 'created_at', 'updated_at', 'status') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def to_representation(self, instance): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| representation = super().to_representation(instance) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Add additional user details | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| representation['requesting'] = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'id': instance.requesting.id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'username': instance.requesting.username, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| representation['requested'] = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'id': instance.requested.id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'username': instance.requested.username, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return representation | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| class Friendships(viewsets.ModelViewSet): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| permission_classes = [permissions.IsAuthenticated] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| queryset = Friendship.objects.all() # pylint: disable=no-member | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| serializer_class = FriendshipSerializer | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def get_queryset(self): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Filter friendships based on the authenticated user | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user = self.request.user | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Filter by status if provided in query params | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status_filter = self.request.query_params.get('status', None) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| queryset = Friendship.objects.filter( # pylint: disable=no-member | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Q(requesting=user) | Q(requested=user) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if status_filter: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| queryset = queryset.filter(status__status=status_filter.upper()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return queryset | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def create(self, request): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Get pending status | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pending_status = StatusTypes.objects.get(status='PENDING') # pylint: disable=no-member | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Validate the input data | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| serializer = self.get_serializer(data=request.data) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| serializer.is_valid(raise_exception=True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Save with the requesting user and status | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| friendship = serializer.save( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| requesting=request.user, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status=pending_status | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Response( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.get_serializer(friendship).data, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status=status.HTTP_201_CREATED | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except IntegrityError: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Response( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {"detail": "Friendship request already exists"}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status=status.HTTP_400_BAD_REQUEST | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except ValidationError as e: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Response( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {"detail": str(e)}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status=status.HTTP_400_BAD_REQUEST | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except StatusTypes.DoesNotExist: # pylint: disable=no-member | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Response( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {"detail": "Pending status type not found"}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status=status.HTTP_500_INTERNAL_SERVER_ERROR | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Response( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {"detail": f"Failed to create friendship request: {str(e)}"}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Check warningCode scanning / CodeQL Information exposure through an exception Medium Stack trace information Error loading related location Loading
Copilot AutofixAI 12 months ago To fix the problem, we need to ensure that detailed exception messages are not exposed to the end user. Instead, we should log the detailed error message on the server and return a generic error message to the user. This can be achieved by modifying the exception handling block to log the exception and return a generic error message.
Suggested changeset
1
api/views/friendship.py
Copilot is powered by AI and may make mistakes. Always verify output.
Refresh and try again.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status=status.HTTP_500_INTERNAL_SERVER_ERROR | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def update(self, request, pk=None): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| friendship = self.get_object() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Only the requested user can update the friendship status | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if request.user != friendship.requested: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Response( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {"detail": "Only the requested user can update friendship status"}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status=status.HTTP_403_FORBIDDEN | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| new_status = StatusTypes.objects.get(status=request.data.get('status', '').upper()) # pylint: disable=no-member | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| friendship.status = new_status | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| friendship.save() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| serializer = self.get_serializer(friendship) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Response(serializer.data) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except StatusTypes.DoesNotExist: # pylint: disable=no-member | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Response( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {"detail": "Invalid status"}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status=status.HTTP_400_BAD_REQUEST | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @action(detail=True, methods=['post']) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def accept(self, request, pk=None): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Accept a friend request""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| friendship = self.get_object() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if request.user != friendship.requested: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Response({"detail": "Not authorized"}, status=status.HTTP_403_FORBIDDEN) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| accepted_status = StatusTypes.objects.get(status='ACCEPTED') # pylint: disable=no-member | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| friendship.status = accepted_status | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| friendship.save() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| serializer = self.get_serializer(friendship) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Response(serializer.data) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Check warningCode scanning / CodeQL Information exposure through an exception Medium Stack trace information Error loading related location Loading
Copilot AutofixAI 12 months ago To fix the problem, we need to ensure that detailed exception messages are not exposed to the user. Instead, we should log the detailed error message on the server and return a generic error message to the user. This can be achieved by modifying the exception handling in the
Suggested changeset
1
api/views/friendship.py
Copilot is powered by AI and may make mistakes. Always verify output.
Refresh and try again.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @action(detail=True, methods=['post']) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def reject(self, request, pk=None): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Reject a friend request""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| friendship = self.get_object() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if request.user != friendship.requested: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Response({"detail": "Not authorized"}, status=status.HTTP_403_FORBIDDEN) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if friendship.status.status != "PENDING": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Response({"detail": "This request is not pending."}, status=status.HTTP_400_BAD_REQUEST) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rejected_status = StatusTypes.objects.get(status='REJECTED') # pylint: disable=no-member | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| friendship.status = rejected_status | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| friendship.save() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| serializer = self.get_serializer(friendship) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Response(serializer.data) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Check warningCode scanning / CodeQL Information exposure through an exception Medium Stack trace information Error loading related location Loading
Copilot AutofixAI 12 months ago To fix the problem, we need to ensure that detailed exception messages are not exposed to the user. Instead, we should log the detailed error message on the server and return a generic error message to the user. This can be achieved by modifying the exception handling blocks to log the exception and return a generic error message.
Suggested changeset
1
api/views/friendship.py
Copilot is powered by AI and may make mistakes. Always verify output.
Refresh and try again.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @action(detail=True, methods=['post']) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def terminate(self, request, pk=None): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Terminate an existing friendship""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| friendship = self.get_object() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if request.user not in [friendship.requesting, friendship.requested]: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Response({"detail": "Not authorized"}, status=status.HTTP_403_FORBIDDEN) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if friendship.status.status != "ACCEPTED": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Response({"detail": "Only accepted friendships can be terminated."}, status=status.HTTP_400_BAD_REQUEST) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| terminated_status = StatusTypes.objects.get(status='TERMINATED') # pylint: disable=no-member | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| friendship.status = terminated_status | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| friendship.save() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| serializer = self.get_serializer(friendship) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Response(serializer.data) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Check warningCode scanning / CodeQL Information exposure through an exception Medium Stack trace information Error loading related location Loading
Copilot AutofixAI 12 months ago To fix the problem, we need to ensure that detailed exception messages are not exposed to the end user. Instead, we should log the detailed error message on the server and return a generic error message to the user. This can be achieved by modifying the exception handling code to log the exception and return a generic error message.
Suggested changeset
1
api/views/friendship.py
Copilot is powered by AI and may make mistakes. Always verify output.
Refresh and try again.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Check warning
Code scanning / CodeQL
Information exposure through an exception Medium
Copilot Autofix
AI 12 months ago
To fix the problem, we need to ensure that detailed exception messages are not exposed to the end user. Instead, we should log the detailed error message on the server and return a generic error message to the user. This can be achieved by modifying the exception handling blocks to log the exception details and return a generic error message.
Specifically, we will:
loggingmodule to log the exception details.