Skip to content

Commit

Permalink
Merge branch 'master' into robin/react-hook-form
Browse files Browse the repository at this point in the history
  • Loading branch information
robines authored Sep 25, 2024
2 parents bb66c04 + 742c4ac commit 493e24e
Show file tree
Hide file tree
Showing 31 changed files with 1,255 additions and 174 deletions.
8 changes: 0 additions & 8 deletions backend/root/management/commands/seed_scripts/campus.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@
'name_en': 'Norwegian School of Photography',
'name_nb': 'Fotofagskolen',
},
{
'name_en': 'Kristiania University College',
'name_nb': 'Høyskolen Kristiania',
},
{
'name_en': 'Trondheim Academy of Fine Art',
'name_nb': 'Kunstakademiet i Trondheim',
Expand All @@ -41,10 +37,6 @@
'name_en': 'NTNU Øya',
'name_nb': 'NTNU Øya',
},
{
'name_en': 'NTNU Rotvoll',
'name_nb': 'NTNU Rotvoll',
},
{
'name_en': 'NTNU Tunga',
'name_nb': 'NTNU Tunga',
Expand Down
2 changes: 2 additions & 0 deletions backend/root/utils/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,8 @@
samfundet__recruitment_for_recruiter_detail = 'samfundet:recruitment_for_recruiter-detail'
samfundet__recruitment_stats_list = 'samfundet:recruitment_stats-list'
samfundet__recruitment_stats_detail = 'samfundet:recruitment_stats-detail'
samfundet__recruitment_separateposition_list = 'samfundet:recruitment_separateposition-list'
samfundet__recruitment_separateposition_detail = 'samfundet:recruitment_separateposition-detail'
samfundet__recruitment_position_list = 'samfundet:recruitment_position-list'
samfundet__recruitment_position_detail = 'samfundet:recruitment_position-detail'
samfundet__recruitment_position_for_applicant_list = 'samfundet:recruitment_position_for_applicant-list'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Generated by Django 5.1.1 on 2024-09-24 17:14

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("samfundet", "0003_remove_gang_event_admin_group_and_more"),
]

