new chnages

This commit is contained in:
Faheed 2025-10-23 18:49:59 +03:00
parent 6fefee7f43
commit 9cd63d3897
67 changed files with 237705 additions and 132 deletions

View File

@ -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',
},
]

View File

@ -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
@ -75,7 +79,6 @@ 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))
# Use Faker's HTML generation for CKEditor5 fields
description_html = f"<h1>{title} Role</h1>" + "".join(f"<p>{fake.paragraph(nb_sentences=3, variable_nb_sentences=True)}</p>" 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')}",
"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."))

View File

@ -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])),

View File

@ -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)

View File

@ -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,

View File

@ -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",

BIN
resumes/AltaCV_Template.pdf Normal file

Binary file not shown.

BIN
resumes/Balance Sheet.pdf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
resumes/FaheeedResume.docx Normal file

Binary file not shown.

BIN
resumes/FaheeedResume.pdf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
resumes/cv-for-freshers.pdf Normal file

Binary file not shown.

BIN
resumes/cv-template.docx Normal file

Binary file not shown.

BIN
resumes/cv.pdf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
resumes/hassan-razas-cv.pdf Normal file

Binary file not shown.

Binary file not shown.

BIN
resumes/jitendra.pdf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
resumes/npu-cv.pdf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -111,12 +111,31 @@
{# ------------------- ADD EMAIL FORM ------------------- #}
<h5 class="fw-bold mb-3">{% translate "Add Email Address" %}</h5>
<form method="post" action="{% url 'account_email' %}">
<form method="post" action="{% url 'account_email' %}">
{% csrf_token %}
{# 1. Explicitly render non-field errors first #}
{% if form.non_field_errors %}
<div class="alert alert-danger" role="alert">
{% for error in form.non_field_errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
{# 2. Render the fields using crispy #}
{{ form|crispy }}
{# 3. If there are any global errors (not common here, but safe to include) #}
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }} mt-3">{{ message }}</div>
{% endfor %}
{% endif %}
{# Teal/Dark Green button consistent with "Save Changes" #}
<button class="btn btn-success mt-3" type="submit" style="background-color: #008080; border-color: #008080;">{% translate "Add Email" %}</button>
</form>
</form>
</div>
</div>
</div>

View File

@ -2,6 +2,8 @@
{% load static %}
{% autoescape off %}
{# Use the built-in context variable 'password_reset_url' that allauth provides. #}
<div style="font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: auto; border: 1px solid #ddd; border-radius: 8px; overflow: hidden;">
<div style="background-color: #00636e; padding: 20px; color: white; text-align: center;">

View File

@ -1,5 +1,5 @@
{% load static i18n %}
<!DOCTYPE html>
{% load crispy_forms_tags %} <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
@ -82,52 +82,8 @@
<form method="post" action=".">
{% csrf_token %}
{# Non-Field Errors (General errors like tokens or passwords not matching) #}
{% if form.non_field_errors %}
<div class="alert alert-danger" role="alert">
{% for error in form.non_field_errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
{# Password 1 Field #}
<div class="mb-3">
<label for="{{ form.password.id_for_label }}" class="form-label fw-semibold">{% trans "New Password" %} *</label>
{# **CRITICAL FIX:** Iterate over the errors to display them correctly #}
{% if form.password.errors %}
<div class="alert alert-danger p-2 small">
{% for error in form.password.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
<input type="password" name="{{ form.password.name }}" id="{{ form.password.id_for_label }}"
class="form-control" placeholder="{% trans 'Enter new password' %}" required autofocus>
</div>
{# Password 2 Field #}
<div class="mb-3">
<label for="{{ form.password2.id_for_label }}" class="form-label fw-semibold">{% trans "Confirm New Password" %} *</label>
{# **CRITICAL FIX:** Iterate over the errors to display them correctly #}
{% if form.password2.errors %}
<div class="alert alert-danger p-2 small">
{% for error in form.password2.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
<input type="password" name="{{ form.password2.name }}" id="{{ form.password2.id_for_label }}"
class="form-control" placeholder="{% trans 'Confirm new password' %}" required>
</div>
{# Hidden fields MUST be present for the POST request to be valid #}
{{ form.uid }}
{{ form.token }}
{# RENDER THE FORM USING CRISPY FORMS #}
{{ form|crispy }}
{# Submit Button #}
<button type="submit" class="btn btn-primary w-100 mt-4">

View File

@ -146,9 +146,12 @@
<div class="container-fluid py-4">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'job_detail' submission.template.job.slug %}">Job Detail</a></li>
<li class="breadcrumb-item"><a href="{% url 'form_builder' submission.template.pk%}">Form Template</a></li>
<li class="breadcrumb-item active" aria-current="page">Submission Details</li>
<li class="breadcrumb-item text-decorat"><a href="{% url 'job_detail' submission.template.job.slug %}" class="text-secondary">Job Detail</a></li>
<li class="breadcrumb-item"><a href="{% url 'form_builder' submission.template.pk%}" class="text-secondary">Form Template</a></li>
<li class="breadcrumb-item active" aria-current="page" style="
color: #F43B5E; /* Rosy Accent Color */
font-weight: 600;
">Submission Details</li>
</ol>
</nav>
<div class="row">

View File

@ -185,9 +185,12 @@
<div class="container py-4">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'dashboard' %}">{% trans "Dashboard" %}</a></li>
<li class="breadcrumb-item"><a href="{% url 'form_templates_list' %}">{% trans "Form Templates" %}</a></li>
<li class="breadcrumb-item active">{% trans "Submissions" %}</li>
<li class="breadcrumb-item"><a href="{% url 'dashboard' %}" class="text-secondary">{% trans "Dashboard" %}</a></li>
<li class="breadcrumb-item"><a href="{% url 'form_templates_list' %}" class="text-secondary">{% trans "Form Templates" %}</a></li>
<li class="breadcrumb-item active" style="
color: #F43B5E; /* Rosy Accent Color */
font-weight: 600;
">{% trans "Submissions" %}</li>
</ol>
</nav>

View File

@ -364,6 +364,7 @@
</style>
</head>
<body>
<div class="resume-container">
<div class="header">
<div class="header-content">

View File

@ -152,7 +152,10 @@
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'dashboard' %}" class="text-secondary">Home</a></li>
<li class="breadcrumb-item"><a href="{% url 'job_list' %}" class="text-secondary">Jobs</a></li>
<li class="breadcrumb-item active" aria-current="page">Job Detail</li>
<li class="breadcrumb-item active" aria-current="page" style="
color: #F43B5E; /* Rosy Accent Color */
font-weight: 600;
">Job Detail</li>
</ol>
</nav>
<div class="row g-4">

View File

@ -186,7 +186,10 @@
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'dashboard' %}" class="text-secondary">Home</a></li>
<li class="breadcrumb-item"><a href="{% url 'job_detail' candidate.job.slug %}" class="text-secondary">Job:({{candidate.job.title}})</a></li>
<li class="breadcrumb-item active" aria-current="page" class="text-secondary">Applicant Detail</li>
<li class="breadcrumb-item active" aria-current="page" class="text-secondary" style="
color: #F43B5E; /* Rosy Accent Color */
font-weight: 600;
">Applicant Detail</li>
</ol>
</nav>

View File

@ -1,4 +1,5 @@
<!DOCTYPE html>
{% load i18n %}
<html lang="en">
<head>
<meta charset="UTF-8">
@ -517,7 +518,10 @@
</style>
</head>
<body class="bg-kaauh-light-bg font-sans">
<div class="container container-fluid flex-grow-1" style="max-width: 1600px; margin: 0 auto;">
{% include 'recruitment/partials/ai_overview_breadcromb.html' %}
<!-- Header Section -->
<header class="header-box">
<div class="header-info">
@ -716,21 +720,9 @@
</div>
{% endif %}
<div style="margin-bottom: 1rem;">
<div class="analysis-metric" style="margin-bottom: 0.5rem; border-bottom: none;">
<span class="metric-title">Match Score</span>
<span class="metric-value">{{ candidate.analysis_data.match_score|default:0 }}/100</span>
</div>
<div class="progress-container">
<div class="progress-bar progress-bar-animated"
style="width: {{ candidate.analysis_data.match_score|default:0 }}%; background-color:
{% if candidate.analysis_data.match_score|default:0 < 50 %}var(--score-red){% elif candidate.analysis_data.match_score|default:0 < 75 %}var(--score-yellow){% else %}var(--score-green){% endif %}">
</div>
</div>
</div>
{% if candidate.analysis_data.red_flags %}
<div class="narrative-box red-flag-box">
<div class="narrative-box">
<h3 class="flag-title red"><i class="fas fa-flag"></i>Red Flags</h3>
<!-- scoring_data.red_flags -->
<p class="narrative-text">{{ candidate.analysis_data.red_flags|join:". "|default:"None." }}</p>

View File

@ -0,0 +1,118 @@
{% load i18n %}
{% with request.resolver_match as resolved %}
{% if 'resume-template' in resolved.route and resolved.kwargs.slug == candidate.slug %}
<nav aria-label="breadcrumb" style="font-family: 'Inter', sans-serif; margin-bottom:1.5rem;">
<ol style="
display: flex;
flex-wrap: wrap;
padding: 0;
margin: 0;
list-style: none;
font-size: 0.875rem; /* text-sm */
">
<li style="display: flex; align-items: center;">
<a
href="{% url 'dashboard' %}"
style="
color: #6c757d; /* text-secondary/gray */
text-decoration: none;
transition: color 0.15s ease-in-out;
"
onmouseover="this.style.color='#000000';"
onmouseout="this.style.color='#6c757d';"
>
{% translate 'Home' %}
</a>
</li>
<li style="
display: flex;
align-items: center;
padding-left: 0.5rem;
padding-right: 0.5rem;
">
<span style="color: #6c757d; opacity: 0.75;" aria-hidden="true">/</span>
</li>
<li style="display: flex; align-items: center;">
<a
href="{% url 'job_list' %}"
style="
color: #6c757d; /* text-secondary/gray */
text-decoration: none;
transition: color 0.15s ease-in-out;
"
onmouseover="this.style.color='#000000';"
onmouseout="this.style.color='#6c757d';"
>
{% trans 'Jobs' %}
</a>
</li>
<li style="
display: flex;
align-items: center;
padding-left: 0.5rem;
padding-right: 0.5rem;
">
<span style="color: #6c757d; opacity: 0.75;" aria-hidden="true">/</span>
</li>
<li style="display: flex; align-items: center;">
<a
href="{% url 'candidate_list' %}"
style="
color: #6c757d; /* text-secondary/gray */
text-decoration: none;
transition: color 0.15s ease-in-out;
"
onmouseover="this.style.color='#000000';"
onmouseout="this.style.color='#6c757d';"
>
{% trans 'Applicants' %}
</a>
</li>
<li style="
display: flex;
align-items: center;
padding-left: 0.5rem;
padding-right: 0.5rem;
">
<span style="color: #6c757d; opacity: 0.75;" aria-hidden="true">/</span>
</li>
<li style="display: flex; align-items: center;">
<a
href="{% url 'candidate_detail' candidate.slug %}"
style="
color: #6c757d; /* text-secondary/gray */
text-decoration: none;
transition: color 0.15s ease-in-out;
"
onmouseover="this.style.color='#000000';"
onmouseout="this.style.color='#6c757d';"
>
{{candidate.resume_data.full_name}}
</a>
</li>
<li style="
display: flex;
align-items: center;
padding-left: 0.5rem;
padding-right: 0.5rem;
">
<span style="color: #6c757d; opacity: 0.75;" aria-hidden="true">/</span>
</li>
<li
aria-current="page"
style="
color: #F43B5E; /* Rosy Accent Color */
font-weight: 600;
"
>
{% trans 'AI Overview' %}
</li>
</ol>
</nav>
{% endif %}
{% endwith %}