talentmap_api/bidding/views/bidlist.py
from dateutil.relativedelta import relativedelta from django.shortcuts import get_object_or_404from django.core.exceptions import PermissionDeniedfrom django.utils import timezone from rest_framework import mixinsfrom rest_framework.viewsets import GenericViewSetfrom rest_framework.permissions import IsAuthenticated from rest_framework.views import APIViewfrom rest_framework.response import Responsefrom rest_framework import status from talentmap_api.bidding.serializers.serializers import BidSerializerfrom talentmap_api.bidding.models import Bid, BidCyclefrom talentmap_api.bidding.filters import BidFilterfrom talentmap_api.user_profile.models import UserProfilefrom talentmap_api.messaging.models import Notification import logginglogger = logging.getLogger(__name__) class BidListView(mixins.ListModelMixin, GenericViewSet): ''' list: Lists all bids for the user's current bidlist ''' serializer_class = BidSerializer filter_class = BidFilter permission_classes = (IsAuthenticated,) Function `destroy` has a Cognitive Complexity of 8 (exceeds 7 allowed). Consider refactoring. def destroy(self, request, *args, **kwargs): ''' Closes or deletes specified bid This endpoint will delete bids which are still in a draft status, but close any other status if permitted. ''' user = UserProfile.objects.get(user=self.request.user) # Grab the bid in question bid = get_object_or_404(Bid, id=kwargs.get("pk")) # Are we the owning user? is_ours = bid.user.id is user.id # Are we the CDO? is_direct_report = bid.user.id in user.direct_reports.values_list("id", flat=True) if not is_ours and not is_direct_report: logger.warning(f"User {self.request.user.id}:{self.request.user} attempted to remove bid {bid} but does not own and is not CDO for owner") raise PermissionDenied # If we're the CDO, we can close it regardless of status/date restrictions if is_direct_report: bid.status = Bid.Status.closed bid.save() Notification.objects.create(owner=bid.user, tags=['bidding'], message=f"Bid {bid} has been closed by CDO {user}") return Response(status=status.HTTP_204_NO_CONTENT) elif timezone.now() < bid.bidcycle.cycle_deadline_date: if bid.status in [Bid.Status.draft, Bid.Status.submitted]: bid.status = Bid.Status.closed bid.save() logger.info(f"User {user.user.id}:{user.user} closed bid {bid}") return Response(status=status.HTTP_204_NO_CONTENT) else: # We are either outside of the permittable date range for self-closing, # or we are looking at a status that requires CDO action (i.e. handshake) logger.info(f"User {user.user.id}:{user.user} attempted to close bid {bid}, but is not CDO and is outside of permissable date range for closing bids without CDO approval") raise PermissionDenied else: # We're not CDO, and we're after the deadline logger.info(f"User {user.user.id}:{user.user} attempted to close bid {bid} but is outside of permissable date range") raise PermissionDenied def get_queryset(self): queryset = UserProfile.objects.get(user=self.request.user).bidlist.all() queryset = self.serializer_class.prefetch_model(Bid, queryset) return queryset class BidListPositionActionView(APIView): def get(self, request, pk, format=None): ''' Indicates if the position is in the user's bidlist Returns 204 if the position is in the list, otherwise, 404 ''' if UserProfile.objects.get(user=self.request.user).bidlist.filter(position__id=pk).count() > 0: return Response(status=status.HTTP_204_NO_CONTENT) else: return Response(status=status.HTTP_404_NOT_FOUND) def put(self, request, pk, format=None): ''' Adds a position to the user's bid list ''' bidcycle = BidCycle.objects.filter(active=True).latest("cycle_start_date") position = get_object_or_404(bidcycle.positions.all(), id=pk) # Position must not already have a handshake or greater bid status if not position.can_accept_new_bids(bidcycle): return Response("Cannot bid on a position that already has a qualifying bid", status=status.HTTP_400_BAD_REQUEST) # For now, we use whatever the latest active bidcycle is bid = Bid.objects.get_or_create(bidcycle=bidcycle, user=UserProfile.objects.get(user=self.request.user), position=position)[0] user = UserProfile.objects.get(user=self.request.user) # User cannot be retiring during the position's tour of duty current_assignment = bid.position.current_assignment if current_assignment and current_assignment.estimated_end_date + relativedelta(months=bid.position.post.tour_of_duty.months) > user.mandatory_retirement_date: return Response("Cannot bid on a position during which the user will retire", status=status.HTTP_400_BAD_REQUEST) logger.info(f"User {self.request.user.id}:{self.request.user} creating draft bid {bid}") user.bidlist.add(bid) return Response(status=status.HTTP_204_NO_CONTENT) def delete(self, request, pk, format=None): ''' Removes the position from the user's bid list, if that bid is still in draft status ''' bid = get_object_or_404(Bid, user=UserProfile.objects.get(user=self.request.user), position__id=pk, status=Bid.Status.draft) logger.info(f"User {self.request.user.id}:{self.request.user} deleting draft bid {bid}") bid.delete() return Response(status=status.HTTP_204_NO_CONTENT)