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