Skip to content

Commit

Permalink
feat: add has_fileuploadfield (#1632)
Browse files Browse the repository at this point in the history
* feat: add has_fileuploadfield

* add docs

---------

Co-authored-by: magsyg <[email protected]>
  • Loading branch information
magsyg and magsyg authored Dec 20, 2024
1 parent 96df1c8 commit ce08b37
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Generated by Django 5.1.1 on 2024-12-07 12:20

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("samfundet", "0010_recruitment_promo_media"),
]

operations = [
migrations.AddField(
model_name="recruitmentposition",
name="file_description_en",
field=models.TextField(
blank=True, help_text="Description of file needed (EN)", null=True
),
),
migrations.AddField(
model_name="recruitmentposition",
name="file_description_nb",
field=models.TextField(
blank=True, help_text="Description of file needed (NB)", null=True
),
),
migrations.AddField(
model_name="recruitmentposition",
name="has_file_upload",
field=models.BooleanField(
default=False, help_text="Does position have file upload"
),
),
]
42 changes: 33 additions & 9 deletions backend/samfundet/models/recruitment.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,16 @@ class RecruitmentPosition(CustomBaseModel):
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)

has_file_upload = models.BooleanField(help_text='Does position have file upload', default=False)
file_description_nb = models.TextField(help_text='Description of file needed (NB)', null=True, blank=True)
file_description_en = models.TextField(help_text='Description of file needed (EN)', null=True, blank=True)

# TODO: Implement tag functionality
tags = models.CharField(max_length=100, help_text='Tags for the position')

# TODO: Implement interviewer functionality
interviewers = models.ManyToManyField(to=User, help_text='Interviewers for the position', blank=True, related_name='interviewers')

recruitment = models.ForeignKey(
Recruitment,
on_delete=models.CASCADE,
Expand All @@ -161,12 +171,6 @@ class RecruitmentPosition(CustomBaseModel):
help_text='Shared interviewgroup for position',
)

# TODO: Implement tag functionality
tags = models.CharField(max_length=100, help_text='Tags for the position')

# 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
Expand All @@ -185,11 +189,31 @@ 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:
# Error messages
ONLY_ONE_OWNER_ERROR = 'Position must be owned by either gang or section, not both'
NO_OWNER_ERROR = 'Position must have an owner, either a gang or a gang section'
FILE_DESCRIPTION_REQUIRED_ERROR = 'Description of file is needed, if position has file upload'

def clean(self) -> None: # noqa: C901
super().clean()
errors: dict[str, list[ValidationError]] = defaultdict(list)

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')
if self.gang and self.section:
# Both gang and section provide
errors['gang'].append(self.ONLY_ONE_OWNER_ERROR)
errors['section'].append(self.ONLY_ONE_OWNER_ERROR)
elif not (self.gang or self.section):
# neither gang nor section provided
errors['gang'].append(self.NO_OWNER_ERROR)
errors['section'].append(self.NO_OWNER_ERROR)
if self.has_file_upload:
# Check Norwegian file description
if not self.file_description_nb or len(self.file_description_nb) == 0:
errors['file_description_nb'].append(self.FILE_DESCRIPTION_REQUIRED_ERROR)
# Check English file description
if not self.file_description_en or len(self.file_description_en) == 0:
errors['file_description_en'].append(self.FILE_DESCRIPTION_REQUIRED_ERROR)
raise ValidationError(errors)

def save(self, *args: tuple, **kwargs: dict) -> None:
if self.norwegian_applicants_only:
Expand Down
61 changes: 61 additions & 0 deletions backend/samfundet/models/tests/test_recruitment.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,67 @@ def test_actual_deadline_before_shown_deadline(self, fixture_org):
assert Recruitment.SHOWN_AFTER_ACTUAL_ERROR in e['shown_application_deadline']


class TestRecruitmentPosition:
default_data = {
'name_en': 'Name_en',
'name_nb': 'Name_nb',
'short_description_nb': 'short_description_nb',
'short_description_en': 'short_description_en',
'long_description_nb': 'long_description_nb',
'long_description_en': 'long_description_en',
'is_funksjonaer_position': False,
'default_application_letter_nb': 'default_application_letter_nb',
'default_application_letter_en': 'default_application_letter_en',
'norwegian_applicants_only': False,
'tags': 'tag1, tag2, tag3',
}

def test_create_recruitmentposition_gang(self, fixture_gang: Gang):
test_position = RecruitmentPosition.objects.create(**self.default_data, gang=fixture_gang)
assert test_position.id

def test_create_recruitmentposition_section(self, fixture_gang_section: GangSection):
test_position = RecruitmentPosition.objects.create(**self.default_data, section=fixture_gang_section)
assert test_position.id

def test_create_recruitmentposition_no_section(self):
with pytest.raises(ValidationError) as error:
RecruitmentPosition.objects.create(**self.default_data)
e = dict(error.value)
assert RecruitmentPosition.NO_OWNER_ERROR in e['section']
assert RecruitmentPosition.NO_OWNER_ERROR in e['gang']

def test_create_recruitmentposition_only_one_owner(self, fixture_gang_section: GangSection, fixture_gang: Gang):
with pytest.raises(ValidationError) as error:
RecruitmentPosition.objects.create(**self.default_data, section=fixture_gang_section, gang=fixture_gang)
e = dict(error.value)
assert RecruitmentPosition.ONLY_ONE_OWNER_ERROR in e['section']
assert RecruitmentPosition.ONLY_ONE_OWNER_ERROR in e['gang']

def test_create_recruitmentposition_file_upload_no_description(self, fixture_gang_section: GangSection):
with pytest.raises(ValidationError) as error:
RecruitmentPosition.objects.create(**self.default_data, section=fixture_gang_section, has_file_upload=True)
e = dict(error.value)
assert RecruitmentPosition.FILE_DESCRIPTION_REQUIRED_ERROR in e['file_description_nb']
assert RecruitmentPosition.FILE_DESCRIPTION_REQUIRED_ERROR in e['file_description_en']

with pytest.raises(ValidationError) as error:
RecruitmentPosition.objects.create(**self.default_data, section=fixture_gang_section, has_file_upload=True, file_description_en='Description')
e = dict(error.value)
assert RecruitmentPosition.FILE_DESCRIPTION_REQUIRED_ERROR in e['file_description_nb']

with pytest.raises(ValidationError) as error:
RecruitmentPosition.objects.create(**self.default_data, section=fixture_gang_section, has_file_upload=True, file_description_nb='Description')
e = dict(error.value)
assert RecruitmentPosition.FILE_DESCRIPTION_REQUIRED_ERROR in e['file_description_en']

def test_create_recruitmentposition_file_upload(self, fixture_gang_section: GangSection):
test_position = RecruitmentPosition.objects.create(
**self.default_data, section=fixture_gang_section, has_file_upload=True, file_description_en='Description', file_description_nb='Description'
)
assert test_position.id


class TestRecruitmentStats:
def test_recruitment_has_stats(self, fixture_recruitment: Recruitment):
"""Check if fixture_recruitment has the related object"""
Expand Down

0 comments on commit ce08b37

Please sign in to comment.