retry filed for ai scoring and hooks

This commit is contained in:
Faheed 2025-10-26 15:36:22 +03:00
parent 9cd63d3897
commit de83838392
17 changed files with 331 additions and 70 deletions

View File

@ -185,8 +185,7 @@ ACCOUNT_SIGNUP_FIELDS = ['email*', 'password1*', 'password2*']
ACCOUNT_UNIQUE_EMAIL = True
ACCOUNT_EMAIL_VERIFICATION = 'none'
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
ACCOUNT_LOGIN_ON_EMAIL_CONFIRMATION = True
@ -238,7 +237,7 @@ STATICFILES_DIRS = [
BASE_DIR / 'static'
]
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
MEDIA_ROOT = os.path.join(BASE_DIR, 'static/media')
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# Default primary key field type
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field

View File

@ -642,3 +642,6 @@ class CandidateExamDateForm(forms.ModelForm):
widgets = {
'exam_date': forms.DateTimeInput(attrs={'type': 'datetime-local', 'class': 'form-control'}),
}

View File

@ -1 +1,14 @@
from .models import Candidate
from time import sleep
def callback_ai_parsing(task):
if task.success:
try:
pk = task.args[0]
c = Candidate.objects.get(pk=pk)
if c.retry and not c.is_resume_parsed:
sleep(30)
c.retry -= 1
c.save()
except Exception as e:
print(e)

View File

@ -7,7 +7,7 @@ import random
from datetime import date, timedelta
from django.core.management.base import BaseCommand
from django.utils import timezone
from time import sleep
from faker import Faker
from recruitment.models import JobPosting, Candidate, Source, FormTemplate
@ -67,6 +67,7 @@ class Command(BaseCommand):
created_jobs = []
for i in range(jobs_count):
# Dynamic job details
sleep(random.randint(4,10))
title = fake.job()
department = random.choice(DEPARTMENTS)
is_faculty = random.random() < 0.1 # 10% chance of being a faculty job
@ -101,7 +102,7 @@ class Command(BaseCommand):
job = JobPosting.objects.create(
**job_data
)
FormTemplate.objects.create(job=job, name=f"{job.title} Form", description=f"Form for {job.title}",is_active=True)
# FormTemplate.objects.create(job=job, name=f"{job.title} Form", description=f"Form for {job.title}",is_active=True)
created_jobs.append(job)
self.stdout.write(self.style.SUCCESS(f'Created JobPosting {i+1}/{jobs_count}: {job.title}'))
@ -109,6 +110,7 @@ class Command(BaseCommand):
# 4. Generate Candidates
if created_jobs:
for i in range(candidates_count):
sleep(random.randint(4,10))
# Link candidate to a random job
target_job = random.choice(created_jobs)
print(target_job)

View File

@ -0,0 +1,18 @@
# Generated by Django 5.2.7 on 2025-10-26 11:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('recruitment', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='candidate',
name='retry',
field=models.SmallIntegerField(default=3, verbose_name='Resume Parsing Retry'),
),
]

View File

