oncall-engine/engine/apps/api/serializers/shift_swap.py
Yulya Artyukhina 64bf1e5096
Speed up internal api endpoints (#4830)
# What this PR does
Reduces number of calls to db for `/schedules`, `/alertgroups` and
`/users` endpoints.
Fixes the issue when there was an additional call to db to get
organization url to build user avatar full link.

## Which issue(s) this PR closes

Related to [issue link here]

<!--
*Note*: If you want the issue to be auto-closed once the PR is merged,
change "Related to" to "Closes" in the line above.
If you have more than one GitHub issue that this PR closes, be sure to
preface
each issue link with a [closing
keyword](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/using-keywords-in-issues-and-pull-requests#linking-a-pull-request-to-an-issue).
This ensures that the issue(s) are auto-closed once the PR has been
merged.
-->

## Checklist

- [x] Unit, integration, and e2e (if applicable) tests updated
- [x] Documentation added (or `pr:no public docs` PR label added if not
required)
- [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.
2024-08-15 14:20:55 +00:00

128 lines
4.6 KiB
Python

import datetime
import typing
from django.utils import timezone
from rest_framework import serializers
from apps.schedules.models import OnCallSchedule, ShiftSwapRequest
from common.api_helpers.custom_fields import OrganizationFilteredPrimaryKeyRelatedField, TimeZoneAwareDatetimeField
from common.api_helpers.mixins import EagerLoadingMixin
if typing.TYPE_CHECKING:
from apps.user_management.models import User
class BaseShiftSwapRequestListSerializer(EagerLoadingMixin, serializers.ModelSerializer):
id = serializers.CharField(read_only=True, source="public_primary_key")
schedule = OrganizationFilteredPrimaryKeyRelatedField(queryset=OnCallSchedule.objects)
created_at = TimeZoneAwareDatetimeField(read_only=True)
updated_at = TimeZoneAwareDatetimeField(read_only=True)
swap_start = TimeZoneAwareDatetimeField()
swap_end = TimeZoneAwareDatetimeField()
beneficiary = serializers.CharField(read_only=True, source="beneficiary.public_primary_key")
benefactor = serializers.SerializerMethodField(read_only=True)
SELECT_RELATED = [
"schedule",
"beneficiary",
"benefactor",
]
class Meta:
model = ShiftSwapRequest
fields = [
"id",
"created_at",
"updated_at",
"status",
"schedule",
"swap_start",
"swap_end",
"description",
"beneficiary",
"benefactor",
]
read_only_fields = [
"status",
]
class ShiftSwapRequestListSerializer(BaseShiftSwapRequestListSerializer):
def get_benefactor(self, obj: ShiftSwapRequest) -> str | None:
return obj.benefactor.public_primary_key if obj.benefactor else None
class ShiftSwapRequestSerializer(ShiftSwapRequestListSerializer):
class Meta(ShiftSwapRequestListSerializer.Meta):
fields = ShiftSwapRequestListSerializer.Meta.fields + [
"shifts",
]
read_only_fields = ShiftSwapRequestListSerializer.Meta.read_only_fields + [
"shifts",
]
@staticmethod
def validate_start_and_end_times(swap_start: datetime.datetime, swap_end: datetime.datetime) -> None:
if timezone.now() > swap_start:
raise serializers.ValidationError("swap_start must be a datetime in the future")
if swap_start > swap_end:
raise serializers.ValidationError("swap_end must occur after swap_start")
def validate(self, data):
swap_start = data.get("swap_start", None)
swap_end = data.get("swap_end", None)
if self.partial: # self.partial is true when it's a "partial update" aka PATCH
# if any time related field is specified then we will enforce that they must all be specified
time_fields = [swap_start, swap_end]
any_time_fields_specified = any(time_fields)
all_time_fields_specified = all(time_fields)
if any_time_fields_specified and not all_time_fields_specified:
raise serializers.ValidationError(
"when doing a partial update on time related fields, both start and end times must be specified"
)
elif all_time_fields_specified:
self.validate_start_and_end_times(swap_start, swap_end)
else:
self.validate_start_and_end_times(swap_start, swap_end)
# TODO: we should validate that the beneficiary actually has shifts for the specified schedule
# between swap_start and swap_end
return data
class ShiftSwapRequestExpandedUsersListSerializer(BaseShiftSwapRequestListSerializer):
beneficiary = serializers.SerializerMethodField(read_only=True)
benefactor = serializers.SerializerMethodField(read_only=True)
def _serialize_user(self, user: "User") -> dict | None:
user_data = None
if user:
organization = (
self.context["request"].auth.organization if self.context.get("request") else user.organization
)
user_data = {
"display_name": user.username,
"email": user.email,
"pk": user.public_primary_key,
"avatar_full": user.avatar_full_url(organization),
}
return user_data
def get_benefactor(self, obj: ShiftSwapRequest) -> dict | None:
return self._serialize_user(obj.benefactor)
def get_beneficiary(self, obj: ShiftSwapRequest) -> dict | None:
return self._serialize_user(obj.beneficiary)
class ShiftSwapRequestExpandedUsersSerializer(
ShiftSwapRequestExpandedUsersListSerializer,
ShiftSwapRequestSerializer,
):
pass