import logging import random from datetime import 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 .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 ) # Enhanced reminder scheduling logic if instance.status == "ACTIVE" and instance.application_deadline: # Schedule 1-day reminder one_day_schedule = Schedule.objects.filter( name=f"one_day_reminder_{instance.pk}" ).first() one_day_before = instance.application_deadline - timedelta(days=1) if not one_day_schedule: schedule( "recruitment.tasks.send_one_day_reminder", instance.pk, schedule_type=Schedule.ONCE, next_run=one_day_before, repeats=-1, name=f"one_day_reminder_{instance.pk}", ) elif one_day_schedule.next_run != one_day_before: one_day_schedule.next_run = one_day_before one_day_schedule.save() # Schedule 15-minute reminder fifteen_min_schedule = Schedule.objects.filter( name=f"fifteen_min_reminder_{instance.pk}" ).first() fifteen_min_before = instance.application_deadline - timedelta(minutes=15) if not fifteen_min_schedule: schedule( "recruitment.tasks.send_fifteen_minute_reminder", instance.pk, schedule_type=Schedule.ONCE, next_run=fifteen_min_before, repeats=-1, name=f"fifteen_min_reminder_{instance.pk}", ) elif fifteen_min_schedule.next_run != fifteen_min_before: fifteen_min_schedule.next_run = fifteen_min_before fifteen_min_schedule.save() # Schedule job closing notification (enhanced form_close) closing_schedule = Schedule.objects.filter( name=f"job_closing_{instance.pk}" ).first() if not closing_schedule: schedule( "recruitment.tasks.send_job_closed_notification", instance.pk, schedule_type=Schedule.ONCE, next_run=instance.application_deadline, repeats=-1, name=f"job_closing_{instance.pk}", ) elif closing_schedule.next_run != instance.application_deadline: closing_schedule.next_run = instance.application_deadline closing_schedule.save() else: # Clean up all reminder schedules if job is no longer active reminder_schedules = Schedule.objects.filter( name__in=[f"one_day_reminder_{instance.pk}", f"fifteen_min_reminder_{instance.pk}", f"job_closing_{instance.pk}"] ) if reminder_schedules.exists(): reminder_schedules.delete() logger.info(f"Cleaned up reminder schedules for job {instance.pk}") # @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="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, ) 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}") from .utils import generate_random_password @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}") try: 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() except Exception as e: print(e) @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")