@ -453,6 +453,7 @@ class Candidate(Base):
related_name="submitted_candidates",
verbose_name=_("Submitted by Agency"),
)
retry = models.SmallIntegerField(verbose_name="Resume Parsing Retry",default=3)
class Meta:
verbose_name = _("Candidate")

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=False, name=instance.title)
FormTemplate.objects.create(job=instance, is_active=False, name=instance.title)
async_task(
'recruitment.tasks.format_job_description',
instance.pk,
@ -52,7 +52,7 @@ def score_candidate_resume(sender, instance, created, **kwargs):
async_task(
'recruitment.tasks.handle_reume_parsing_and_scoring',
instance.pk,
# hook='myapp.tasks.email_sent_callback' # Optional callback
hook='recruitment.hooks.callback_ai_parsing'
)
@receiver(post_save, sender=FormTemplate)

View File

@ -167,6 +167,7 @@ def ai_handler(prompt):
print(response.status_code)
if response.status_code == 200:
res = response.json()
print(res)
content = res["choices"][0]['message']['content']
try:
# print(content)
@ -231,7 +232,7 @@ def handle_reume_parsing_and_scoring(pk):
logger.error(f"Error during initial data retrieval/parsing for candidate {instance.pk}: {e}")
print(f"Error during initial data retrieval/parsing for candidate {instance.pk}: {e}")
return
print(resume_text)
# --- 3. Single, Combined LLM Prompt (Major Cost & Latency Optimization) ---
prompt = f"""
You are an expert AI system functioning as both a Resume Parser and a Technical Recruiter.

View File

@ -17,12 +17,13 @@
<div class="row">
{# ------------------- LEFT COLUMN: ACCOUNT MENU (New Card Style) ------------------- #}
{# ------------------- LEFT COLUMN: ACCOUNT MENU ------------------- #}
<div class="col-md-4 col-lg-3 mb-4">
<div class="card shadow-sm border-0 rounded-4">
<div class="card-body p-0">
<div class="list-group list-group-flush rounded-4">
{# Assuming a main 'Profile' or 'Personal Information' page exists #}
{# NOTE: You will need to replace 'user_detail' with your actual profile URL name #}
<a href="{% url 'user_detail' user.pk %}" class="list-group-item list-group-item-action border-0 rounded-top-4 py-3">
<i class="fas fa-user-circle me-2"></i> {% translate "Personal Information" %}
</a>
@ -51,6 +52,15 @@
<h5 class="fw-bold mb-3">{% translate "Email Addresses" %}</h5>
<p class="text-muted border-bottom pb-3">{% translate "These email addresses are linked to your account. You can set the primary address, resend verification, or remove an address." %}</p>
{# Django Messages for Success/Error feedback #}
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }} mt-3" role="alert">
{{ message }}
</div>
{% endfor %}
{% endif %}
{% if emailaddresses %}
{% for emailaddress in emailaddresses %}
<div class="d-flex flex-column flex-sm-row justify-content-between align-items-sm-center py-3 {% if not forloop.last %}border-bottom{% endif %}">
@ -58,7 +68,7 @@
<p class="mb-2 mb-sm-0 me-3">
<span class="fw-bold text-dark">{{ emailaddress.email }}</span>
{# Status Badges: Using rounded-pill and appropriate colors #}
{# Status Badges #}
{% if emailaddress.primary %}
<span class="badge rounded-pill bg-info text-dark ms-2">{% translate "Primary" %}</span>
{% endif %}
@ -71,33 +81,30 @@
<div class="d-flex flex-wrap gap-2">
{# 1. MAKE PRIMARY ACTION #}
{# 1. MAKE PRIMARY ACTION (Uses 'action_primary') #}
{% if not emailaddress.primary %}
<form method="post" action="{% url 'account_email' %}" class="d-inline">
{% csrf_token %}
<input type="hidden" name="email" value="{{ emailaddress.email }}" />
<input type="hidden" name="action" value="set_primary" />
<button type="submit" class="btn btn-sm btn-outline-primary">{% translate "Make Primary" %}</button>
<button type="submit" name="action_primary" class="btn btn-sm btn-outline-primary">{% translate "Make Primary" %}</button>
</form>
{% endif %}
{# 2. RESEND VERIFICATION ACTION #}
{# 2. RESEND VERIFICATION ACTION (Uses 'action_send') #}
{% if not emailaddress.verified %}
<form method="post" action="{% url 'account_email' %}" class="d-inline">
{% csrf_token %}
<input type="hidden" name="email" value="{{ emailaddress.email }}" />
<input type="hidden" name="action" value="send" />
<button type="submit" class="btn btn-sm btn-outline-warning">{% translate "Re-send Verification" %}</button>
<button type="submit" name="action_send" class="btn btn-sm btn-outline-warning">{% translate "Re-send Verification" %}</button>
</form>
{% endif %}
{# 3. REMOVE ACTION #}
{# 3. REMOVE ACTION (Uses 'action_remove') #}
{% if not emailaddress.primary %}
<form method="post" action="{% url 'account_email' %}" class="d-inline">
{% csrf_token %}
<input type="hidden" name="email" value="{{ emailaddress.email }}" />
<input type="hidden" name="action" value="remove" />
<button type="submit" class="btn btn-sm btn-outline-danger">{% translate "Remove" %}</button>
<button type="submit" name="action_remove" class="btn btn-sm btn-outline-danger">{% translate "Remove" %}</button>
</form>
{% endif %}
</div>
@ -109,33 +116,26 @@
<hr class="my-4">
{# ------------------- ADD EMAIL FORM ------------------- #}
<h5 class="fw-bold mb-3">{% translate "Add Email Address" %}</h5>
<form method="post" action="{% url 'account_email' %}">
{% csrf_token %}
{# ------------------- ADD EMAIL FORM (The form that failed previously) ------------------- #}
{% if can_add_email %}
<h5 class="fw-bold mb-3">{% translate "Add Email Address" %}</h5>
<form method="post" action="{% url 'account_email' %}" class="add_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 %}
{# Ensure non-field errors are displayed (e.g., "Email already registered") #}
{% if form.non_field_errors %}
<div class="alert alert-danger" role="alert">
{{ form.non_field_errors }}
</div>
{% endif %}
{# 2. Render the fields using crispy #}
{{ form|crispy }}
{# Crispy renders the form fields (including new_email) #}
{{ 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>
{# The button name is crucial for the allauth view to recognize the "Add" action #}
<button class="btn btn-success mt-3" type="submit" name="action_add" style="background-color: #008080; border-color: #008080;">{% translate "Add Email" %}</button>
</form>
{% endif %}
</div>
</div>
</div>

View File

@ -0,0 +1,28 @@
{% load account i18n %}
{% autoescape off %}
<p style="font-family: Arial, sans-serif; font-size: 16px; color: #333;">
{% blocktrans %}Hello,{% endblocktrans %}
</p>
<p style="font-family: Arial, sans-serif; font-size: 16px; color: #333;">
{% blocktrans %}Thank you for choosing **KAAUH ATS**. To verify the ownership of your email address, please click the confirmation link below:{% endblocktrans %}
</p>
<div style="text-align: center; margin: 30px 0;">
<a href="{{ activate_url }}"
style="display: inline-block; padding: 15px 35px; background-color: #008080; color: #ffffff; text-decoration: none; border-radius: 8px; font-weight: bold; font-size: 18px;">
{% trans "Confirm My KAAUH ATS Email" %}
</a>
</div>
<p style="font-family: Arial, sans-serif; font-size: 14px; color: #777; margin-top: 20px;">
{% blocktrans %}If you did not request this verification, you can safely ignore this email.{% endblocktrans %}
</p>
<p style="font-family: Arial, sans-serif; font-size: 14px; color: #777; margin-top: 10px;">
{% blocktrans %}Alternatively, copy and paste this link into your browser:{% endblocktrans %}<br>
<a href="{{ activate_url }}" style="color: #008080; word-break: break-all;">{{ activate_url }}</a>
</p>
{% endautoescape %}

View File

@ -2,62 +2,65 @@
{% load i18n %}
{% load account %}
{% block title %}{% translate "Confirm Email Address" %}{% endblock %}
{% block title %}{% trans "Confirm Email Address" %}{% endblock %}
{% block content %}
<div class="container my-5">
<div class="row mb-4">
<div class="col">
<h3 class="fw-bold">{% translate "Account Verification" %}</h3>
<p class="text-muted">{% translate "Verify your email to secure your account and unlock full features." %}</p>
{# Centering the main header content #}
<div class="row mb-5 justify-content-center text-center">
<div class="col-md-8 col-lg-6">
<h3 class="fw-bolder" style="color: #00636e;">{% trans "Account Verification" %}</h3>
<p class="lead text-muted">{% trans "Verify your email to secure your account and unlock full features." %}</p>
</div>
</div>
<div class="row justify-content-center">
<div class="col-md-8 col-lg-6">
<div class="card shadow-sm border-0 rounded-4">
<div class="card shadow-lg border-0 rounded-4">
<div class="card-body p-5 text-center">
{% with emailaddress.email as email %}
{% with email as email %}
{% if confirmation %}
{# ------------------- CONFIRMATION REQUEST (GET) ------------------- #}
{# ------------------- CONFIRMATION REQUEST (GET) - Success Theme ------------------- #}
{% user_display confirmation.email_address.user as user_display %}
<i class="fas fa-envelope-open-text mb-4" style="font-size: 3rem; color: #008080;"></i>
<i class="fas fa-check-circle mb-4" style="font-size: 4rem; color: #008080;"></i> {# Changed icon to a checkmark for clarity #}
<h4 class="fw-bold mb-3">{% translate "Confirm Your Email Address" %}</h4>
<p class="lead text-muted">
{% blocktrans with email as email %}Please confirm that **{{ email }}** is an email address for user **{{ user_display }}**.{% endblocktrans %}
<p class="lead" style="color: #343a40;">
{% blocktrans with email as email %}Please confirm that **{{ email }}** is the correct email address for your account.{% endblocktrans %}
</p>
{# Confirmation Form #}
<form method="post" action="{% url 'account_confirm_email' confirmation.key %}">
{% csrf_token %}
{# Teal/Dark Green button consistent with the UI theme #}
<button class="btn btn-lg mt-4 px-5" type="submit" style="background-color: #008080; border-color: #008080; color: white;">
{% translate "Confirm" %}
<button class="btn btn-lg mt-4 px-5 fw-semibold text-white" type="submit"
style="background-color: #008080; border-color: #008080; box-shadow: 0 4px 8px rgba(0, 128, 128, 0.3);">
{% translate "Confirm & Activate" %}
</button>
</form>
{% else %}
{# ------------------- CONFIRMATION FAILED (Error) ------------------- #}
<i class="fas fa-exclamation-triangle text-danger mb-4" style="font-size: 3rem;"></i>
<h4 class="fw-bold mb-3 text-danger">{% translate "Invalid Link" %}</h4>
{# ------------------- CONFIRMATION FAILED (Error) - Danger Theme ------------------- #}
<i class="fas fa-unlink text-danger mb-4" style="font-size: 4rem;"></i> {# Changed icon to be more specific #}
<p class="lead">
{% translate "The email confirmation link has expired or is invalid." %}
<h4 class="fw-bold mb-3 text-danger">{% translate "Verification Failed" %}</h4>
<p class="lead text-danger">
{% translate "The email confirmation link is expired or invalid." %}
</p>
<p class="text-muted">
{% translate "Please request a new verification email from your account settings page." %}
<p class="text-muted small">
{% translate "If you recently requested a link, please ensure you use the newest one. You can request a new verification email from your account settings." %}
</p>
<a href="{% url 'account_email' %}" class="btn btn-outline-secondary mt-3 px-5">
{% translate "Go to Settings" %}
<a href="{% url 'account_email' %}" class="btn btn-outline-secondary mt-4 px-5 fw-semibold">
<i class="fas fa-cog me-2"></i> {% translate "Go to Settings" %}
</a>
{% endif %}

View File

@ -0,0 +1,193 @@
{% load static %}
{% load i18n %}
{% load allauth %}
{% load account %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>KAAUH ATS - Verify Email</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<style>
/* -------------------------------------------------------------------------- */
/* CUSTOM TEAL THEME OVERRIDES FOR BOOTSTRAP (Copied from your provided code) */
/* -------------------------------------------------------------------------- */
:root {
/* Define TEAL as the primary color for Bootstrap overrides */
--bs-primary: #00636e; /* Dark Teal */
--bs-primary-rgb: 0, 99, 110;
--bs-primary-light: #007a88; /* Lighter Teal for hover */
/* Background and Text Colors */
--bs-body-bg: #f8f9fa; /* Light gray background */
--bs-body-color: #212529; /* Dark text */
/* Utility colors */
--bs-border-color: #dee2e6; /* Bootstrap default border */
}
body {
font-family: 'Inter', sans-serif;
background-color: var(--bs-body-bg);
}
/* Custom Left Panel (Replicating the original look) */
.left-panel {
flex: 1;
/* NOTE: Ensure you have an 'image/kaauh_banner.png' in your static files */
background: url("{% static 'image/kaauh_banner.png' %}") no-repeat center center;
background-size: cover;
position: relative;
display: flex;
align-items: flex-end;
padding: 3rem;
color: white;
z-index: 1;
}
.left-panel::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(to top, rgba(0,0,0,0.8) 0%, rgba(0,0,0,0) 50%);
z-index: 0;
}
.left-panel-content {
position: relative;
z-index: 2;
}
/* Right Panel Styling */
.right-panel {
background-color: white;
padding: 3rem;
}
.form-fields {
max-height: 100%;
overflow-y: auto;
}
/* Component Overrides to use Teal Theme */
.btn-primary {
background-color: var(--bs-primary);
border-color: var(--bs-primary);
font-weight: 600;
border-radius: 0.5rem;
box-shadow: 0 4px 8px rgba(0, 99, 110, 0.2);
transition: all 0.2s ease;
}
.btn-primary:hover {
background-color: var(--bs-primary-light);
border-color: var(--bs-primary-light);
box-shadow: 0 6px 10px rgba(0, 99, 110, 0.3);
}
.form-control:focus {
border-color: var(--bs-primary);
box-shadow: 0 0 0 0.25rem rgba(0, 99, 110, 0.25);
}
.form-check-input:checked {
background-color: var(--bs-primary);
border-color: var(--bs-primary);
}
.text-accent {
color: var(--bs-primary) !important;
}
.text-accent:hover {
color: var(--bs-primary-light) !important;
text-decoration: underline;
}
/* Custom size adjustment for right panel on desktop */
@media (min-width: 992px) {
/* 1. Set a NARROWER fixed width for the right panel container */
.right-panel-col {
flex: 0 0 450px; /* Reduced from 800px to 450px */
}
/* 2. Vertically and horizontally center the content within the narrow panel */
.right-panel-col > .d-flex {
height: 100%;
justify-content: center;
align-items: center;
padding-top: 0;
padding-bottom: 0;
}
/* 3. Ensure the form container doesn't exceed a smaller size and is centered */
.right-panel-content-wrapper {
max-width: 350px; /* Max width of the form elements inside the panel */
width: 100%;
}
}
</style>
</head>
<body>
<div class="d-flex vh-100 w-100">
<div class="left-panel d-none d-lg-flex flex-grow-1">
<div class="left-panel-content">
<h1 class="text-4xl font-weight-bold mb-4" style="font-size: 1.5rem;">
<span class="text-white">
<div class="hospital-text text-center text-md-start me-3">
<div class="ar small">جامعة الأميرة نورة بنت عبدالرحمن الأكاديمية</div>
<div class="ar small">ومستشفى الملك عبدالله بن عبدالعزيز التخصصي</div>
<div class="en small">Princess Nourah bint Abdulrahman University</div>
<div class="en small">King Abdullah bin Abdulaziz University Hospital</div>
</div>
</span>
</h1>
<small>Powered By TENHAL | تنحل</small>
</div>
</div>
<div class="d-flex flex-column right-panel right-panel-col flex-grow-1 align-items-center justify-content-center">
<div class="right-panel-content-wrapper text-center">
<h2 id="form-title" class="h3 fw-bold mb-4 text-center text-accent">
{% trans "Verify Your Email Address" %}
</h2>
<div class="form-fields text-start">
<p class="mb-4">
{% blocktrans %}
We have sent an email to your email id for verification. Follow the link provided to finalize the signup process.
{% endblocktrans %}
</p>
<p class="mb-4 text-muted small">
{% trans "If you do not see the verification email in your main inbox, please check your spam folder." %}
</p>
<p class="mb-4 text-muted small">
{% trans "Please contact us if you do not receive the verification email within a few minutes." %}
</p>
<hr>
<div class="d-grid mt-4">
<a href="{% url 'account_email' %}" class="btn btn-outline-secondary w-100">
{% trans "Change or Resend Email" %}
</a>
</div>
<div class="text-center mt-3">
<a href="{% url 'account_login' %}" class="small text-accent fw-medium">
{% trans "Go to Sign In" %}
</a>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
</body>
</html>