Skip to content

Commit

Permalink
Merge branch 'master' into robin/update-role-model
Browse files Browse the repository at this point in the history
  • Loading branch information
robines authored Sep 24, 2024
2 parents 6d9d28f + 2b2df97 commit fa7bb3c
Show file tree
Hide file tree
Showing 30 changed files with 1,190 additions and 175 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"
),
),
]
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)
8 changes: 7 additions & 1 deletion backend/samfundet/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,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 +613,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 +713,8 @@ class RecruitmentSeparatePositionSerializer(CustomBaseSerializer):
class Meta:
model = RecruitmentSeparatePosition
fields = [
'id',
'recruitment',
'name_nb',
'name_en',
'description_nb',
Expand All @@ -731,7 +737,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
1 change: 1 addition & 0 deletions backend/samfundet/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
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
9 changes: 9 additions & 0 deletions backend/samfundet/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
RecruitmentPositionSerializer,
RecruitmentStatisticsSerializer,
RecruitmentForRecruiterSerializer,
RecruitmentSeparatePositionSerializer,
RecruitmentApplicationForGangSerializer,
RecruitmentUpdateUserPrioritySerializer,
RecruitmentPositionForApplicantSerializer,
Expand Down Expand Up @@ -129,6 +130,7 @@
RecruitmentPosition,
RecruitmentStatistics,
RecruitmentApplication,
RecruitmentSeparatePosition,
RecruitmentInterviewAvailability,
)
from .models.model_choices import RecruitmentStatusChoices, RecruitmentPriorityChoices
Expand Down Expand Up @@ -641,6 +643,13 @@ class RecruitmentPositionForApplicantView(ModelViewSet):
queryset = RecruitmentPosition.objects.all()


@method_decorator(ensure_csrf_cookie, 'dispatch')
class RecruitmentSeparatePositionView(ModelViewSet):
permission_classes = [IsAuthenticated]
serializer_class = RecruitmentSeparatePositionSerializer
queryset = RecruitmentSeparatePosition.objects.all()


class RecruitmentApplicationView(ModelViewSet):
permission_classes = [AllowAny]
serializer_class = RecruitmentApplicationForGangSerializer
Expand Down
84 changes: 78 additions & 6 deletions docs/technical/pipeline.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,89 @@
# Pipelines

Is your PR not passing the pipeline checks? Look no further.
Below you will find a collection of commands thats run by the pipeline and will allow you to check and fix issues locally.
At the bottom of each section is a link to a comprehensive collection of all commands, should you discover a missing command you find it there. (In that case please add the command to this doc)

## Frontend

_No docs yet_
_Install yarn_

```
yarn install
```

_Run Biome_

```
yarn run biome:ci
```

_fix biome_

```
yarn run biome:fix
```

_Run Stylelint_

```
yarn run stylelint:check
```

_Run typescript compiler check_

```
yarn run tsc:check
```

Didnt find what you were looking for? See all backend commands [here](../../frontend/package.json)

## Backend

_No docs yet_
```
poetry install
```

_Run Ruff_

```
poetry run ruff check
```

_Run Ruff fix_

```
poetry run ruff check --fix
```

_Verify migrations_

```
poetry run python manage.py makemigrations --check --dry-run --noinput --verbosity 2
```

_Apply migrations_

```
poetry run python manage.py migrate
```

_Run (Py)tests_

```
poetry run pytest
```

_Run mypy_

### Yapf
```
poetry run mypy --config-file mypy.ini .
```

Python formatter
_Run seed_

### mypy
```
poetry run python manage.py seed
```

Static typing
Didnt find what you were looking for? See all frontend commands [here](../../backend/aliases.sh)
2 changes: 0 additions & 2 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
"build-storybook-dev": "storybook build",
"cypress:open": "cypress open",
"cypress:run": "yarn run cypress run",

"biome//": "echo Biome is configured for entire repo.",
"biome:check": "biome check",
"biome:ci": "biome ci",
Expand All @@ -34,7 +33,6 @@
"lint:fix-unsafe": "biome lint --write --unsafe",
"format:check": "biome format",
"format:fix": "biome format --write",

"stylelint:check": "stylelint --config .stylelintrc src/**/*.{css,scss}",
"tsc:check": "tsc",
"tsc:watch": "tsc --watch",
Expand Down
Loading

0 comments on commit fa7bb3c

Please sign in to comment.