operations = [
migrations.AddField(
model_name="campus",
name="total_students",
field=models.PositiveIntegerField(
default=1, verbose_name="Total students enrolled"
),
),
migrations.AddField(
model_name="recruitmentcampusstat",
name="applicant_percentage",
field=models.PositiveIntegerField(
blank=True,
default=0,
null=True,
verbose_name="Percentages of enrolled students applied for campus",
),
),
migrations.AddField(
model_name="recruitmentgangstat",
name="average_priority",
field=models.FloatField(
blank=True, null=True, verbose_name="Average priority"
),
),
migrations.AddField(
model_name="recruitmentgangstat",
name="total_accepted",
field=models.PositiveIntegerField(
blank=True, null=True, verbose_name="Total accepted"
),
),
migrations.AddField(
model_name="recruitmentgangstat",
name="total_rejected",
field=models.PositiveIntegerField(
blank=True, null=True, verbose_name="Total called and rejected"
),
),
migrations.AddField(
model_name="recruitmentstatistics",
name="average_applications_per_applicant",
field=models.FloatField(
blank=True, null=True, verbose_name="Gang diversity"
),
),
migrations.AddField(
model_name="recruitmentstatistics",
name="average_gangs_applied_to_per_applicant",
field=models.FloatField(
blank=True, null=True, verbose_name="Gang diversity"
),
),
migrations.AddField(
model_name="recruitmentstatistics",
name="total_accepted",
field=models.PositiveIntegerField(
blank=True, null=True, verbose_name="Total accepted applicants"
),
),
migrations.AddField(
model_name="recruitmentstatistics",
name="total_withdrawn",
field=models.PositiveIntegerField(
blank=True, null=True, verbose_name="Total Withdrawn applications"
),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Generated by Django 5.1.1 on 2024-09-24 22:26

import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('samfundet', '0004_campus_total_students_and_more'),
]

operations = [
migrations.AddField(
model_name='role',
name='content_type',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype'),
),
migrations.AddField(
model_name='role',
name='created_at',
field=models.DateTimeField(blank=True, editable=False, null=True),
),
migrations.AddField(
model_name='role',
name='created_by',
field=models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='role',
name='updated_at',
field=models.DateTimeField(blank=True, editable=False, null=True),
),
migrations.AddField(
model_name='role',
name='updated_by',
field=models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='role',
name='version',
field=models.PositiveIntegerField(blank=True, default=0, editable=False, null=True),
),
]
1 change: 1 addition & 0 deletions backend/samfundet/models/general.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ class Campus(FullCleanSaveMixin):
name_nb = models.CharField(max_length=64, unique=True, blank=False, null=False)
name_en = models.CharField(max_length=64, unique=True, blank=False, null=False)
abbreviation = models.CharField(max_length=10, blank=True, null=True)
total_students = models.PositiveIntegerField(null=False, blank=False, default=1, verbose_name='Total students enrolled')

def __str__(self) -> str:
if not self.abbreviation:
Expand Down
48 changes: 46 additions & 2 deletions backend/samfundet/models/recruitment.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ def resolve_org(self, *, return_id: bool = False) -> Organization | int:
return self.recruitment.resolve_org(return_id=return_id)

def __str__(self) -> str:
return f'Seperate recruitment: {self.name_nb} ({self.recruitment})'
return f'Separate recruitment: {self.name_nb} ({self.recruitment})'


class InterviewRoom(CustomBaseModel):
Expand Down Expand Up @@ -455,9 +455,33 @@ class RecruitmentStatistics(FullCleanSaveMixin):
total_applicants = models.PositiveIntegerField(null=True, blank=True, verbose_name='Total applicants')
total_applications = models.PositiveIntegerField(null=True, blank=True, verbose_name='Total applications')

# Total withdrawn applications
total_withdrawn = models.PositiveIntegerField(null=True, blank=True, verbose_name='Total Withdrawn applications')

# Total accepted applicants
total_accepted = models.PositiveIntegerField(null=True, blank=True, verbose_name='Total accepted applicants')

# Average amount of different gangs an applicant applies for
average_gangs_applied_to_per_applicant = models.FloatField(null=True, blank=True, verbose_name='Gang diversity')

# Average amount of applications for an applicant
average_applications_per_applicant = models.FloatField(null=True, blank=True, verbose_name='Gang diversity')

def save(self, *args: tuple, **kwargs: dict) -> None:
self.total_applications = self.recruitment.applications.count()
self.total_applicants = self.recruitment.applications.values('user').distinct().count()
self.total_withdrawn = self.recruitment.applications.filter(withdrawn=True).count()
self.total_accepted = (
self.recruitment.applications.filter(recruiter_status=RecruitmentStatusChoices.CALLED_AND_ACCEPTED).values('user').distinct().count()
)
if self.total_applicants > 0:
self.average_gangs_applied_to_per_applicant = (
self.recruitment.applications.values('user', 'recruitment_position__gang').distinct().count() / self.total_applicants
)
self.average_applications_per_applicant = self.total_applications / self.total_applicants if self.total_applicants > 0 else 0
else:
self.average_gangs_applied_to_per_applicant = 0
self.average_applications_per_applicant = 0
super().save(*args, **kwargs)
self.generate_time_stats()
self.generate_date_stats()
Expand Down Expand Up @@ -542,6 +566,7 @@ class RecruitmentCampusStat(models.Model):
campus = models.ForeignKey(Campus, on_delete=models.CASCADE, blank=False, null=False, related_name='date_stats')

count = models.PositiveIntegerField(null=False, blank=False, verbose_name='Count')
applicant_percentage = models.PositiveIntegerField(null=True, blank=True, default=0, verbose_name='Percentages of enrolled students applied for campus')

def __str__(self) -> str:
return f'{self.recruitment_stats} {self.campus} {self.count}'
Expand All @@ -550,8 +575,19 @@ def save(self, *args: tuple, **kwargs: dict) -> None:
self.count = User.objects.filter(
id__in=self.recruitment_stats.recruitment.applications.values_list('user', flat=True).distinct(), campus=self.campus
).count()
self.applicant_percentage = self.count / (self.campus.total_students if self.campus.total_students else 1)
super().save(*args, **kwargs)

def normalized_applicant_percentage(self) -> float:
applicant_percentages = list(
RecruitmentCampusStat.objects.filter(recruitment_stats=self.recruitment_stats).values_list('applicant_percentage', flat=True)
)
max_percent = max(applicant_percentages)
min_percent = min(applicant_percentages)
if max_percent - min_percent == 0:
return 0
return (self.applicant_percentage - min_percent) / (max_percent - min_percent)

def resolve_org(self, *, return_id: bool = False) -> Organization | int:
return self.recruitment_stats.resolve_org(return_id=return_id)

Expand All @@ -563,11 +599,19 @@ class RecruitmentGangStat(models.Model):
application_count = models.PositiveIntegerField(null=False, blank=False, verbose_name='Count')
applicant_count = models.PositiveIntegerField(null=False, blank=False, verbose_name='Count')

average_priority = models.FloatField(null=True, blank=True, verbose_name='Average priority')
total_accepted = models.PositiveIntegerField(null=True, blank=True, verbose_name='Total accepted')
total_rejected = models.PositiveIntegerField(null=True, blank=True, verbose_name='Total called and rejected')

def __str__(self) -> str:
return f'{self.recruitment_stats} {self.gang} {self.application_count}'

def save(self, *args: tuple, **kwargs: dict) -> None:
applications = RecruitmentApplication.objects.filter(recruitment=self.recruitment_stats.recruitment, recruitment_position__gang=self.gang)
self.application_count = applications.count()
self.applicant_count = applications.values_list('user', flat=True).distinct().count()
self.applicant_count = applications.values('user').distinct().count()

self.average_priority = applications.aggregate(models.Avg('applicant_priority'))['applicant_priority__avg'] if len(applications) > 0 else 0
self.total_accepted = applications.filter(recruiter_status=RecruitmentStatusChoices.CALLED_AND_ACCEPTED).values('user').distinct().count()
self.total_rejected = applications.filter(recruiter_status=RecruitmentStatusChoices.CALLED_AND_REJECTED).values('user').distinct().count()
super().save(*args, **kwargs)
4 changes: 3 additions & 1 deletion backend/samfundet/models/role.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

from django.db import models
from django.conf import settings
from django.contrib.contenttypes.models import ContentType

from root.utils.mixins import CustomBaseModel


class Role(models.Model):
class Role(CustomBaseModel):
name = models.CharField(max_length=255)
permissions = models.ManyToManyField('auth.Permission')
content_type = models.ForeignKey(ContentType, null=True, blank=True, on_delete=models.CASCADE)

def __str__(self) -> str:
return self.name
Expand Down
15 changes: 14 additions & 1 deletion backend/samfundet/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from root.constants import PHONE_NUMBER_REGEX
from root.utils.mixins import CustomBaseSerializer

from .models.role import Role
from .models.event import Event, EventGroup, EventCustomTicket, PurchaseFeedbackModel, PurchaseFeedbackQuestion, PurchaseFeedbackAlternative
from .models.billig import BilligEvent, BilligPriceGroup, BilligTicketGroup
from .models.general import (
Expand Down Expand Up @@ -484,6 +485,12 @@ class Meta:
fields = '__all__'


class RoleSerializer(CustomBaseSerializer):
class Meta:
model = Role
fields = '__all__'


class SaksdokumentSerializer(CustomBaseSerializer):
# Read only url file path used in frontend
url = serializers.SerializerMethodField(method_name='get_url', read_only=True)
Expand Down Expand Up @@ -604,6 +611,7 @@ class Meta:

class RecruitmentCampusStatSerializer(serializers.ModelSerializer):
campus = serializers.SerializerMethodField(method_name='campus_name', read_only=True)
applicant_percentage = serializers.SerializerMethodField(method_name='get_applicant_percentage', read_only=True)

class Meta:
model = RecruitmentCampusStat
Expand All @@ -612,6 +620,9 @@ class Meta:
def campus_name(self, stat: RecruitmentCampusStat) -> str:
return stat.campus.name_nb if stat.campus else None

def get_applicant_percentage(self, stat: RecruitmentCampusStat) -> float:
return stat.normalized_applicant_percentage()


class RecruitmentGangStatSerializer(serializers.ModelSerializer):
gang = serializers.SerializerMethodField(method_name='gang_name', read_only=True)
Expand Down Expand Up @@ -709,6 +720,8 @@ class RecruitmentSeparatePositionSerializer(CustomBaseSerializer):
class Meta:
model = RecruitmentSeparatePosition
fields = [
'id',
'recruitment',
'name_nb',
'name_en',
'description_nb',
Expand All @@ -731,7 +744,7 @@ def to_representation(self, instance: Recruitment) -> dict:


class RecruitmentForRecruiterSerializer(CustomBaseSerializer):
seperate_positions = RecruitmentSeparatePositionSerializer(many=True, read_only=True)
separate_positions = RecruitmentSeparatePositionSerializer(many=True, read_only=True)
recruitment_progress = serializers.SerializerMethodField(method_name='get_recruitment_progress', read_only=True)
statistics = RecruitmentStatisticsSerializer(read_only=True)

Expand Down
2 changes: 2 additions & 0 deletions backend/samfundet/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,13 @@
router.register('key-value', views.KeyValueView, 'key_value')
router.register('organizations', views.OrganizationView, 'organizations')
router.register('merch', views.MerchView, 'merch')
router.register('role', views.RoleView, 'role')

########## Recruitment ##########
router.register('recruitment', views.RecruitmentView, 'recruitment')
router.register('recruitment-for-recruiter', views.RecruitmentForRecruiterView, 'recruitment_for_recruiter')
router.register('recruitment-stats', views.RecruitmentStatisticsView, 'recruitment_stats')
router.register('recruitment-separateposition', views.RecruitmentSeparatePositionView, 'recruitment_separateposition')
router.register('recruitment-position', views.RecruitmentPositionView, 'recruitment_position')
router.register('recruitment-position-for-applicant', views.RecruitmentPositionForApplicantView, 'recruitment_position_for_applicant')
router.register('recruitment-applications-for-applicant', views.RecruitmentApplicationForApplicantView, 'recruitment_applications_for_applicant')
Expand Down
Loading

0 comments on commit 493e24e

Please sign in to comment.