diff --git a/.gitignore b/.gitignore index 81a04ce..da642b3 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ secrets/ rest_framework/ django_extensions/ admin/ +.env # Created by https://www.toptal.com/developers/gitignore/api/django # Edit at https://www.toptal.com/developers/gitignore?templates=django diff --git a/backend/api/migrate_skills_to_long.py b/backend/api/migrate_skills_to_long.py new file mode 100644 index 0000000..8444c52 --- /dev/null +++ b/backend/api/migrate_skills_to_long.py @@ -0,0 +1,129 @@ +from api.models import Candidates, Jobs, SoftSkills, Skills +import json + + +def migrate_candidates_soft_skills_to_long(): + candidates = Candidates.objects.all().values("candidate_id", "soft_skills") + + set_soft_skills: set = set() + + dict_candidates_soft_skills: dict = {} + dict_soft_skills: dict = {} + + for cand in candidates.iterator(): + cand_soft_skills: list = json.loads(cand["soft_skills"].replace("'", '"')) + dict_candidates_soft_skills[cand["candidate_id"]] = cand_soft_skills + + set_soft_skills.update(cand_soft_skills) + + soft_skills = SoftSkills.objects.filter(soft_skill_name__in=set_soft_skills) + + dict_soft_skills = { + ss.soft_skill_name: ss.soft_skill_id for ss in soft_skills.iterator() + } + + for cand, cand_skills in dict_candidates_soft_skills.items(): + for i in cand_skills: + if i in dict_soft_skills: + print(dict_soft_skills[i]) + + dict_candidates_soft_skills[cand] = [ + dict_soft_skills[i] for i in cand_skills if i in dict_soft_skills + ] + + for id, items in dict_candidates_soft_skills.items(): + if len(items) > 0: + Candidates.objects.get(pk=id).soft_skill_test_matching.set(items) + + +def migrate_candidates_hard_skills_to_long(): + candidates = Candidates.objects.all().values("candidate_id", "hard_skills") + + set_soft_skills: set = set() + + dict_candidates_soft_skills: dict = {} + dict_soft_skills: dict = {} + + for cand in candidates.iterator(): + if type(cand["hard_skills"]) == str: + cand_soft_skills: list = cand["hard_skills"].split(", ") + dict_candidates_soft_skills[cand["candidate_id"]] = cand_soft_skills + + set_soft_skills.update(cand_soft_skills) + + soft_skills = Skills.objects.filter(skill_name__in=set_soft_skills) + + dict_soft_skills = {ss.skill_name: ss.skill_id for ss in soft_skills.iterator()} + + for cand, cand_skills in dict_candidates_soft_skills.items(): + # for i in cand_skills: + # if i in dict_soft_skills: + # print(dict_soft_skills[i]) + + dict_candidates_soft_skills[cand] = [ + dict_soft_skills[i] for i in cand_skills if i in dict_soft_skills + ] + + for id, items in dict_candidates_soft_skills.items(): + if len(items) > 0: + Candidates.objects.get(pk=id).hard_skill_test_matching.set(items) + + +def migrate_job_hard_skills_to_long(): + candidates = Jobs.objects.all().values("job_id", "hard_skills") + + set_soft_skills: set = set() + + dict_candidates_soft_skills: dict = {} + dict_soft_skills: dict = {} + + for cand in candidates.iterator(): + if type(cand["hard_skills"]) == str: + cand_soft_skills: list = json.loads(cand["hard_skills"]) + dict_candidates_soft_skills[cand["job_id"]] = cand_soft_skills + + set_soft_skills.update(cand_soft_skills) + + soft_skills = Skills.objects.filter(skill_name__in=set_soft_skills) + + dict_soft_skills = {ss.skill_name: ss.skill_id for ss in soft_skills.iterator()} + + for cand, cand_skills in dict_candidates_soft_skills.items(): + dict_candidates_soft_skills[cand] = [ + dict_soft_skills[i] for i in cand_skills if i in dict_soft_skills + ] + + for id, items in dict_candidates_soft_skills.items(): + if len(items) > 0: + Jobs.objects.get(pk=id).hard_skill_test_matching.set(items) + + +def migrate_job_soft_skills_to_long(): + candidates = Jobs.objects.all().values("job_id", "soft_skills") + + set_soft_skills: set = set() + + dict_candidates_soft_skills: dict = {} + dict_soft_skills: dict = {} + + for cand in candidates.iterator(): + if type(cand["soft_skills"]) == str: + cand_soft_skills: list = json.loads(cand["soft_skills"]) + dict_candidates_soft_skills[cand["job_id"]] = cand_soft_skills + + set_soft_skills.update(cand_soft_skills) + + soft_skills = SoftSkills.objects.filter(soft_skill_name__in=set_soft_skills) + + dict_soft_skills = { + ss.soft_skill_name: ss.soft_skill_id for ss in soft_skills.iterator() + } + + for cand, cand_skills in dict_candidates_soft_skills.items(): + dict_candidates_soft_skills[cand] = [ + dict_soft_skills[i] for i in cand_skills if i in dict_soft_skills + ] + + for id, items in dict_candidates_soft_skills.items(): + if len(items) > 0: + Jobs.objects.get(pk=id).soft_skill_test_matching.set(items) diff --git a/backend/api/migrations/0029_candidates_hard_skill_test_matching_and_more.py b/backend/api/migrations/0029_candidates_hard_skill_test_matching_and_more.py new file mode 100644 index 0000000..7a96956 --- /dev/null +++ b/backend/api/migrations/0029_candidates_hard_skill_test_matching_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 4.2.5 on 2023-10-23 08:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0028_alter_jobs_company_alter_jobs_hard_skills_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='candidates', + name='hard_skill_test_matching', + field=models.ManyToManyField(to='api.skills'), + ), + migrations.AddField( + model_name='candidates', + name='soft_skill_test_matching', + field=models.ManyToManyField(to='api.softskills'), + ), + migrations.AddField( + model_name='jobs', + name='hard_skill_test_matching', + field=models.ManyToManyField(to='api.skills'), + ), + migrations.AddField( + model_name='jobs', + name='soft_skill_test_matching', + field=models.ManyToManyField(to='api.softskills'), + ), + ] diff --git a/backend/api/models.py b/backend/api/models.py index 98b28ad..9eed83f 100644 --- a/backend/api/models.py +++ b/backend/api/models.py @@ -1,3 +1,4 @@ +from typing import Iterator from django.db import models from api.auth_models import AuthUsers import os @@ -6,6 +7,28 @@ DEFAULT_MAX_LENGTH = 255 +class Skills(models.Model): + skill_id = models.AutoField(primary_key=True) + skill_name = models.CharField(max_length=DEFAULT_MAX_LENGTH) + + class Meta: + db_table = "skills" + + def __str__(self): + return self.skill_name + + +class SoftSkills(models.Model): + soft_skill_id = models.AutoField(primary_key=True) + soft_skill_name = models.CharField(max_length=DEFAULT_MAX_LENGTH) + + def __str__(self): + return self.soft_skill_name + + class Meta: + db_table = "soft_skills" + + class AssociationUsers(models.Model): association_user_id = models.AutoField(primary_key=True) supabase_authenticaiton_uuid = models.UUIDField() @@ -109,6 +132,39 @@ class Candidates(models.Model): last_update = models.DateTimeField(auto_now=True, null=True) created_at = models.DateTimeField(auto_now_add=True, null=True) + soft_skill_test_matching = models.ManyToManyField(SoftSkills) + hard_skill_test_matching = models.ManyToManyField(Skills) + + def get_match_percentage( + self, list_skills_id: Iterator, soft_or_hard_skill: str + ) -> int: + """ + Calculate the percentage of matching skills between the input list of skills and the skills in the database. + + Args: + list_skills_id (Iterator): An iterator of integers representing the IDs of the skills to be matched. + soft_or_hard_skill (str): A string indicating whether to match soft or hard skills. + + Returns: + int: The percentage of matching skills between the input list of skills and the skills in the database. + """ + if soft_or_hard_skill == "soft": + skills = self.soft_skill_test_matching.values_list( + "soft_skill_id", flat=True + ) + elif soft_or_hard_skill == "hard": + skills = self.hard_skill_test_matching.values_list("skill_id", flat=True) + else: + raise ValueError + + skills = list(skills) + + return ( + len(set(list_skills_id).intersection(set(skills))) / len(list_skills_id) + if len(list_skills_id) > 0 + else 0 + ) * 100 + def __str__(self): return f"{self.candidate_id} - {self.first_name} {self.last_name}" @@ -237,13 +293,22 @@ class Jobs(models.Model): soft_skills = models.TextField(blank=True, null=True) hard_skills = models.TextField(blank=True, null=True) languages = models.TextField(blank=True, null=True) - open = models.BooleanField(default=True, blank=True, null=True) + soft_skill_test_matching = models.ManyToManyField(SoftSkills) + hard_skill_test_matching = models.ManyToManyField(Skills) + last_day_to_apply = models.DateField(blank=True, null=True) closed_at = models.DateTimeField(blank=True, null=True) created_at = models.DateTimeField(auto_now_add=True, blank=True, null=True) + @property + def matches(self): + matched_soft_candidates = Candidates.objects.filter( + soft_skill_test_matching__in=self.soft_skill_test_matching.iterator() + ) + return matched_soft_candidates + class Meta: db_table = "jobs" @@ -418,22 +483,6 @@ class Meta: db_table = "personality_candidates" -class Skills(models.Model): - skill_id = models.AutoField(primary_key=True) - skill_name = models.CharField(max_length=DEFAULT_MAX_LENGTH) - - class Meta: - db_table = "skills" - - -class SoftSkills(models.Model): - soft_skill_id = models.AutoField(primary_key=True) - soft_skill_name = models.CharField(max_length=DEFAULT_MAX_LENGTH) - - class Meta: - db_table = "soft_skills" - - class Status(models.Model): status_id = models.AutoField(primary_key=True) status_descrption = models.CharField(max_length=DEFAULT_MAX_LENGTH) diff --git a/backend/api/serializers.py b/backend/api/serializers.py index 024cded..cbadc1a 100644 --- a/backend/api/serializers.py +++ b/backend/api/serializers.py @@ -1,5 +1,6 @@ from django.contrib.auth.models import User + from api.models import * from rest_framework import serializers @@ -32,10 +33,22 @@ class Meta: many = True -class CandidatesSerializer(serializers.HyperlinkedModelSerializer): +class CandidatesSerializer(serializers.ModelSerializer): + hard_skills = serializers.StringRelatedField( + many=True, + source="hard_skill_test_matching", + ) + + soft_skills = serializers.StringRelatedField( + many=True, source="soft_skill_test_matching" + ) + class Meta: model = Candidates - fields = "__all__" + exclude = ( + "hard_skill_test_matching", + "soft_skill_test_matching", + ) many = True @@ -51,13 +64,6 @@ class Meta: fields = "__all__" -class CandidatesSerializer(serializers.HyperlinkedModelSerializer): - class Meta: - model = Candidates - fields = "__all__" - many = True - - class InvitationSerializer(serializers.HyperlinkedModelSerializer): association = AssociationsSerializer() @@ -122,10 +128,62 @@ class Meta: many = True -class JobsSerializer(serializers.HyperlinkedModelSerializer): +class CandidateMatchPercentageSerializer(serializers.BaseSerializer): + def to_representation(self, instance): + job = Jobs.objects.get(pk=210) + job_soft_skills = list( + job.soft_skill_test_matching.values_list("soft_skill_id", flat=True) + ) + job_hard_skills = list( + job.hard_skill_test_matching.values_list("skill_id", flat=True) + ) + + return [ + { + "id": candidate.candidate_id, + "name": candidate.preferred_name, + "soft_skills": candidate.soft_skill_test_matching.values_list( + "soft_skill_id", flat=True + ), + "soft_skills_match_percentage": candidate.get_match_percentage( + job_soft_skills, "soft" + ), + "hard_skills": candidate.hard_skill_test_matching.values_list( + "skill_id", flat=True + ), + "hard_skills_match_percentage": candidate.get_match_percentage( + job_soft_skills, "hard" + ), + } + for candidate in instance + ] + + def __init__(self, *args, **kwargs): + self.soft_skills = kwargs["context"].get("soft_skills") + super().__init__(*args, **kwargs) + + +class JobsSerializer(serializers.ModelSerializer): + # hard_skills = serializers.StringRelatedField( + # many=True, + # source="hard_skill_test_matching", + # ) + + soft_skills = serializers.PrimaryKeyRelatedField( + read_only=True, + many=True, + source="soft_skill_test_matching", + ) + + matches = CandidateMatchPercentageSerializer(context={"job_id": 1}) + class Meta: model = Jobs - fields = "__all__" + fields = ["matches", "soft_skills"] + # exclude = ( + # "soft_skill_test_matching", + # "hard_skill_test_matching", + # ) many = True