kaauh_ats/recruitment/signals.py

500 lines
17 KiB
Python

import logging
import random
from datetime import datetime, timedelta
from django.db import transaction
from django_q.models import Schedule
from django_q.tasks import schedule
from django.dispatch import receiver
from django_q.tasks import async_task
from django.db.models.signals import post_save
from django.contrib.auth.models import User
from django.utils import timezone
from .models import (
FormField,
FormStage,
FormTemplate,
Application,
JobPosting,
Notification,
HiringAgency,
Person,
Source,
)
from .forms import generate_api_key, generate_api_secret
from django.contrib.auth import get_user_model
from django_q.models import Schedule
logger = logging.getLogger(__name__)
User = get_user_model()
@receiver(post_save, sender=JobPosting)
def format_job(sender, instance, created, **kwargs):
if created or not instance.ai_parsed:
form = getattr(instance, "form_template", None)
if not form:
FormTemplate.objects.get_or_create(
job=instance, is_active=True, name=instance.title
)
async_task(
"recruitment.tasks.format_job_description",
instance.pk,
# hook='myapp.tasks.email_sent_callback' # Optional callback
)
else:
existing_schedule = Schedule.objects.filter(
func="recruitment.tasks.form_close",
args=f"[{instance.pk}]",
schedule_type=Schedule.ONCE,
).first()
if instance.STATUS_CHOICES == "ACTIVE" and instance.application_deadline:
if not existing_schedule:
# Create a new schedule if one does not exist
schedule(
"recruitment.tasks.form_close",
instance.pk,
schedule_type=Schedule.ONCE,
next_run=instance.application_deadline,
repeats=-1, # Ensure the schedule is deleted after it runs
name=f"job_closing_{instance.pk}", # Add a name for easier lookup
)
elif existing_schedule.next_run != instance.application_deadline:
# Update an existing schedule's run time
existing_schedule.next_run = instance.application_deadline
existing_schedule.save()
elif existing_schedule:
# If the instance is no longer active, delete the scheduled task
existing_schedule.delete()
# @receiver(post_save, sender=JobPosting)
# def update_form_template_status(sender, instance, created, **kwargs):
# if not created:
# if instance.status == "Active":
# instance.form_template.is_active = True
# else:
# instance.form_template.is_active = False
# instance.save()
@receiver(post_save, sender=Application)
def score_candidate_resume(sender, instance, created, **kwargs):
if instance.resume and not instance.is_resume_parsed:
logger.info(f"Scoring resume for candidate {instance.pk}")
async_task(
"recruitment.tasks.handle_resume_parsing_and_scoring",
instance.pk,
hook="recruitment.hooks.callback_ai_parsing",
)
@receiver(post_save, sender=FormTemplate)
def create_default_stages(sender, instance, created, **kwargs):
"""
Create default resume stages when a new FormTemplate is created
"""
if created:
with transaction.atomic():
# Stage 1: Contact Information
contact_stage = FormStage.objects.create(
template=instance,
name="Contact Information",
order=0,
is_predefined=True,
)
# FormField.objects.create(
# stage=contact_stage,
# label="First Name",
# field_type="text",
# required=True,
# order=0,
# is_predefined=True,
# )
# FormField.objects.create(
# stage=contact_stage,
# label="Last Name",
# field_type="text",
# required=True,
# order=1,
# is_predefined=True,
# )
# FormField.objects.create(
# stage=contact_stage,
# label="Email Address",
# field_type="email",
# required=True,
# order=2,
# is_predefined=True,
# )
# FormField.objects.create(
# stage=contact_stage,
# label="Phone Number",
# field_type="phone",
# required=True,
# order=3,
# is_predefined=True,
# )
# FormField.objects.create(
# stage=contact_stage,
# label="Address",
# field_type="text",
# required=False,
# order=4,
# is_predefined=True,
# )
# FormField.objects.create(
# stage=contact_stage,
# label="National ID / Iqama Number",
# field_type="text",
# required=False,
# order=5,
# is_predefined=True,
# )
FormField.objects.create(
stage=contact_stage,
label="GPA",
field_type="text",
required=False,
order=1,
is_predefined=True,
)
FormField.objects.create(
stage=contact_stage,
label="Resume Upload",
field_type="file",
required=True,
order=2,
is_predefined=True,
file_types=".pdf,.doc,.docx",
max_file_size=1,
)
# # Stage 2: Resume Objective
# objective_stage = FormStage.objects.create(
# template=instance,
# name='Resume Objective',
# order=1,
# is_predefined=True
# )
# FormField.objects.create(
# stage=objective_stage,
# label='Career Objective',
# field_type='textarea',
# required=False,
# order=0,
# is_predefined=True
# )
# # Stage 3: Education
# education_stage = FormStage.objects.create(
# template=instance,
# name='Education',
# order=2,
# is_predefined=True
# )
# FormField.objects.create(
# stage=education_stage,
# label='Degree',
# field_type='text',
# required=True,
# order=0,
# is_predefined=True
# )
# FormField.objects.create(
# stage=education_stage,
# label='Institution',
# field_type='text',
# required=True,
# order=1,
# is_predefined=True
# )
# FormField.objects.create(
# stage=education_stage,
# label='Location',
# field_type='text',
# required=False,
# order=2,
# is_predefined=True
# )
# FormField.objects.create(
# stage=education_stage,
# label='Graduation Date',
# field_type='date',
# required=False,
# order=3,
# is_predefined=True
# )
# # Stage 4: Experience
# experience_stage = FormStage.objects.create(
# template=instance,
# name='Experience',
# order=3,
# is_predefined=True
# )
# FormField.objects.create(
# stage=experience_stage,
# label='Position Title',
# field_type='text',
# required=True,
# order=0,
# is_predefined=True
# )
# FormField.objects.create(
# stage=experience_stage,
# label='Company Name',
# field_type='text',
# required=True,
# order=1,
# is_predefined=True
# )
# FormField.objects.create(
# stage=experience_stage,
# label='Location',
# field_type='text',
# required=False,
# order=2,
# is_predefined=True
# )
# FormField.objects.create(
# stage=experience_stage,
# label='Start Date',
# field_type='date',
# required=True,
# order=3,
# is_predefined=True
# )
# FormField.objects.create(
# stage=experience_stage,
# label='End Date',
# field_type='date',
# required=True,
# order=4,
# is_predefined=True
# )
# FormField.objects.create(
# stage=experience_stage,
# label='Responsibilities & Achievements',
# field_type='textarea',
# required=False,
# order=5,
# is_predefined=True
# )
# # Stage 5: Skills
# skills_stage = FormStage.objects.create(
# template=instance,
# name='Skills',
# order=4,
# is_predefined=True
# )
# FormField.objects.create(
# stage=skills_stage,
# label='Technical Skills',
# field_type='checkbox',
# required=False,
# order=0,
# is_predefined=True,
# options=['Programming Languages', 'Frameworks', 'Tools & Technologies']
# )
# # Stage 6: Summary
# summary_stage = FormStage.objects.create(
# template=instance,
# name='Summary',
# order=5,
# is_predefined=True
# )
# FormField.objects.create(
# stage=summary_stage,
# label='Professional Summary',
# field_type='textarea',
# required=False,
# order=0,
# is_predefined=True
# )
# # Stage 7: Certifications
# certifications_stage = FormStage.objects.create(
# template=instance,
# name='Certifications',
# order=6,
# is_predefined=True
# )
# FormField.objects.create(
# stage=certifications_stage,
# label='Certification Name',
# field_type='text',
# required=False,
# order=0,
# is_predefined=True
# )
# FormField.objects.create(
# stage=certifications_stage,
# label='Issuing Organization',
# field_type='text',
# required=False,
# order=1,
# is_predefined=True
# )
# FormField.objects.create(
# stage=certifications_stage,
# label='Issue Date',
# field_type='date',
# required=False,
# order=2,
# is_predefined=True
# )
# FormField.objects.create(
# stage=certifications_stage,
# label='Expiration Date',
# field_type='date',
# required=False,
# order=3,
# is_predefined=True
# )
# # Stage 8: Awards and Recognitions
# awards_stage = FormStage.objects.create(
# template=instance,
# name='Awards and Recognitions',
# order=7,
# is_predefined=True
# )
# FormField.objects.create(
# stage=awards_stage,
# label='Award Name',
# field_type='text',
# required=False,
# order=0,
# is_predefined=True
# )
# FormField.objects.create(
# stage=awards_stage,
# label='Issuing Organization',
# field_type='text',
# required=False,
# order=1,
# is_predefined=True
# )
# FormField.objects.create(
# stage=awards_stage,
# label='Date Received',
# field_type='date',
# required=False,
# order=2,
# is_predefined=True
# )
# FormField.objects.create(
# stage=awards_stage,
# label='Description',
# field_type='textarea',
# required=False,
# order=3,
# is_predefined=True
# )
# AgencyMessage signal handler removed - model has been deleted
# SSE notification cache for real-time updates
SSE_NOTIFICATION_CACHE = {}
@receiver(post_save, sender=Notification)
def notification_created(sender, instance, created, **kwargs):
"""Signal handler for when a notification is created"""
if created:
logger.info(
f"New notification created: {instance.id} for user {instance.recipient.username}"
)
# Store notification in cache for SSE
user_id = instance.recipient.id
if user_id not in SSE_NOTIFICATION_CACHE:
SSE_NOTIFICATION_CACHE[user_id] = []
notification_data = {
"id": instance.id,
"message": instance.message[:100]
+ ("..." if len(instance.message) > 100 else ""),
"type": instance.get_notification_type_display(),
"status": instance.get_status_display(),
"time_ago": "Just now",
"url": f"/notifications/{instance.id}/",
}
SSE_NOTIFICATION_CACHE[user_id].append(notification_data)
# Keep only last 50 notifications per user in cache
if len(SSE_NOTIFICATION_CACHE[user_id]) > 50:
SSE_NOTIFICATION_CACHE[user_id] = SSE_NOTIFICATION_CACHE[user_id][-50:]
logger.info(f"Notification cached for SSE: {notification_data}")
def generate_random_password():
import string
return "".join(random.choices(string.ascii_letters + string.digits, k=12))
@receiver(post_save, sender=HiringAgency)
def hiring_agency_created(sender, instance, created, **kwargs):
if created:
logger.info(f"New hiring agency created: {instance.pk} - {instance.name}")
password = generate_random_password()
user = User.objects.create_user(
username=instance.name, email=instance.email, user_type="agency"
)
user.set_password(password)
user.save()
instance.user = user
instance.generated_password = password
instance.save()
logger.info(f"Generated password stored for agency: {instance.pk}")
@receiver(post_save, sender=Person)
def person_created(sender, instance, created, **kwargs):
if created and not instance.user:
logger.info(f"New Person created: {instance.pk} - {instance.email}")
user = User.objects.create_user(
username=instance.email,
first_name=instance.first_name,
last_name=instance.last_name,
email=instance.email,
phone=instance.phone,
user_type="candidate",
)
instance.user = user
instance.save()
@receiver(post_save, sender=Source)
def source_created(sender, instance, created, **kwargs):
"""
Automatically generate API key and API secret when a new Source is created.
"""
if created:
# Only generate keys if they don't already exist
if not instance.api_key and not instance.api_secret:
logger.info(f"Generating API keys for new Source: {instance.pk} - {instance.name}")
# Generate API key and secret using existing secure functions
api_key = generate_api_key()
api_secret = generate_api_secret()
# Update the source with generated keys
instance.api_key = api_key
instance.api_secret = api_secret
instance.save(update_fields=['api_key', 'api_secret'])
logger.info(f"API keys generated successfully for Source: {instance.name} (Key: {api_key[:8]}...)")
else:
logger.info(f"Source {instance.name} already has API keys, skipping generation")