oncall-engine/engine/apps/api/views/shift_swap.py
Joey Orlando 33364b63c6
Google Calendar Out of Office events - autogenerated shift swap requests (#4104)
# What this PR does

## Which issue(s) this PR closes

Closes https://github.com/grafana/oncall-private/issues/2590

## Checklist

- [x] Unit, integration, and e2e (if applicable) tests updated
- [x] Documentation added (or `pr:no public docs` PR label added if not
required) - will be done in
https://github.com/grafana/oncall-private/issues/2591
- [x] Added the relevant release notes label (see labels prefixed w/
`release:`). These labels dictate how your PR will
show up in the autogenerated release notes. - will be done in
https://github.com/grafana/oncall-private/issues/2591

---------

Co-authored-by: Dominik <dominik.broj@grafana.com>
Co-authored-by: Maxim Mordasov <maxim.mordasov@grafana.com>
2024-04-02 20:10:16 +00:00

125 lines
5.1 KiB
Python

import logging
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.serializers import BaseSerializer
from rest_framework.viewsets import ModelViewSet
from apps.api.permissions import AuthenticatedRequest, IsOwner, RBACPermission
from apps.api.serializers.shift_swap import (
ShiftSwapRequestExpandedUsersListSerializer,
ShiftSwapRequestExpandedUsersSerializer,
ShiftSwapRequestListSerializer,
ShiftSwapRequestSerializer,
)
from apps.auth_token.auth import PluginAuthentication
from apps.mobile_app.auth import MobileAppAuthTokenAuthentication
from apps.schedules import exceptions
from apps.schedules.models import ShiftSwapRequest
from apps.schedules.tasks.shift_swaps import update_shift_swap_request_message
from apps.user_management.models import User
from common.api_helpers.exceptions import BadRequest
from common.api_helpers.mixins import PublicPrimaryKeyMixin
from common.api_helpers.paginators import FiftyPageSizePaginator
from common.insight_log import EntityEvent, write_resource_insight_log
logger = logging.getLogger(__name__)
class BaseShiftSwapViewSet(ModelViewSet):
model = ShiftSwapRequest
serializer_class = ShiftSwapRequestSerializer
pagination_class = FiftyPageSizePaginator
def _do_take(self, benefactor: User) -> dict:
shift_swap = self.get_object()
prev_state = shift_swap.insight_logs_serialized
try:
shift_swap.take(benefactor)
except exceptions.ShiftSwapRequestNotOpenForTaking:
raise BadRequest(detail="The shift swap request is not in a state which allows it to be taken")
except exceptions.BeneficiaryCannotTakeOwnShiftSwapRequest:
raise BadRequest(detail="A shift swap request cannot be created and taken by the same user")
write_resource_insight_log(
instance=shift_swap,
author=self.request.user,
event=EntityEvent.UPDATED,
prev_state=prev_state,
new_state=shift_swap.insight_logs_serialized,
)
return ShiftSwapRequestSerializer(shift_swap).data
def get_serializer_class(self):
if self.request.query_params.get("expand_users", "false") == "true":
# return detailed benefactor/beneficiary user information
return (
ShiftSwapRequestExpandedUsersListSerializer
if self.action == "list"
else ShiftSwapRequestExpandedUsersSerializer
)
return ShiftSwapRequestListSerializer if self.action == "list" else super().get_serializer_class()
def get_queryset(self):
queryset = ShiftSwapRequest.objects.filter(schedule__organization=self.request.auth.organization)
return self.serializer_class.setup_eager_loading(queryset)
def perform_destroy(self, instance: ShiftSwapRequest) -> None:
super().perform_destroy(instance)
write_resource_insight_log(instance=instance, author=self.request.user, event=EntityEvent.DELETED)
update_shift_swap_request_message.apply_async((instance.pk,))
def perform_create(self, serializer: BaseSerializer[ShiftSwapRequest]) -> None:
# default to create swap request with logged in user as beneficiary
serializer.save(beneficiary=self.request.user)
def perform_update(self, serializer: BaseSerializer[ShiftSwapRequest]) -> None:
prev_state = serializer.instance.insight_logs_serialized
serializer.save()
shift_swap_request = serializer.instance
write_resource_insight_log(
instance=shift_swap_request,
author=self.request.user,
event=EntityEvent.UPDATED,
prev_state=prev_state,
new_state=shift_swap_request.insight_logs_serialized,
)
update_shift_swap_request_message.apply_async((shift_swap_request.pk,))
class ShiftSwapViewSet(PublicPrimaryKeyMixin[ShiftSwapRequest], BaseShiftSwapViewSet):
authentication_classes = (MobileAppAuthTokenAuthentication, PluginAuthentication)
permission_classes = (IsAuthenticated, RBACPermission)
rbac_permissions = {
"metadata": [RBACPermission.Permissions.SCHEDULES_READ],
"list": [RBACPermission.Permissions.SCHEDULES_READ],
"retrieve": [RBACPermission.Permissions.SCHEDULES_READ],
"create": [RBACPermission.Permissions.SCHEDULES_WRITE],
"update": [RBACPermission.Permissions.SCHEDULES_WRITE],
"partial_update": [RBACPermission.Permissions.SCHEDULES_WRITE],
"destroy": [RBACPermission.Permissions.SCHEDULES_WRITE],
"take": [RBACPermission.Permissions.SCHEDULES_WRITE],
}
is_beneficiary = IsOwner(ownership_field="beneficiary")
rbac_object_permissions = {
is_beneficiary: [
"update",
"partial_update",
"destroy",
],
}
@action(methods=["post"], detail=True)
def take(self, request: AuthenticatedRequest, pk: str) -> Response:
serialized_shift_swap = self._do_take(benefactor=request.user)
return Response(serialized_shift_swap, status=status.HTTP_200_OK)