Skip to content

Commit

Permalink
feat: make fetching and organized applicants by state to backend
Browse files Browse the repository at this point in the history
  • Loading branch information
magsyg committed Nov 5, 2024
1 parent 475739b commit 33430af
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 70 deletions.
2 changes: 2 additions & 0 deletions backend/root/utils/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,7 @@
samfundet__interview_list = 'samfundet:interview-list'
samfundet__interview_detail = 'samfundet:interview-detail'
samfundet__api_root = 'samfundet:api-root'
samfundet__api_root = 'samfundet:api-root'
samfundet__schema = 'samfundet:schema'
samfundet__swagger_ui = 'samfundet:swagger_ui'
samfundet__redoc = 'samfundet:redoc'
Expand Down Expand Up @@ -591,6 +592,7 @@
samfundet__recruitment_set_interview = 'samfundet:recruitment_set_interview'
samfundet__recruitment_application_states_choices = 'samfundet:recruitment_application_states_choices'
samfundet__recruitment_application_update_state_gang = 'samfundet:recruitment_application_update_state_gang'
samfundet__recruitment_position_organized_applications = 'samfundet:recruitment_position_organized_applications'
samfundet__recruitment_application_update_state_position = 'samfundet:recruitment_application_update_state_position'
samfundet__recruitment_applications_recruiter = 'samfundet:recruitment_applications_recruiter'
samfundet__recruitment_withdraw_application = 'samfundet:recruitment_withdraw_application'
Expand Down
31 changes: 31 additions & 0 deletions backend/samfundet/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1094,6 +1094,37 @@ def update(self, instance: RecruitmentApplication, validated_data: dict) -> Recr
def get_application_count(self, application: RecruitmentApplication) -> int:
return application.user.applications.filter(recruitment=application.recruitment).count()

class RecruitmentPositionOrganizedApplications(CustomBaseSerializer):
applicationSerializer = RecruitmentApplicationForGangSerializer
unprocessed = serializers.SerializerMethodField(method_name='get_unprocessed', read_only=True)
withdrawn = serializers.SerializerMethodField(method_name='get_withdrawn', read_only=True)
accepted = serializers.SerializerMethodField(method_name='get_accepted', read_only=True)
rejected = serializers.SerializerMethodField(method_name='get_rejected', read_only=True)
hardtoget = serializers.SerializerMethodField(method_name='get_hardtoget', read_only=True)

class Meta:
model = RecruitmentPosition
fields = ['unprocessed', 'withdrawn', 'accepted', 'rejected', 'hardtoget']

def get_unprocessed(self, instance: RecruitmentPosition):
unprocessed = instance.applications.filter(withdrawn=False, recruiter_status=RecruitmentStatusChoices.NOT_SET)
return self.applicationSerializer(unprocessed, many=True).data

def get_withdrawn(self, instance: RecruitmentPosition):
withdrawn = instance.applications.filter(withdrawn=True)
return self.applicationSerializer(withdrawn, many=True).data

def get_rejected(self, instance: RecruitmentPosition):
rejected = instance.applications.filter(withdrawn=False, recruiter_status__in=[RecruitmentStatusChoices.AUTOMATIC_REJECTION, RecruitmentStatusChoices.REJECTION])
return self.applicationSerializer(rejected, many=True).data

def get_accepted(self, instance: RecruitmentPosition):
accepted = instance.applications.filter(withdrawn=False, recruiter_status=RecruitmentStatusChoices.CALLED_AND_ACCEPTED)
return self.applicationSerializer(accepted, many=True).data

def get_hardtoget(self, instance: RecruitmentPosition):
hardtoget = instance.applications.filter(withdrawn=False, recruiter_status=RecruitmentStatusChoices.CALLED_AND_REJECTED)
return self.applicationSerializer(hardtoget, many=True).data

