144 lines
4.8 KiB
Python
144 lines
4.8 KiB
Python
# 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}" |