diff --git a/NorahUniversity/__pycache__/settings.cpython-312.pyc b/NorahUniversity/__pycache__/settings.cpython-312.pyc index 2d7be7f..b40e327 100644 Binary files a/NorahUniversity/__pycache__/settings.cpython-312.pyc and b/NorahUniversity/__pycache__/settings.cpython-312.pyc differ diff --git a/NorahUniversity/settings.py b/NorahUniversity/settings.py index d741e46..e6c1980 100644 --- a/NorahUniversity/settings.py +++ b/NorahUniversity/settings.py @@ -155,19 +155,27 @@ DATABASES = { +# AUTH_PASSWORD_VALIDATORS = [ +# { +# 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', +# }, +# { +# 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', +# }, +# { +# 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', +# }, +# { +# 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', +# }, +# ] + +# settings.py + AUTH_PASSWORD_VALIDATORS = [ - { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', - }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, - { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', - }, ] diff --git a/recruitment/__pycache__/models.cpython-312.pyc b/recruitment/__pycache__/models.cpython-312.pyc index 4609063..ae64c81 100644 Binary files a/recruitment/__pycache__/models.cpython-312.pyc and b/recruitment/__pycache__/models.cpython-312.pyc differ diff --git a/recruitment/__pycache__/signals.cpython-312.pyc b/recruitment/__pycache__/signals.cpython-312.pyc index 5dbffd2..6a63901 100644 Binary files a/recruitment/__pycache__/signals.cpython-312.pyc and b/recruitment/__pycache__/signals.cpython-312.pyc differ diff --git a/recruitment/__pycache__/urls.cpython-312.pyc b/recruitment/__pycache__/urls.cpython-312.pyc index 00b718a..fe68685 100644 Binary files a/recruitment/__pycache__/urls.cpython-312.pyc and b/recruitment/__pycache__/urls.cpython-312.pyc differ diff --git a/recruitment/management/commands/seed.py b/recruitment/management/commands/seed.py index 7989456..da15698 100644 --- a/recruitment/management/commands/seed.py +++ b/recruitment/management/commands/seed.py @@ -1,3 +1,7 @@ +from pathlib import Path +from rich import print +from django.conf import settings +import os import uuid import random from datetime import date, timedelta @@ -39,9 +43,9 @@ class Command(BaseCommand): self.stdout.write(f"Preparing to create {jobs_count} jobs and {candidates_count} candidates.") # 1. Clear existing data (Optional, but useful for clean seeding) - Candidate.objects.all().delete() JobPosting.objects.all().delete() - Source.objects.all().delete() + FormTemplate.objects.all().delete() + Candidate.objects.all().delete() self.stdout.write(self.style.WARNING("Existing JobPostings and Candidates cleared.")) # 2. Create Foreign Key dependency: Source @@ -74,8 +78,7 @@ class Command(BaseCommand): # Random dates start_date = fake.date_object() - deadline_date = start_date + timedelta(days=random.randint(14, 60)) - joining_date = deadline_date + timedelta(days=random.randint(30, 90)) + deadline_date = start_date + timedelta(days=random.randint(14, 60)) # Use Faker's HTML generation for CKEditor5 fields description_html = f"
{fake.paragraph(nb_sentences=3, variable_nb_sentences=True)}
" for _ in range(3)) @@ -88,26 +91,11 @@ class Command(BaseCommand): "department": department, "job_type": job_type, "workplace_type": random.choice(WORKPLACE_TYPES), - "location_city": fake.city(), - "location_state": fake.state_abbr(), "location_country": "Saudia Arabia", "description": description_html, "qualifications": qualifications_html, - "salary_range": salary_range, - "benefits": benefits_html, - "application_url": fake.url(), - "application_start_date": start_date, "application_deadline": deadline_date, - "application_instructions": instructions_html, - "created_by": "Faker Script", "status": random.choice(STATUS_CHOICES), - "hash_tags": f"#{department.lower().replace(' ', '')},#jobopening,#{fake.word()}", - "position_number": f"{department[:3].upper()}{random.randint(100, 999)}", - "reporting_to": random.choice(REPORTING_TO), - "joining_date": joining_date, - "open_positions": random.randint(1, 5), - "source": default_source, - "published_at": timezone.now() if random.random() < 0.7 else None, } job = JobPosting.objects.create( @@ -123,28 +111,33 @@ class Command(BaseCommand): for i in range(candidates_count): # Link candidate to a random job target_job = random.choice(created_jobs) - + print(target_job) first_name = fake.first_name() last_name = fake.last_name() - + path = os.path.join(settings.BASE_DIR,'media/resumes/') + + # path = Path('media/resumes/') # <-- CORRECT + file = random.choice(os.listdir(path)) + print(file) + # file = os.path.abspath(file) candidate_data = { "first_name": first_name, "last_name": last_name, # Create a plausible email based on name "email": f"{first_name.lower()}.{last_name.lower()}@{fake.domain_name()}", - "phone": fake.phone_number(), + "phone": "0566987458", "address": fake.address(), - # Placeholder resume path - 'match_score': random.randint(0, 100), - "resume": f"resumes/{last_name.lower()}_{target_job.internal_job_id}_{fake.file_name(extension='pdf')}", + # Placeholder resume path + "resume": 'resumes/'+ file, "job": target_job, } + print(candidate_data) Candidate.objects.create(**candidate_data) self.stdout.write(self.style.NOTICE( f'Created Candidate {i+1}/{candidates_count}: {first_name} for {target_job.title[:30]}...' )) - + print("done") else: self.stdout.write(self.style.WARNING("No jobs created, skipping candidate generation.")) diff --git a/recruitment/migrations/0001_initial.py b/recruitment/migrations/0001_initial.py index fcbfe6e..d6c2da6 100644 --- a/recruitment/migrations/0001_initial.py +++ b/recruitment/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.2.7 on 2025-10-22 16:33 +# Generated by Django 5.2.7 on 2025-10-23 14:08 import django.core.validators import django.db.models.deletion @@ -221,6 +221,7 @@ class Migration(migrations.Migration): migrations.CreateModel( name='JobPosting', fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), ('slug', django_extensions.db.fields.RandomCharField(blank=True, editable=False, length=8, unique=True, verbose_name='Slug')), @@ -238,7 +239,7 @@ class Migration(migrations.Migration): ('application_url', models.URLField(blank=True, help_text='URL where candidates apply', null=True, validators=[django.core.validators.URLValidator()])), ('application_deadline', models.DateField(db_index=True)), ('application_instructions', django_ckeditor_5.fields.CKEditor5Field(blank=True, null=True)), - ('internal_job_id', models.CharField(editable=False, max_length=50, primary_key=True, serialize=False)), + ('internal_job_id', models.CharField(editable=False, max_length=50)), ('created_by', models.CharField(blank=True, help_text='Name of person who created this job', max_length=100)), ('status', models.CharField(choices=[('DRAFT', 'Draft'), ('ACTIVE', 'Active'), ('CLOSED', 'Closed'), ('CANCELLED', 'Cancelled'), ('ARCHIVED', 'Archived')], db_index=True, default='DRAFT', max_length=20)), ('hash_tags', models.CharField(blank=True, help_text='Comma-separated hashtags for linkedin post like #hiring,#jobopening', max_length=200, validators=[recruitment.validators.validate_hash_tags])), diff --git a/recruitment/models.py b/recruitment/models.py index abae095..720cb71 100644 --- a/recruitment/models.py +++ b/recruitment/models.py @@ -92,7 +92,7 @@ class JobPosting(Base): ) # Internal Tracking - internal_job_id = models.CharField(max_length=50, primary_key=True, editable=False) + internal_job_id = models.CharField(max_length=50, editable=False) created_by = models.CharField( max_length=100, blank=True, help_text="Name of person who created this job" ) @@ -193,26 +193,29 @@ class JobPosting(Base): return self.source.name if self.source else "System" def save(self, *args, **kwargs): + # from django.db import transaction + # Generate unique internal job ID if not exists - if not self.internal_job_id: - prefix = "KAAUH" - year = timezone.now().year - # Get next sequential number - last_job = ( - JobPosting.objects.filter( - internal_job_id__startswith=f"{prefix}-{year}-" - ) - .order_by("internal_job_id") - .last() - ) + # with transaction.atomic(): + # if not self.internal_job_id: + # prefix = "KAAUH" + # year = timezone.now().year + # # Get next sequential number + # last_job = ( + # JobPosting.objects.select_for_update().filter( + # internal_job_id__startswith=f"{prefix}-{year}-" + # ) + # .order_by("internal_job_id") + # .last() + # ) - if last_job: - last_num = int(last_job.internal_job_id.split("-")[-1]) - next_num = last_num + 1 - else: - next_num = 1 + # if last_job: + # last_num = int(last_job.internal_job_id.split("-")[-1]) + # next_num = last_num + 1 + # else: + # next_num = 1 - self.internal_job_id = f"{prefix}-{year}-{next_num:06d}" + # self.internal_job_id = f"{prefix}-{year}-{next_num:06d}" super().save(*args, **kwargs) diff --git a/recruitment/signals.py b/recruitment/signals.py index 174cf35..501a944 100644 --- a/recruitment/signals.py +++ b/recruitment/signals.py @@ -13,7 +13,7 @@ logger = logging.getLogger(__name__) @receiver(post_save, sender=JobPosting) def format_job(sender, instance, created, **kwargs): if created: - FormTemplate.objects.create(job=instance, is_active=True, name=instance.title) + # FormTemplate.objects.create(job=instance, is_active=False, name=instance.title) async_task( 'recruitment.tasks.format_job_description', instance.pk, diff --git a/recruitment/tasks.py b/recruitment/tasks.py index bf024e9..31ebc8d 100644 --- a/recruitment/tasks.py +++ b/recruitment/tasks.py @@ -290,7 +290,7 @@ def handle_reume_parsing_and_scoring(pk): "strengths": "Brief summary of strengths", "weaknesses": "Brief summary of weaknesses", "years_of_experience": "Total years of experience (float, e.g., 6.5)", - "criteria_checklist": {{ "Python": "Met", "AWS": "Not Mentioned"}}, + "criteria_checklist": List of job requirements if any {{ "Python": "Met", "AWS": "Not Met"}} only output the criteria_checklist in one of ('Met','Not Met') don't output any extra text, "category": "Most fitting professional field (e.g., Data Science), only output the category name and no other text example ('Software Development', 'correct') , ('Software Development and devops','wrong') ('Software Development / Backend Development','wrong')", "most_recent_job_title": "Candidate's most recent job title", "recommendation": "Detailed hiring recommendation narrative", diff --git a/resumes/AltaCV_Template.pdf b/resumes/AltaCV_Template.pdf new file mode 100644 index 0000000..34b6f6d Binary files /dev/null and b/resumes/AltaCV_Template.pdf differ diff --git a/resumes/Backend Developer Roadmap_ What is Backend Development.pdf b/resumes/Backend Developer Roadmap_ What is Backend Development.pdf new file mode 100644 index 0000000..b073af9 Binary files /dev/null and b/resumes/Backend Developer Roadmap_ What is Backend Development.pdf differ diff --git a/resumes/Balance Sheet.pdf b/resumes/Balance Sheet.pdf new file mode 100644 index 0000000..231ae5d Binary files /dev/null and b/resumes/Balance Sheet.pdf differ diff --git a/resumes/Cash Flow Statement1.pdf b/resumes/Cash Flow Statement1.pdf new file mode 100644 index 0000000..0566095 Binary files /dev/null and b/resumes/Cash Flow Statement1.pdf differ diff --git a/resumes/DA_2026_Syllabus.pdf b/resumes/DA_2026_Syllabus.pdf new file mode 100644 index 0000000..7ccea32 Binary files /dev/null and b/resumes/DA_2026_Syllabus.pdf differ diff --git a/resumes/Django Documentation.pdf b/resumes/Django Documentation.pdf new file mode 100644 index 0000000..5e68377 --- /dev/null +++ b/resumes/Django Documentation.pdf @@ -0,0 +1,237468 @@ +%PDF-1.7 +% +1 0 obj +<> +endobj +2 0 obj +<> +endobj +3 0 obj +<> +endobj +4 0 obj +<> +endobj +5 0 obj +<>stream +xڅRN0}yH( ѝH(pU>v@Jwfǁ?E{qOKcV+pas> vg]3\:T "*fyyAwDUD;.XhJbfGxʖ)n{{r4D|z T4($͌I4kja5tl8`QB