diff --git a/backend/root/management/commands/seed_scripts/recruitment_applications.py b/backend/root/management/commands/seed_scripts/recruitment_applications.py index 6f5be2a63..4c9eab2db 100644 --- a/backend/root/management/commands/seed_scripts/recruitment_applications.py +++ b/backend/root/management/commands/seed_scripts/recruitment_applications.py @@ -1,6 +1,6 @@ from __future__ import annotations -from random import randint +from random import sample, randint from samfundet.models.general import User from samfundet.models.recruitment import RecruitmentPosition, RecruitmentApplication @@ -20,19 +20,13 @@ def seed(): yield 0, 'Deleted old applications' positions = RecruitmentPosition.objects.all() - users = User.objects.all() + users = list(User.objects.all()) created_count = 0 for position_index, position in enumerate(positions): - for _ in range(randint(0, 5)): # Create between 0 and 5 instances for each position + for user in sample(users, randint(0, 5)): # Create between 0 and 5 instances for each position application_data = APPLICATION_DATA.copy() - application_data.update( - { - 'recruitment_position': position, - 'recruitment': position.recruitment, - 'user': users[randint(0, len(users) - 1)], # random user from all users - } - ) + application_data.update({'recruitment_position': position, 'recruitment': position.recruitment, 'user': user}) _application, created = RecruitmentApplication.objects.get_or_create(**application_data) if created: diff --git a/backend/samfundet/models/recruitment.py b/backend/samfundet/models/recruitment.py index a6c045a2d..5d6fa1d50 100644 --- a/backend/samfundet/models/recruitment.py +++ b/backend/samfundet/models/recruitment.py @@ -393,15 +393,24 @@ def update_priority(self, direction: int) -> None: break self.organize_priorities() + ALREADY_APPLIED_ERROR = 'Already created an application for this recruitment' + REAPPLY_TOO_MANY_APPLICATIONS_ERROR = 'Can not reapply application, too many active application' TOO_MANY_APPLICATIONS_ERROR = 'Too many applications for recruitment' - def clean(self, *args: tuple, **kwargs: dict) -> None: + def clean(self, *args: tuple, **kwargs: dict) -> None: # noqa: C901 super().clean() errors: dict[str, list[ValidationError]] = defaultdict(list) + # Cant use not self.pk, due to UUID generating it before save + current_application = RecruitmentApplication.objects.filter(pk=self.pk).first() + # validates if there are not two applications for same user and same recruitmentposition + if ( + not current_application + and RecruitmentApplication.objects.filter(user=self.user, recruitment=self.recruitment, recruitment_position=self.recruitment_position).first() + ): + errors['recruitment_position'].append(self.ALREADY_APPLIED_ERROR) # If there is max applications, check if applicant have applied to not to many - # Cant use not self.pk, due to UUID generating it before save. if self.recruitment.max_applications: user_applications_count = RecruitmentApplication.objects.filter(user=self.user, recruitment=self.recruitment, withdrawn=False).count() current_application = RecruitmentApplication.objects.filter(pk=self.pk).first() diff --git a/backend/samfundet/models/tests/test_recruitment.py b/backend/samfundet/models/tests/test_recruitment.py index bf42b572e..da83d643e 100644 --- a/backend/samfundet/models/tests/test_recruitment.py +++ b/backend/samfundet/models/tests/test_recruitment.py @@ -630,6 +630,38 @@ def test_recruitmentapplication_total_interviews_single_gang( class TestRecruitmentApplicationStatus: + def test_recruitmentstats_create(self, fixture_user: User, fixture_recruitment_position: RecruitmentPosition, fixture_recruitment: Recruitment): + application = RecruitmentApplication.objects.create( + user=fixture_user, + recruitment_position=fixture_recruitment_position, + recruitment=fixture_recruitment, + application_text='I have applied', + applicant_priority=1, + ) + assert application.id + + def test_recruitmentstats_no_doubleapplication_for_position( + self, fixture_user: User, fixture_recruitment_position: RecruitmentPosition, fixture_recruitment: Recruitment + ): + application = RecruitmentApplication.objects.create( + user=fixture_user, + recruitment_position=fixture_recruitment_position, + recruitment=fixture_recruitment, + application_text='I have applied', + applicant_priority=1, + ) + assert application.id + with pytest.raises(ValidationError) as error: + RecruitmentApplication.objects.create( + user=application.user, + recruitment_position=application.recruitment_position, + recruitment=application.recruitment, + application_text='I have applied a secound time!', + applicant_priority=1, + ) + e = dict(error.value) + assert RecruitmentApplication.ALREADY_APPLIED_ERROR in e['recruitment_position'] + def test_check_called_accepted_sets_auto_rejection( self, fixture_recruitment_application: RecruitmentApplication, fixture_recruitment_application2: RecruitmentApplication ): diff --git a/backend/samfundet/tests/test_email.py b/backend/samfundet/tests/test_email.py index d27d2fc9e..7639bfe37 100644 --- a/backend/samfundet/tests/test_email.py +++ b/backend/samfundet/tests/test_email.py @@ -63,6 +63,7 @@ def setUp(self): self.user_withdrawn = User.objects.create(username='withdrawn-user', email='withdrawn@example.com') self.user_contacted = User.objects.create(username='contacted-user', email='contacted@example.com') self.user_rejected_but_contacted = User.objects.create(username='skurra-user', email='hard.to.get@example.com') + self.user_rejected_but_contacted2 = User.objects.create(username='skurra-user2', email='hard2.to.get@example.com') self.admin_user = User.objects.create_superuser(username='admin', email='admin@example.com', password='adminpassword') @@ -111,7 +112,7 @@ def setUp(self): ) RecruitmentApplication.objects.create( - user=self.user_rejected_but_contacted, + user=self.user_rejected_but_contacted2, recruitment=self.recruitment, recruiter_status=RecruitmentStatusChoices.CALLED_AND_ACCEPTED, withdrawn=False, diff --git a/backend/samfundet/views.py b/backend/samfundet/views.py index 42e6a4e3e..065def20d 100644 --- a/backend/samfundet/views.py +++ b/backend/samfundet/views.py @@ -27,6 +27,7 @@ from django.shortcuts import get_object_or_404 from django.contrib.auth import login, logout, update_session_auth_hash from django.utils.encoding import force_bytes +from django.core.exceptions import ValidationError from django.middleware.csrf import get_token from django.utils.decorators import method_decorator from django.contrib.auth.models import Group, Permission @@ -890,19 +891,26 @@ class RecruitmentApplicationForApplicantView(ModelViewSet): def update(self, request: Request, pk: int) -> Response: data = request.data.dict() if isinstance(request.data, QueryDict) else request.data recruitment_position = get_object_or_404(RecruitmentPosition, pk=pk) + existing_application = RecruitmentApplication.objects.filter(user=request.user, recruitment_position=pk).first() + # If update + if existing_application: + try: + existing_application.withdrawn = False + existing_application.application_text = data['application_text'] + existing_application.save() + serializer = self.serializer_class(existing_application) + return Response(serializer.data, status.HTTP_200_OK) + except ValidationError as e: + return Response(e.message_dict, status=status.HTTP_400_BAD_REQUEST) + + # If create data['recruitment_position'] = recruitment_position.pk data['recruitment'] = recruitment_position.recruitment.pk data['user'] = request.user.pk serializer = self.get_serializer(data=data) if serializer.is_valid(): - existing_application = RecruitmentApplication.objects.filter(user=request.user, recruitment_position=pk).first() - if existing_application: - existing_application.application_text = serializer.validated_data['application_text'] - existing_application.save() - serializer = self.get_serializer(existing_application) - return Response(serializer.data, status=status.HTTP_200_OK) serializer.save() - return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.data, status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) def retrieve(self, request: Request, pk: int) -> Response: