Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/applicantobject #700

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from random import randint

from samfundet.models.recruitment import RecruitmentAdmission, RecruitmentPosition
from samfundet.models.recruitment import RecruitmentAdmission, RecruitmentPosition, RecruitmentApplicant
from samfundet.models.general import User

# Some example data to use for the new RecruitmentAdmission instances
Expand All @@ -23,13 +23,12 @@ def seed():
for position_index, position in enumerate(positions):
for i in range(randint(0, 5)): # Create between 0 and 5 instances for each position
admission_data = ADMISSION_DATA.copy()
applicant, created = RecruitmentApplicant.objects.get_or_create(user=users[randint(0, len(users) - 1)])
admission_data.update(
{
'recruitment_position': position,
'recruitment': position.recruitment,
'user':
users[randint(0,
len(users) - 1)] # random user from all users
'applicant': applicant # random user from all users
}
)
admission, created = RecruitmentAdmission.objects.get_or_create(**admission_data)
Expand Down
18 changes: 13 additions & 5 deletions backend/samfundet/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
CustomGuardedModelAdmin,
)
from .models.event import (Event, EventGroup, EventRegistration)
from .models.recruitment import (Recruitment, RecruitmentPosition, RecruitmentAdmission, InterviewRoom)
from .models.recruitment import (Recruitment, RecruitmentPosition, RecruitmentAdmission, RecruitmentApplicant, InterviewRoom)
from .models.general import (
Tag,
User,
Expand Down Expand Up @@ -499,8 +499,8 @@ class RecruitmentAdmissionInline(admin.TabularInline):
"""
model = RecruitmentAdmission
extra = 0
readonly_fields = ['linked_admission_text', 'user', 'applicant_priority']
fields = ['linked_admission_text', 'user', 'applicant_priority']
readonly_fields = ['linked_admission_text', 'applicant', 'applicant_priority']
fields = ['linked_admission_text', 'applicant', 'applicant_priority']

def linked_admission_text(self, obj: RecruitmentAdmission) -> str:
"""
Expand Down Expand Up @@ -530,6 +530,14 @@ def admissions_count(self, obj: RecruitmentPosition) -> int:
return count


@admin.register(RecruitmentApplicant)
class RecruitmentApplicantAdmin(CustomGuardedModelAdmin):
sortable_by = ['id', 'first_name', 'last_name', 'email']
list_display = ['id', 'first_name', 'last_name', 'email']
search_fields = ['id', 'first_name', 'last_name', 'email']
list_select_related = True


@admin.register(RecruitmentAdmission)
class RecruitmentAdmissionAdmin(CustomGuardedModelAdmin):
sortable_by = [
Expand All @@ -538,15 +546,15 @@ class RecruitmentAdmissionAdmin(CustomGuardedModelAdmin):
'recruitment',
'interview_time',
'interview_location',
'user',
'applicant',
]
list_display = [
'id',
'recruitment_position',
'recruitment',
'interview_time',
'interview_location',
'user',
'applicant',
]
search_fields = [
'id',
Expand Down
15 changes: 12 additions & 3 deletions backend/samfundet/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from samfundet.contants import DEV_PASSWORD
from samfundet.models.billig import BilligEvent
from samfundet.models.event import Event, EventAgeRestriction, EventTicketType
from samfundet.models.recruitment import Recruitment, RecruitmentPosition, RecruitmentAdmission
from samfundet.models.recruitment import Recruitment, RecruitmentPosition, RecruitmentAdmission, RecruitmentApplicant
from samfundet.models.general import User, Image, InformationPage, Organization, Gang, BlogPost

import root.management.commands.seed_scripts.billig as billig_seed
Expand Down Expand Up @@ -108,6 +108,8 @@ def fixture_user_pw() -> Iterator[str]:
def fixture_user(fixture_user_pw: str) -> Iterator[User]:
user = User.objects.create_user(
username='user',
first_name='first',
last_name='last',
email='[email protected]',
password=fixture_user_pw,
)
Expand Down Expand Up @@ -254,13 +256,20 @@ def fixture_blogpost(fixture_image: Image) -> Iterator[BlogPost]:


@pytest.fixture
def fixture_recruitment_admission(fixture_user: User, fixture_recruitment_position: RecruitmentPosition,
def fixture_applicant(fixture_user: User) -> Iterator[RecruitmentApplicant]:
applicant = RecruitmentApplicant.objects.create(user=fixture_user, )
yield applicant
applicant.delete()


@pytest.fixture
def fixture_recruitment_admission(fixture_applicant: RecruitmentApplicant, fixture_recruitment_position: RecruitmentPosition,
fixture_recruitment: Recruitment) -> Iterator[RecruitmentAdmission]:
admission = RecruitmentAdmission.objects.create(
admission_text='Test admission text',
recruitment_position=fixture_recruitment_position,
recruitment=fixture_recruitment,
user=fixture_user,
applicant=fixture_applicant,
applicant_priority=1,
recruiter_priority=RecruitmentAdmission.PRIORITY_CHOICES[0][0],
recruiter_status=RecruitmentAdmission.STATUS_CHOICES[0][0],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Generated by Django 4.2.5 on 2023-09-26 18:07

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import jsonfield.fields


class Migration(migrations.Migration):

dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('samfundet', '0038_alter_recruitmentadmission_interview_location_and_more'),
]

operations = [
migrations.AlterModelOptions(
name='notification',
options={'ordering': ('-timestamp',), 'verbose_name': 'Notification', 'verbose_name_plural': 'Notifications'},
),
migrations.RemoveField(
model_name='recruitmentadmission',
name='user',
),
migrations.AlterField(
model_name='notification',
name='action_object_content_type',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notify_action_object', to='contenttypes.contenttype', verbose_name='action object content type'),
),
migrations.AlterField(
model_name='notification',
name='action_object_object_id',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='action object object id'),
),
migrations.AlterField(
model_name='notification',
name='actor_content_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notify_actor', to='contenttypes.contenttype', verbose_name='actor content type'),
),
migrations.AlterField(
model_name='notification',
name='actor_object_id',
field=models.CharField(max_length=255, verbose_name='actor object id'),
),
migrations.AlterField(
model_name='notification',
name='data',
field=jsonfield.fields.JSONField(blank=True, null=True, verbose_name='data'),
),
migrations.AlterField(
model_name='notification',
name='deleted',
field=models.BooleanField(db_index=True, default=False, verbose_name='deleted'),
),
migrations.AlterField(
model_name='notification',
name='description',
field=models.TextField(blank=True, null=True, verbose_name='description'),
),
migrations.AlterField(
model_name='notification',
name='emailed',
field=models.BooleanField(db_index=True, default=False, verbose_name='emailed'),
),
migrations.AlterField(
model_name='notification',
name='level',
field=models.CharField(choices=[('success', 'success'), ('info', 'info'), ('warning', 'warning'), ('error', 'error')], default='info', max_length=20, verbose_name='level'),
),
migrations.AlterField(
model_name='notification',
name='public',
field=models.BooleanField(db_index=True, default=True, verbose_name='public'),
),
migrations.AlterField(
model_name='notification',
name='recipient',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications', to=settings.AUTH_USER_MODEL, verbose_name='recipient'),
),
migrations.AlterField(
model_name='notification',
name='target_content_type',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notify_target', to='contenttypes.contenttype', verbose_name='target content type'),
),
migrations.AlterField(
model_name='notification',
name='target_object_id',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='target object id'),
),
migrations.AlterField(
model_name='notification',
name='timestamp',
field=models.DateTimeField(db_index=True, default=django.utils.timezone.now, verbose_name='timestamp'),
),
migrations.AlterField(
model_name='notification',
name='unread',
field=models.BooleanField(db_index=True, default=True, verbose_name='unread'),
),
migrations.AlterField(
model_name='notification',
name='verb',
field=models.CharField(max_length=255, verbose_name='verb'),
),
migrations.CreateModel(
name='RecruitmentApplicant',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('user', models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='applicant', to=settings.AUTH_USER_MODEL)),
],
),
migrations.AddField(
model_name='recruitmentadmission',
name='applicant',
field=models.ForeignKey(help_text='The user that is applying', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='admissions', to='samfundet.recruitmentapplicant'),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by Django 4.2.5 on 2023-09-26 18:55

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('samfundet', '0039_alter_notification_options_and_more'),
]

operations = [
migrations.AlterModelOptions(
name='recruitmentapplicant',
options={'verbose_name': 'Applicant', 'verbose_name_plural': 'Applicants'},
),
]
25 changes: 24 additions & 1 deletion backend/samfundet/models/recruitment.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,36 @@ def clean(self) -> None:
super().clean()


class RecruitmentApplicant(models.Model):
user = models.OneToOneField(User, null=True, on_delete=models.SET_NULL, related_name='applicant')

def name(self) -> str:
return f'{self.user.first_name} {self.user.last_name}' if self.user else 'Redacted'

def first_name(self) -> str:
return f'{self.user.first_name}' if self.user else 'Redacted'

def last_name(self) -> str:
return f'{self.user.last_name}' if self.user else 'Redacted'

def email(self) -> str:
return self.user.email if self.user else '[email protected]'

def __str__(self) -> str:
return self.name()

class Meta:
verbose_name = 'Applicant'
verbose_name_plural = 'Applicants'


class RecruitmentAdmission(models.Model):
admission_text = models.TextField(help_text='Admission text for the admission')
recruitment_position = models.ForeignKey(
RecruitmentPosition, on_delete=models.CASCADE, help_text='The recruitment position that is recruiting', related_name='admissions'
)
recruitment = models.ForeignKey(Recruitment, on_delete=models.CASCADE, help_text='The recruitment that is recruiting', related_name='admissions')
user = models.ForeignKey(User, on_delete=models.CASCADE, help_text='The user that is applying', related_name='admissions')
applicant = models.ForeignKey(RecruitmentApplicant, null=True, on_delete=models.CASCADE, help_text='The user that is applying', related_name='admissions')
applicant_priority = models.IntegerField(help_text='The priority of the admission')

