diff --git a/backend/samfundet/migrations/0006_recruitmentposition_section_and_more.py b/backend/samfundet/migrations/0006_recruitmentposition_section_and_more.py new file mode 100644 index 000000000..e2d1c196d --- /dev/null +++ b/backend/samfundet/migrations/0006_recruitmentposition_section_and_more.py @@ -0,0 +1,24 @@ +# Generated by Django 5.1.1 on 2024-09-29 16:52 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('samfundet', '0005_role_content_type_role_created_at_role_created_by_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='recruitmentposition', + name='section', + field=models.ForeignKey(blank=True, help_text='The section that is recruiting', null=True, on_delete=django.db.models.deletion.CASCADE, to='samfundet.gangsection'), + ), + migrations.AlterField( + model_name='recruitmentposition', + name='gang', + field=models.ForeignKey(blank=True, help_text='The gang that is recruiting', null=True, on_delete=django.db.models.deletion.CASCADE, to='samfundet.gang'), + ), + ] diff --git a/backend/samfundet/models/recruitment.py b/backend/samfundet/models/recruitment.py index e48ef433b..f4b9548b9 100644 --- a/backend/samfundet/models/recruitment.py +++ b/backend/samfundet/models/recruitment.py @@ -12,7 +12,7 @@ from root.utils.mixins import CustomBaseModel, FullCleanSaveMixin -from .general import Gang, User, Campus, Organization +from .general import Gang, User, Campus, GangSection, Organization from .model_choices import RecruitmentStatusChoices, RecruitmentApplicantStates, RecruitmentPriorityChoices @@ -136,7 +136,9 @@ class RecruitmentPosition(CustomBaseModel): norwegian_applicants_only = models.BooleanField(help_text='Is this position only for Norwegian applicants?', default=False) - gang = models.ForeignKey(to=Gang, on_delete=models.CASCADE, help_text='The gang that is recruiting') + gang = models.ForeignKey(to=Gang, on_delete=models.CASCADE, help_text='The gang that is recruiting', null=True, blank=True) + section = models.ForeignKey(GangSection, on_delete=models.CASCADE, help_text='The section that is recruiting', null=True, blank=True) + recruitment = models.ForeignKey( Recruitment, on_delete=models.CASCADE, @@ -161,6 +163,12 @@ class RecruitmentPosition(CustomBaseModel): # TODO: Implement interviewer functionality interviewers = models.ManyToManyField(to=User, help_text='Interviewers for the position', blank=True, related_name='interviewers') + def resolve_section(self, *, return_id: bool = False) -> GangSection | int: + if return_id: + # noinspection PyTypeChecker + return self.section_id + return self.section + def resolve_gang(self, *, return_id: bool = False) -> Gang | int: if return_id: # noinspection PyTypeChecker @@ -173,6 +181,12 @@ def resolve_org(self, *, return_id: bool = False) -> Organization | int: def __str__(self) -> str: return f'Position: {self.name_en} in {self.recruitment}' + def clean(self) -> None: + super().clean() + + if (self.gang and self.section) or not (self.gang or self.section): + raise ValidationError('Position must be owned by either gang or section, not both') + def save(self, *args: tuple, **kwargs: dict) -> None: if self.norwegian_applicants_only: self.name_en = 'Norwegian speaking applicants only' diff --git a/backend/samfundet/models/tests/test_recruitment.py b/backend/samfundet/models/tests/test_recruitment.py index b30001d7f..392ed364b 100644 --- a/backend/samfundet/models/tests/test_recruitment.py +++ b/backend/samfundet/models/tests/test_recruitment.py @@ -5,7 +5,7 @@ from django.utils import timezone from django.core.exceptions import ValidationError -from samfundet.models.general import Gang, User, Campus +from samfundet.models.general import Gang, User, Campus, GangSection from samfundet.models.recruitment import ( Interview, Recruitment, @@ -834,3 +834,22 @@ def test_recruitment_progress_applications_multiple_new_updates_progress( new_application.recruiter_status = RecruitmentStatusChoices.CALLED_AND_ACCEPTED new_application.save() assert fixture_recruitment.recruitment_progress() == 1 + + +def test_position_must_have_single_owner(fixture_recruitment_position: RecruitmentPosition, fixture_gang: Gang, fixture_gang_section: GangSection): + fixture_recruitment_position.gang = fixture_gang + fixture_recruitment_position.section = fixture_gang_section + with pytest.raises(ValidationError): + fixture_recruitment_position.save() + + fixture_recruitment_position.gang = None + fixture_recruitment_position.section = None + with pytest.raises(ValidationError): + fixture_recruitment_position.save() + + fixture_recruitment_position.gang = fixture_gang + fixture_recruitment_position.save() + + fixture_recruitment_position.gang = None + fixture_recruitment_position.section = fixture_gang_section + fixture_recruitment_position.save()