# 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.
128 lines
4.6 KiB
Python
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
|