# models.py from django.db import models from django.core.exceptions import ValidationError from jobs.models import JobPosting from django.urls import reverse class ApplicantForm(models.Model): """Multiple dynamic forms per job posting, only one active at a time""" job_posting = models.ForeignKey( JobPosting, on_delete=models.CASCADE, related_name='applicant_forms' ) name = models.CharField( max_length=200, help_text="Form version name (e.g., 'Version A', 'Version B' etc)" ) description = models.TextField( blank=True, help_text="Optional description of this form version" ) is_active = models.BooleanField( default=False, help_text="Only one form can be active per job" ) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: unique_together = ('job_posting', 'name') ordering = ['-created_at'] verbose_name = "Application Form" verbose_name_plural = "Application Forms" def __str__(self): status = "(Active)" if self.is_active else "(Inactive)" return f"{self.name} for {self.job_posting.title} {status}" def clean(self): """Ensure only one active form per job""" if self.is_active: existing_active = self.job_posting.applicant_forms.filter( is_active=True ).exclude(pk=self.pk) if existing_active.exists(): raise ValidationError( "Only one active application form is allowed per job posting." ) super().clean() def activate(self): """Set this form as active and deactivate others""" self.is_active = True self.save() # Deactivate other forms self.job_posting.applicant_forms.exclude(pk=self.pk).update( is_active=False ) def get_public_url(self): """Returns the public application URL for this job's active form""" return reverse('applicant:apply_form', args=[self.job_posting.internal_job_id]) class FormField(models.Model): FIELD_TYPES = [ ('text', 'Text'), ('email', 'Email'), ('phone', 'Phone'), ('number', 'Number'), ('date', 'Date'), ('select', 'Dropdown'), ('radio', 'Radio Buttons'), ('checkbox', 'Checkbox'), ('textarea', 'Paragraph Text'), ('file', 'File Upload'), ('image', 'Image Upload'), ] form = models.ForeignKey( ApplicantForm, related_name='fields', on_delete=models.CASCADE ) label = models.CharField(max_length=255) field_type = models.CharField(max_length=20, choices=FIELD_TYPES) required = models.BooleanField(default=True) help_text = models.TextField(blank=True) choices = models.TextField( blank=True, help_text="Comma-separated options for select/radio fields" ) order = models.IntegerField(default=0) field_name = models.CharField(max_length=100, blank=True) class Meta: ordering = ['order'] verbose_name = "Form Field" verbose_name_plural = "Form Fields" def __str__(self): return f"{self.label} ({self.field_type}) in {self.form.name}" def save(self, *args, **kwargs): if not self.field_name: # Create a stable field name from label (e.g., "Full Name" → "full_name") import re # Use Unicode word characters, including Arabic, for field_name self.field_name = re.sub( r'[^\w]+', '_', self.label.lower(), flags=re.UNICODE ).strip('_') # Ensure uniqueness within the form base_name = self.field_name counter = 1 while FormField.objects.filter( form=self.form, field_name=self.field_name ).exists(): self.field_name = f"{base_name}_{counter}" counter += 1 super().save(*args, **kwargs) class ApplicantSubmission(models.Model): job_posting = models.ForeignKey(JobPosting, on_delete=models.CASCADE) form = models.ForeignKey(ApplicantForm, on_delete=models.CASCADE) submitted_at = models.DateTimeField(auto_now_add=True) data = models.JSONField() ip_address = models.GenericIPAddressField(null=True, blank=True) score = models.FloatField( default=0, help_text="Ranking score for the applicant submission" ) class Meta: ordering = ['-submitted_at'] verbose_name = "Applicant Submission" verbose_name_plural = "Applicant Submissions" def __str__(self): return f"Submission for {self.job_posting.title} at {self.submitted_at}"