class RecruitmentApplicationUpdateForGangSerializer(serializers.Serializer):
recruiter_priority = serializers.ChoiceField(choices=RecruitmentPriorityChoices.choices, required=False)
Expand Down
5 changes: 5 additions & 0 deletions backend/samfundet/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@
views.RecruitmentApplicationForGangUpdateStateView.as_view(),
name='recruitment_application_update_state_gang',
),
path(
'recruitment-position-organized-applications/<slug:pk>/',
views.RecruitmentPositionOrganizedApplicationsView.as_view(),
name='recruitment_position_organized_applications',
),
path(
'recruitment-application-update-state-position/<slug:pk>/',
views.RecruitmentApplicationForPositionUpdateStateView.as_view(),
Expand Down
20 changes: 13 additions & 7 deletions backend/samfundet/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
from .homepage import homepage
from .models.role import Role
from .serializers import (
RecruitmentPositionOrganizedApplications,
TagSerializer,
GangSerializer,
MenuSerializer,
Expand Down Expand Up @@ -1043,13 +1044,21 @@ def put(self, request: Request, pk: int) -> Response:
return Response(update_serializer.errors, status=status.HTTP_400_BAD_REQUEST)


class RecruitmentPositionOrganizedApplicationsView(APIView):
permission_classes = [IsAuthenticated]
serializer_class = RecruitmentPositionOrganizedApplications

def get(self, request: Request, pk: int) -> Response:
position = get_object_or_404(RecruitmentPosition, pk=pk)
serializer = self.serializer_class(position)
return Response(serializer.data, status=status.HTTP_200_OK)

class RecruitmentApplicationForPositionUpdateStateView(APIView):
permission_classes = [IsAuthenticated]
serializer_class = RecruitmentApplicationUpdateForGangSerializer

def put(self, request: Request, pk: int) -> Response:
application = get_object_or_404(RecruitmentApplication, pk=pk)

# TODO add check if user has permission to update for GANG
update_serializer = self.serializer_class(data=request.data)
if update_serializer.is_valid():
Expand All @@ -1060,12 +1069,9 @@ def put(self, request: Request, pk: int) -> Response:
application.recruiter_status = update_serializer.data['recruiter_status']
application.save()
application.update_applicant_state()
applications = RecruitmentApplication.objects.filter(
recruitment_position=application.recruitment_position, # Only change from above
recruitment=application.recruitment,
)
serializer = RecruitmentApplicationForGangSerializer(applications, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
position = get_object_or_404(RecruitmentPosition, pk=application.recruitment_position.id)
organized_serializer = RecruitmentPositionOrganizedApplications(position)
return Response(organized_serializer.data, status=status.HTTP_200_OK)
return Response(update_serializer.errors, status=status.HTTP_400_BAD_REQUEST)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useNavigate, useParams } from 'react-router-dom';
import { toast } from 'react-toastify';
import { Button, RecruitmentApplicantsStatus } from '~/Components';
import { Text } from '~/Components/Text/Text';
import { getRecruitmentApplicationsForGang, updateRecruitmentApplicationStateForPosition } from '~/api';
import { getRecruitmentApplicationsForGang, getRecruitmentPositionOrganizedApplications, updateRecruitmentApplicationStateForPosition } from '~/api';
import type { RecruitmentApplicationDto, RecruitmentApplicationStateDto } from '~/dto';
import { useTitle } from '~/hooks';
import { STATUS } from '~/http_status_codes';
Expand Down Expand Up @@ -33,46 +33,22 @@ export function RecruitmentPositionOverviewPage() {
if (!recruitmentId || !gangId || !positionId) {
return;
}
getRecruitmentApplicationsForGang(gangId, recruitmentId)
.then((data) => {
getRecruitmentPositionOrganizedApplications(positionId)
.then((response) => {
setRecruitmentApplicants(
data.data.filter(
(recruitmentApplicant) =>
!recruitmentApplicant.withdrawn &&
recruitmentApplicant.recruiter_status === 0 &&
recruitmentApplicant.recruitment_position?.id === Number.parseInt(positionId),
),
response.data.unprocessed
);
setWithdrawnApplicants(
data.data.filter(
(recruitmentApplicant) =>
recruitmentApplicant.withdrawn &&
recruitmentApplicant.recruitment_position?.id === Number.parseInt(positionId),
),
response.data.withdrawn
);
setHardtogetApplicants(
data.data.filter(
(recruitmentApplicant) =>
!recruitmentApplicant.withdrawn &&
recruitmentApplicant.recruiter_status === 2 &&
recruitmentApplicant.recruitment_position?.id === Number.parseInt(positionId),
),
response.data.hardtoget
);
setRejectedApplicants(
data.data.filter(
(recruitmentApplicant) =>
!recruitmentApplicant.withdrawn &&
recruitmentApplicant.recruiter_status === 3 &&
recruitmentApplicant.recruitment_position?.id === Number.parseInt(positionId),
),
response.data.rejected
);
setAcceptedApplicants(
data.data.filter(
(recruitmentApplicant) =>
!recruitmentApplicant.withdrawn &&
recruitmentApplicant.recruiter_status === 1 &&
recruitmentApplicant.recruitment_position?.id === Number.parseInt(positionId),
),
response.data.accepted
);
setShowSpinner(false);
})
Expand All @@ -91,45 +67,21 @@ export function RecruitmentPositionOverviewPage() {
const updateApplicationState = (id: string, data: RecruitmentApplicationStateDto) => {
positionId &&
updateRecruitmentApplicationStateForPosition(id, data)
.then((data) => {
.then((response) => {
setRecruitmentApplicants(
data.data.filter(
(recruitmentApplicant) =>
!recruitmentApplicant.withdrawn &&
recruitmentApplicant.recruiter_status === 0 &&
recruitmentApplicant.recruitment_position?.id === Number.parseInt(positionId),
),
response.data.unprocessed
);
setWithdrawnApplicants(
data.data.filter(
(recruitmentApplicant) =>
recruitmentApplicant.withdrawn &&
recruitmentApplicant.recruitment_position?.id === Number.parseInt(positionId),
),
response.data.withdrawn
);
setHardtogetApplicants(
data.data.filter(
(recruitmentApplicant) =>
!recruitmentApplicant.withdrawn &&
recruitmentApplicant.recruiter_status === 2 &&
recruitmentApplicant.recruitment_position?.id === Number.parseInt(positionId),
),
response.data.hardtoget
);
setRejectedApplicants(
data.data.filter(
(recruitmentApplicant) =>
!recruitmentApplicant.withdrawn &&
recruitmentApplicant.recruiter_status === 3 &&
recruitmentApplicant.recruitment_position?.id === Number.parseInt(positionId),
),
response.data.rejected
);
setAcceptedApplicants(
data.data.filter(
(recruitmentApplicant) =>
!recruitmentApplicant.withdrawn &&
recruitmentApplicant.recruiter_status === 1 &&
recruitmentApplicant.recruitment_position?.id === Number.parseInt(positionId),
),
response.data.accepted
);
setShowSpinner(false);
})
Expand Down
17 changes: 16 additions & 1 deletion frontend/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import type {
RecruitmentDto,
RecruitmentGangDto,
RecruitmentPositionDto,
RecruitmentPositionOrganizedApplicationsDto,
RecruitmentPositionPostDto,
RecruitmentPositionPutDto,
RecruitmentSeparatePositionDto,
Expand Down Expand Up @@ -819,6 +820,20 @@ export async function getRecruitmentApplicationsForGang(
return await axios.get(url, { withCredentials: true });
}

export async function getRecruitmentPositionOrganizedApplications(
positionId: string,
): Promise<AxiosResponse<RecruitmentPositionOrganizedApplicationsDto>> {
const url =
BACKEND_DOMAIN +
reverse({
pattern: ROUTES.backend.samfundet__recruitment_position_organized_applications,
urlParams: {
pk: positionId
},
});
return await axios.get(url, { withCredentials: true });
}

export async function getRecruitmentSharedInterviewGroups(
recruitmentId: string,
): Promise<AxiosResponse<RecruitmentSharedInterviewGroupDto[]>> {
Expand Down Expand Up @@ -888,7 +903,7 @@ export async function updateRecruitmentApplicationStateForGang(
export async function updateRecruitmentApplicationStateForPosition(
applicationId: string,
application: Partial<RecruitmentApplicationStateDto>,
): Promise<AxiosResponse<RecruitmentApplicationDto[]>> {
): Promise<AxiosResponse<RecruitmentPositionOrganizedApplicationsDto>> {
const url =
BACKEND_DOMAIN +
reverse({
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,14 @@ export type InterviewDto = {
interviewers?: UserDto[];
};

export type RecruitmentPositionOrganizedApplicationsDto = {
unprocessed: RecruitmentApplicationDto[];
withdrawn: RecruitmentApplicationDto[];
rejected: RecruitmentApplicationDto[];
accepted: RecruitmentApplicationDto[];
hardtoget: RecruitmentApplicationDto[];
}

export type RecruitmentApplicationDto = {
id: string;
interview?: InterviewDto;
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/routes/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,7 @@ export const ROUTES_BACKEND = {
samfundet__interview_list: '/api/interview/',
samfundet__interview_detail: '/api/interview/:pk/',
samfundet__api_root: '/api/',
samfundet__api_root: '/api/:format',
samfundet__schema: '/schema/',
samfundet__swagger_ui: '/schema/swagger-ui/',
samfundet__redoc: '/schema/redoc/',
Expand Down Expand Up @@ -590,6 +591,7 @@ export const ROUTES_BACKEND = {
samfundet__recruitment_set_interview: '/recruitment-set-interview/:pk/',
samfundet__recruitment_application_states_choices: '/recruitment-application-states-choices',
samfundet__recruitment_application_update_state_gang: '/recruitment-application-update-state-gang/:pk/',
samfundet__recruitment_position_organized_applications: '/recruitment-position-organized-applications/:pk/',
samfundet__recruitment_application_update_state_position: '/recruitment-application-update-state-position/:pk/',
samfundet__recruitment_applications_recruiter: '/recruitment-application-recruiter/:applicationId/',
samfundet__recruitment_withdraw_application: '/recruitment-withdraw-application/:pk/',
Expand Down

0 comments on commit 33430af

Please sign in to comment.