interview_time = models.DateTimeField(help_text='The time of the interview', null=True, blank=True)
Expand Down
44 changes: 33 additions & 11 deletions backend/samfundet/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from rest_framework import serializers

from .models.billig import BilligEvent, BilligTicketGroup, BilligPriceGroup
from .models.recruitment import (Recruitment, RecruitmentPosition, RecruitmentAdmission, InterviewRoom)
from .models.recruitment import (Recruitment, RecruitmentPosition, RecruitmentApplicant, RecruitmentAdmission, InterviewRoom)
from .models.event import (Event, EventGroup, EventCustomTicket)
from .models.general import (
Tag,
Expand Down Expand Up @@ -494,21 +494,32 @@ class Meta:

class UserForRecruitmentSerializer(serializers.ModelSerializer):
recruitment_admission_ids = serializers.SerializerMethodField()
first_name = serializers.SerializerMethodField(method_name='first_name', read_only=True)
last_name = serializers.SerializerMethodField(method_name='last_name', read_only=True)
email = serializers.SerializerMethodField(method_name='email', read_only=True)

class Meta:
model = User
model = RecruitmentApplicant
fields = [
'id',
'first_name',
'last_name',
'username',
'email',
'recruitment_admission_ids', # Add this to the fields list
]

def get_recruitment_admission_ids(self, obj: User) -> list[int]:
def get_recruitment_admission_ids(self, obj: RecruitmentApplicant) -> list[int]:
"""Return list of recruitment admission IDs for the user."""
return RecruitmentAdmission.objects.filter(user=obj).values_list('id', flat=True)
return RecruitmentAdmission.objects.filter(applicant__user=obj.user).values_list('id', flat=True)

def first_name(self, obj: RecruitmentApplicant) -> str:
return obj.first_name()

def last_name(self, obj: RecruitmentApplicant) -> str:
return obj.last_name()

def email(self, obj: RecruitmentApplicant) -> str:
return obj.email()


class RecruitmentPositionSerializer(serializers.ModelSerializer):
Expand All @@ -532,14 +543,13 @@ class Meta:
def create(self, validated_data: dict) -> RecruitmentAdmission:
recruitment_position = validated_data['recruitment_position']
recruitment = recruitment_position.recruitment
user = self.context['request'].user
applicant = RecruitmentApplicant.get_or_create(user=self.context['request'].user)
applicant_priority = 1

recruitment_admission = RecruitmentAdmission.objects.create(
admission_text=validated_data.get('admission_text'),
recruitment_position=recruitment_position,
recruitment=recruitment,
user=user,
applicant=applicant,
applicant_priority=applicant_priority,
interview_time=validated_data.get('interview_time'),
interview_location=validated_data.get('interview_location')
Expand All @@ -549,14 +559,26 @@ def create(self, validated_data: dict) -> RecruitmentAdmission:


class ApplicantInfoSerializer(serializers.ModelSerializer):
first_name = serializers.SerializerMethodField(method_name='first_name', read_only=True)
last_name = serializers.SerializerMethodField(method_name='last_name', read_only=True)
email = serializers.SerializerMethodField(method_name='email', read_only=True)

class Meta:
model = User
fields = ['id', 'first_name', 'last_name', 'email']
model = RecruitmentApplicant
fields = ['id', 'name', 'first_name', 'last_name', 'email']

def first_name(self, obj: RecruitmentApplicant) -> str:
return obj.first_name()

def last_name(self, obj: RecruitmentApplicant) -> str:
return obj.last_name()

def email(self, obj: RecruitmentApplicant) -> str:
return obj.email()


class RecruitmentAdmissionForGangSerializer(serializers.ModelSerializer):
user = ApplicantInfoSerializer(read_only=True)
applicant = ApplicantInfoSerializer(read_only=True)

class Meta:
model = RecruitmentAdmission
Expand Down
1 change: 0 additions & 1 deletion backend/samfundet/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,6 @@ def test_get_applicants_without_interviews(
### Assert ###
assert response.status_code == status.HTTP_200_OK
assert len(response.data) == 1
assert response.data[0]['id'] == fixture_user.id
assert response.data[0]['first_name'] == fixture_user.first_name
assert response.data[0]['last_name'] == fixture_user.last_name
assert response.data[0]['email'] == fixture_user.email
Expand Down
Loading