diff --git a/NorahUniversity/__pycache__/settings.cpython-312.pyc b/NorahUniversity/__pycache__/settings.cpython-312.pyc
index c5c0a1a..6946313 100644
Binary files a/NorahUniversity/__pycache__/settings.cpython-312.pyc and b/NorahUniversity/__pycache__/settings.cpython-312.pyc differ
diff --git a/NorahUniversity/settings.py b/NorahUniversity/settings.py
index 780b1eb..5e75a2d 100644
--- a/NorahUniversity/settings.py
+++ b/NorahUniversity/settings.py
@@ -191,15 +191,6 @@ SOCIALACCOUNT_PROVIDERS = {
}
}
-UNFOLD = {
- "DASHBOARD_CALLBACK": "recruitment.utils.dashboard_callback",
- "STYLES": [
- lambda request: static("unfold/css/styles.css"),
- ],
- "SCRIPTS": [
- lambda request: static("unfold/js/app.js"),
- ],
-}
ZOOM_ACCOUNT_ID = 'HoGikHXsQB2GNDC5Rvyw9A'
ZOOM_CLIENT_ID = 'brC39920R8C8azfudUaQgA'
diff --git a/db.sqlite3 b/db.sqlite3
index c37b46d..736bcc0 100644
Binary files a/db.sqlite3 and b/db.sqlite3 differ
diff --git a/recruitment/__pycache__/admin.cpython-312.pyc b/recruitment/__pycache__/admin.cpython-312.pyc
index 72b3fe5..7e73a6f 100644
Binary files a/recruitment/__pycache__/admin.cpython-312.pyc and b/recruitment/__pycache__/admin.cpython-312.pyc differ
diff --git a/recruitment/__pycache__/forms.cpython-312.pyc b/recruitment/__pycache__/forms.cpython-312.pyc
index 3f5b7af..a421df6 100644
Binary files a/recruitment/__pycache__/forms.cpython-312.pyc and b/recruitment/__pycache__/forms.cpython-312.pyc differ
diff --git a/recruitment/__pycache__/models.cpython-312.pyc b/recruitment/__pycache__/models.cpython-312.pyc
index 56ce80a..f67832d 100644
Binary files a/recruitment/__pycache__/models.cpython-312.pyc and b/recruitment/__pycache__/models.cpython-312.pyc differ
diff --git a/recruitment/__pycache__/urls.cpython-312.pyc b/recruitment/__pycache__/urls.cpython-312.pyc
index f9d611e..8fd07cf 100644
Binary files a/recruitment/__pycache__/urls.cpython-312.pyc and b/recruitment/__pycache__/urls.cpython-312.pyc differ
diff --git a/recruitment/__pycache__/views.cpython-312.pyc b/recruitment/__pycache__/views.cpython-312.pyc
index 094f95f..cb165ec 100644
Binary files a/recruitment/__pycache__/views.cpython-312.pyc and b/recruitment/__pycache__/views.cpython-312.pyc differ
diff --git a/recruitment/forms.py b/recruitment/forms.py
index 4c6ad9b..9faadaa 100644
--- a/recruitment/forms.py
+++ b/recruitment/forms.py
@@ -4,7 +4,7 @@ from crispy_forms.helper import FormHelper
from django.core.validators import URLValidator
from django.utils.translation import gettext_lazy as _
from crispy_forms.layout import Layout, Submit, HTML, Div, Field
-from .models import ZoomMeeting, Candidate,Job,TrainingMaterial,JobPosting
+from .models import ZoomMeeting, Candidate,TrainingMaterial,JobPosting
class CandidateForm(forms.ModelForm):
class Meta:
diff --git a/recruitment/migrations/0019_merge_20251006_1224.py b/recruitment/migrations/0019_merge_20251006_1224.py
new file mode 100644
index 0000000..a706fa2
--- /dev/null
+++ b/recruitment/migrations/0019_merge_20251006_1224.py
@@ -0,0 +1,14 @@
+# Generated by Django 5.2.7 on 2025-10-06 12:24
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('recruitment', '0013_formfield_formstage_remove_formsubmission_form_and_more'),
+ ('recruitment', '0018_alter_jobposting_hiring_agency'),
+ ]
+
+ operations = [
+ ]
diff --git a/recruitment/migrations/__pycache__/0013_formfield_formstage_remove_formsubmission_form_and_more.cpython-312.pyc b/recruitment/migrations/__pycache__/0013_formfield_formstage_remove_formsubmission_form_and_more.cpython-312.pyc
new file mode 100644
index 0000000..4c63586
Binary files /dev/null and b/recruitment/migrations/__pycache__/0013_formfield_formstage_remove_formsubmission_form_and_more.cpython-312.pyc differ
diff --git a/recruitment/migrations/__pycache__/0019_merge_20251006_1224.cpython-312.pyc b/recruitment/migrations/__pycache__/0019_merge_20251006_1224.cpython-312.pyc
new file mode 100644
index 0000000..7591704
Binary files /dev/null and b/recruitment/migrations/__pycache__/0019_merge_20251006_1224.cpython-312.pyc differ
diff --git a/recruitment/models.py b/recruitment/models.py
index e5a8d7b..e1a6159 100644
--- a/recruitment/models.py
+++ b/recruitment/models.py
@@ -16,22 +16,22 @@ class Base(models.Model):
class Meta:
abstract = True
-# Create your models here.
-class Job(Base):
- title = models.CharField(max_length=255, verbose_name=_('Title'))
- description_en = models.TextField(verbose_name=_('Description English'))
- description_ar = models.TextField(verbose_name=_('Description Arabic'))
- is_published = models.BooleanField(default=False, verbose_name=_('Published'))
- posted_to_linkedin = models.BooleanField(default=False, verbose_name=_('Posted to LinkedIn'))
- created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('Created at'))
- updated_at = models.DateTimeField(auto_now=True, verbose_name=_('Updated at'))
+# # Create your models here.
+# class Job(Base):
+# title = models.CharField(max_length=255, verbose_name=_('Title'))
+# description_en = models.TextField(verbose_name=_('Description English'))
+# description_ar = models.TextField(verbose_name=_('Description Arabic'))
+# is_published = models.BooleanField(default=False, verbose_name=_('Published'))
+# posted_to_linkedin = models.BooleanField(default=False, verbose_name=_('Posted to LinkedIn'))
+# created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('Created at'))
+# updated_at = models.DateTimeField(auto_now=True, verbose_name=_('Updated at'))
- class Meta:
- verbose_name = _('Job')
- verbose_name_plural = _('Jobs')
+# class Meta:
+# verbose_name = _('Job')
+# verbose_name_plural = _('Jobs')
- def __str__(self):
- return self.title
+# def __str__(self):
+# return self.title
class JobPosting(Base):
# Basic Job Information
@@ -506,4 +506,43 @@ class SharedFormTemplate(models.Model):
verbose_name_plural = 'Shared Form Templates'
def __str__(self):
- return f"Shared: {self.template.name}"
\ No newline at end of file
+ return f"Shared: {self.template.name}"
+
+
+class Source(models.Model):
+ name = models.CharField(
+ max_length=100,
+ unique=True,
+ verbose_name=_('Source Name'),
+ help_text=_("e.g., ATS, ERP ")
+ )
+ created_at = models.DateTimeField(auto_now_add=True)
+
+ def __str__(self):
+ return self.name
+
+ class Meta:
+ verbose_name = _('Source')
+ verbose_name_plural = _('Sources')
+ ordering = ['name']
+
+class HiringAgency(Base):
+ name = models.CharField(max_length=200, unique=True, verbose_name=_('Agency Name'))
+ contact_person = models.CharField(max_length=150, blank=True, verbose_name=_('Contact Person'))
+ email = models.EmailField(blank=True)
+ phone = models.CharField(max_length=20, blank=True)
+ website = models.URLField(blank=True)
+ notes = models.TextField(blank=True, help_text=_("Internal notes about the agency"))
+ country=CountryField(blank=True, null=True,blank_label=_('Select country'))
+ address=models.TextField(blank=True,null=True)
+
+ def __str__(self):
+ return self.name
+
+ class Meta:
+ verbose_name = _('Hiring Agency')
+ verbose_name_plural = _('Hiring Agencies')
+ ordering = ['name']
+
+
+
\ No newline at end of file
diff --git a/recruitment/templatetags/__pycache__/__init__.cpython-312.pyc b/recruitment/templatetags/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000..364ecb7
Binary files /dev/null and b/recruitment/templatetags/__pycache__/__init__.cpython-312.pyc differ
diff --git a/recruitment/templatetags/__pycache__/form_filters.cpython-312.pyc b/recruitment/templatetags/__pycache__/form_filters.cpython-312.pyc
new file mode 100644
index 0000000..dc240d2
Binary files /dev/null and b/recruitment/templatetags/__pycache__/form_filters.cpython-312.pyc differ
diff --git a/recruitment/views.py b/recruitment/views.py
index fe0e444..2f26c1b 100644
--- a/recruitment/views.py
+++ b/recruitment/views.py
@@ -14,7 +14,7 @@ from django.contrib import messages
from django.core.paginator import Paginator
from .linkedin_service import LinkedInService
from .models import FormTemplate, FormStage, FormField,FieldResponse,FormSubmission
-from .models import ZoomMeeting, Job, Candidate, JobPosting
+from .models import ZoomMeeting, Candidate, JobPosting
from .serializers import JobPostingSerializer, CandidateSerializer
from django.shortcuts import get_object_or_404, render, redirect
from django.views.generic import CreateView,UpdateView,DetailView,ListView
diff --git a/static/image/applicant/__init__.py b/static/image/applicant/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/static/image/applicant/__pycache__/__init__.cpython-312.pyc b/static/image/applicant/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000..3e50e87
Binary files /dev/null and b/static/image/applicant/__pycache__/__init__.cpython-312.pyc differ
diff --git a/static/image/applicant/__pycache__/admin.cpython-312.pyc b/static/image/applicant/__pycache__/admin.cpython-312.pyc
new file mode 100644
index 0000000..c57cd4a
Binary files /dev/null and b/static/image/applicant/__pycache__/admin.cpython-312.pyc differ
diff --git a/static/image/applicant/__pycache__/apps.cpython-312.pyc b/static/image/applicant/__pycache__/apps.cpython-312.pyc
new file mode 100644
index 0000000..c134cdd
Binary files /dev/null and b/static/image/applicant/__pycache__/apps.cpython-312.pyc differ
diff --git a/static/image/applicant/__pycache__/forms.cpython-312.pyc b/static/image/applicant/__pycache__/forms.cpython-312.pyc
new file mode 100644
index 0000000..cbee2e9
Binary files /dev/null and b/static/image/applicant/__pycache__/forms.cpython-312.pyc differ
diff --git a/static/image/applicant/__pycache__/forms_builder.cpython-312.pyc b/static/image/applicant/__pycache__/forms_builder.cpython-312.pyc
new file mode 100644
index 0000000..dfa9f76
Binary files /dev/null and b/static/image/applicant/__pycache__/forms_builder.cpython-312.pyc differ
diff --git a/static/image/applicant/__pycache__/models.cpython-312.pyc b/static/image/applicant/__pycache__/models.cpython-312.pyc
new file mode 100644
index 0000000..bb29aff
Binary files /dev/null and b/static/image/applicant/__pycache__/models.cpython-312.pyc differ
diff --git a/static/image/applicant/__pycache__/urls.cpython-312.pyc b/static/image/applicant/__pycache__/urls.cpython-312.pyc
new file mode 100644
index 0000000..7ae23f9
Binary files /dev/null and b/static/image/applicant/__pycache__/urls.cpython-312.pyc differ
diff --git a/static/image/applicant/__pycache__/views.cpython-312.pyc b/static/image/applicant/__pycache__/views.cpython-312.pyc
new file mode 100644
index 0000000..58cd1ef
Binary files /dev/null and b/static/image/applicant/__pycache__/views.cpython-312.pyc differ
diff --git a/static/image/applicant/admin.py b/static/image/applicant/admin.py
new file mode 100644
index 0000000..8c38f3f
--- /dev/null
+++ b/static/image/applicant/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/static/image/applicant/apps.py b/static/image/applicant/apps.py
new file mode 100644
index 0000000..27badf7
--- /dev/null
+++ b/static/image/applicant/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class ApplicantConfig(AppConfig):
+ default_auto_field = 'django.db.models.BigAutoField'
+ name = 'applicant'
diff --git a/static/image/applicant/forms.py b/static/image/applicant/forms.py
new file mode 100644
index 0000000..5c5b0b5
--- /dev/null
+++ b/static/image/applicant/forms.py
@@ -0,0 +1,22 @@
+from django import forms
+from .models import ApplicantForm, FormField
+
+class ApplicantFormCreateForm(forms.ModelForm):
+ class Meta:
+ model = ApplicantForm
+ fields = ['name', 'description']
+ widgets = {
+ 'name': forms.TextInput(attrs={'class': 'form-control'}),
+ 'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
+ }
+
+class FormFieldForm(forms.ModelForm):
+ class Meta:
+ model = FormField
+ fields = ['label', 'field_type', 'required', 'help_text', 'choices']
+ widgets = {
+ 'label': forms.TextInput(attrs={'class': 'form-control'}),
+ 'field_type': forms.Select(attrs={'class': 'form-control'}),
+ 'help_text': forms.Textarea(attrs={'class': 'form-control', 'rows': 2}),
+ 'choices': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Option1, Option2, Option3'}),
+ }
\ No newline at end of file
diff --git a/static/image/applicant/forms_builder.py b/static/image/applicant/forms_builder.py
new file mode 100644
index 0000000..c3a43e7
--- /dev/null
+++ b/static/image/applicant/forms_builder.py
@@ -0,0 +1,49 @@
+from django import forms
+from .models import FormField
+
+# applicant/forms_builder.py
+def create_dynamic_form(form_instance):
+ fields = {}
+
+ for field in form_instance.fields.all():
+ field_kwargs = {
+ 'label': field.label,
+ 'required': field.required,
+ 'help_text': field.help_text
+ }
+
+ # Use stable field_name instead of database ID
+ field_key = field.field_name
+
+ if field.field_type == 'text':
+ fields[field_key] = forms.CharField(**field_kwargs)
+ elif field.field_type == 'email':
+ fields[field_key] = forms.EmailField(**field_kwargs)
+ elif field.field_type == 'phone':
+ fields[field_key] = forms.CharField(**field_kwargs)
+ elif field.field_type == 'number':
+ fields[field_key] = forms.IntegerField(**field_kwargs)
+ elif field.field_type == 'date':
+ fields[field_key] = forms.DateField(**field_kwargs)
+ elif field.field_type == 'textarea':
+ fields[field_key] = forms.CharField(
+ widget=forms.Textarea,
+ **field_kwargs
+ )
+ elif field.field_type in ['select', 'radio']:
+ choices = [(c.strip(), c.strip()) for c in field.choices.split(',') if c.strip()]
+ if not choices:
+ choices = [('', '---')]
+ if field.field_type == 'select':
+ fields[field_key] = forms.ChoiceField(choices=choices, **field_kwargs)
+ else:
+ fields[field_key] = forms.ChoiceField(
+ choices=choices,
+ widget=forms.RadioSelect,
+ **field_kwargs
+ )
+ elif field.field_type == 'checkbox':
+ field_kwargs['required'] = False
+ fields[field_key] = forms.BooleanField(**field_kwargs)
+
+ return type('DynamicApplicantForm', (forms.Form,), fields)
\ No newline at end of file
diff --git a/static/image/applicant/migrations/0001_initial.py b/static/image/applicant/migrations/0001_initial.py
new file mode 100644
index 0000000..d7437c3
--- /dev/null
+++ b/static/image/applicant/migrations/0001_initial.py
@@ -0,0 +1,70 @@
+# Generated by Django 5.2.6 on 2025-10-01 21:41
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ('jobs', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='ApplicantForm',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(help_text="Form version name (e.g., 'Version A', 'Version B' etc)", max_length=200)),
+ ('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)),
+ ('job_posting', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='applicant_forms', to='jobs.jobposting')),
+ ],
+ options={
+ 'verbose_name': 'Application Form',
+ 'verbose_name_plural': 'Application Forms',
+ 'ordering': ['-created_at'],
+ 'unique_together': {('job_posting', 'name')},
+ },
+ ),
+ migrations.CreateModel(
+ name='ApplicantSubmission',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('submitted_at', models.DateTimeField(auto_now_add=True)),
+ ('data', models.JSONField()),
+ ('ip_address', models.GenericIPAddressField(blank=True, null=True)),
+ ('score', models.FloatField(default=0, help_text='Ranking score for the applicant submission')),
+ ('form', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='applicant.applicantform')),
+ ('job_posting', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='jobs.jobposting')),
+ ],
+ options={
+ 'verbose_name': 'Applicant Submission',
+ 'verbose_name_plural': 'Applicant Submissions',
+ 'ordering': ['-submitted_at'],
+ },
+ ),
+ migrations.CreateModel(
+ name='FormField',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('label', models.CharField(max_length=255)),
+ ('field_type', models.CharField(choices=[('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')], max_length=20)),
+ ('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(blank=True, max_length=100)),
+ ('form', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='fields', to='applicant.applicantform')),
+ ],
+ options={
+ 'verbose_name': 'Form Field',
+ 'verbose_name_plural': 'Form Fields',
+ 'ordering': ['order'],
+ },
+ ),
+ ]
diff --git a/static/image/applicant/migrations/__init__.py b/static/image/applicant/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/static/image/applicant/migrations/__pycache__/0001_initial.cpython-312.pyc b/static/image/applicant/migrations/__pycache__/0001_initial.cpython-312.pyc
new file mode 100644
index 0000000..3091ce8
Binary files /dev/null and b/static/image/applicant/migrations/__pycache__/0001_initial.cpython-312.pyc differ
diff --git a/static/image/applicant/migrations/__pycache__/0002_formfield_field_name.cpython-312.pyc b/static/image/applicant/migrations/__pycache__/0002_formfield_field_name.cpython-312.pyc
new file mode 100644
index 0000000..bb11c8f
Binary files /dev/null and b/static/image/applicant/migrations/__pycache__/0002_formfield_field_name.cpython-312.pyc differ
diff --git a/static/image/applicant/migrations/__pycache__/0003_applicantsubmission_score.cpython-312.pyc b/static/image/applicant/migrations/__pycache__/0003_applicantsubmission_score.cpython-312.pyc
new file mode 100644
index 0000000..67abc7e
Binary files /dev/null and b/static/image/applicant/migrations/__pycache__/0003_applicantsubmission_score.cpython-312.pyc differ
diff --git a/static/image/applicant/migrations/__pycache__/0004_alter_applicantform_name_alter_formfield_choices_and_more.cpython-312.pyc b/static/image/applicant/migrations/__pycache__/0004_alter_applicantform_name_alter_formfield_choices_and_more.cpython-312.pyc
new file mode 100644
index 0000000..2a2430a
Binary files /dev/null and b/static/image/applicant/migrations/__pycache__/0004_alter_applicantform_name_alter_formfield_choices_and_more.cpython-312.pyc differ
diff --git a/static/image/applicant/migrations/__pycache__/__init__.cpython-312.pyc b/static/image/applicant/migrations/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000..e38639e
Binary files /dev/null and b/static/image/applicant/migrations/__pycache__/__init__.cpython-312.pyc differ
diff --git a/static/image/applicant/models.py b/static/image/applicant/models.py
new file mode 100644
index 0000000..6b35d2f
--- /dev/null
+++ b/static/image/applicant/models.py
@@ -0,0 +1,144 @@
+# 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}"
\ No newline at end of file
diff --git a/static/image/applicant/templates/applicant/apply_form.html b/static/image/applicant/templates/applicant/apply_form.html
new file mode 100644
index 0000000..eae2993
--- /dev/null
+++ b/static/image/applicant/templates/applicant/apply_form.html
@@ -0,0 +1,94 @@
+{% extends 'base.html' %}
+
+{% block title %}
+ Apply: {{ job.title }}
+{% endblock %}
+
+{% block content %}
+
+
+
+
+ {# --- 1. Job Header and Overview (Fixed/Static Info) --- #}
+
+
{{ job.title }}
+
+
+ Your final step to apply for this position.
+
+
+
+
+
+ Department: {{ job.department|default:"Not specified" }}
+
+
+
+ Location: {{ job.get_location_display }}
+
+
+
+ Type: {{ job.get_job_type_display }} • {{ job.get_workplace_type_display }}
+
+
+
+
+ {# --- 2. Application Form Section --- #}
+
+
Application Details
+
+ {% if applicant_form.description %}
+
{{ applicant_form.description }}
+ {% endif %}
+
+
+
+
+
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/static/image/applicant/templates/applicant/create_form.html b/static/image/applicant/templates/applicant/create_form.html
new file mode 100644
index 0000000..e1c616a
--- /dev/null
+++ b/static/image/applicant/templates/applicant/create_form.html
@@ -0,0 +1,68 @@
+{% extends 'base.html' %}
+
+{% block title %}
+ Define Form for {{ job.title }}
+{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+
+ 🛠️ New Application Form Configuration
+
+
+
+ You are creating a new form structure for job: {{ job.title }}
+
+
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/static/image/applicant/templates/applicant/edit_form.html b/static/image/applicant/templates/applicant/edit_form.html
new file mode 100644
index 0000000..e9ad842
--- /dev/null
+++ b/static/image/applicant/templates/applicant/edit_form.html
@@ -0,0 +1,1020 @@
+{% extends 'base.html' %}
+{% load static i18n %}
+
+{% block title %}
+Edit Application Form - {{ applicant_form.name }}
+{% endblock %}
+
+{% block customCSS %}
+
+{% endblock %}
+
+{% block content %}
+
+ {% if messages %}
+
+ {% for message in messages %}
+
+ {% if message.tags == "success" %}
+ ✓
+ {% else %}
+ !
+ {% endif %}
+ {{ message }}
+
+ {% endfor %}
+
+ {% endif %}
+
+
+
+
+
+
+
+{% endblock %}
+
+{% block customJS %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/static/image/applicant/templates/applicant/job_forms_list.html b/static/image/applicant/templates/applicant/job_forms_list.html
new file mode 100644
index 0000000..7c7253f
--- /dev/null
+++ b/static/image/applicant/templates/applicant/job_forms_list.html
@@ -0,0 +1,103 @@
+{% extends 'base.html' %}
+
+{% block title %}
+ Manage Forms | {{ job.title }}
+{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+
+ Application Forms for "{{ job.title }}"
+
+
+ Internal Job ID: **{{ job.internal_job_id }}**
+
+
+
+ {# Primary Action Button using the theme color #}
+
+ Create New Form
+
+
+
+ {% if forms %}
+
+
+ {% for form in forms %}
+
+ {# Custom styling based on active state #}
+
+
+ {# Left Section: Form Details #}
+
+
+ {{ form.name }}
+
+
+ {# Status Badge #}
+ {% if form.is_active %}
+
+ Active Form
+
+ {% else %}
+
+ Inactive
+
+ {% endif %}
+
+
+ {{ form.description|default:"— No description provided. —" }}
+
+
+
+ {# Right Section: Actions #}
+
+
+ {% endfor %}
+
+
+ {% else %}
+
+
+
No application forms have been created yet for this job.
+
Click the button above to define a new form structure.
+
+ {% endif %}
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/static/image/applicant/templates/applicant/review_job_detail.html b/static/image/applicant/templates/applicant/review_job_detail.html
new file mode 100644
index 0000000..44414b3
--- /dev/null
+++ b/static/image/applicant/templates/applicant/review_job_detail.html
@@ -0,0 +1,129 @@
+{% extends "base.html" %}
+
+{% block title %}{{ job.title }} - University ATS{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+
+
+ Department: {{ job.department|default:"Not specified" }}
+
+
+ Position Number: {{ job.position_number|default:"Not specified" }}
+
+
+
+
+
+ Job Type: {{ job.get_job_type_display }}
+
+
+ Workplace: {{ job.get_workplace_type_display }}
+
+
+
+
+
+ Location: {{ job.get_location_display }}
+
+
+ Created By: {{ job.created_by|default:"Not specified" }}
+
+
+
+ {% if job.salary_range %}
+
+
+ Salary Range: {{ job.salary_range }}
+
+
+ {% endif %}
+
+ {% if job.start_date %}
+
+
+ Start Date: {{ job.start_date }}
+
+
+ {% endif %}
+
+ {% if job.application_deadline %}
+
+
+ Application Deadline: {{ job.application_deadline }}
+ {% if job.is_expired %}
+ EXPIRED
+ {% endif %}
+
+
+ {% endif %}
+
+
+ {% if job.description %}
+
+
Description
+
{{ job.description|linebreaks }}
+
+ {% endif %}
+
+ {% if job.qualifications %}
+
+
Qualifications
+
{{ job.qualifications|linebreaks }}
+
+ {% endif %}
+
+ {% if job.benefits %}
+
+
Benefits
+
{{ job.benefits|linebreaks }}
+
+ {% endif %}
+
+ {% if job.application_instructions %}
+
+
Application Instructions
+
{{ job.application_instructions|linebreaks }}
+
+ {% endif %}
+
+
+
+
+
+
+
+
+
+
+
+
+
Review the job details on the left, then click the button below to submit your application.
+
+ Apply for this Position
+
+
+ You'll be redirected to our secure application form where you can upload your resume and provide additional details.
+
+
+
+
+
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/static/image/applicant/templates/applicant/thank_you.html b/static/image/applicant/templates/applicant/thank_you.html
new file mode 100644
index 0000000..b93c945
--- /dev/null
+++ b/static/image/applicant/templates/applicant/thank_you.html
@@ -0,0 +1,35 @@
+{% extends 'base.html' %}
+{% block title %}Application Submitted - {{ job.title }}{% endblock %}
+{% block content %}
+
+
+
+
+
Thank You!
+
Your application has been submitted successfully
+
+
+
Position: {{ job.title }}
+
Job ID: {{ job.internal_job_id }}
+
Department: {{ job.department|default:"Not specified" }}
+ {% if job.application_deadline %}
+
Application Deadline: {{ job.application_deadline|date:"F j, Y" }}
+ {% endif %}
+
+
+
+ We appreciate your interest in joining our team. Our hiring team will review your application
+ and contact you if there's a potential match for this position.
+
+
+ {% comment %}
{% endcomment %}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/static/image/applicant/templatetags/__init__.py b/static/image/applicant/templatetags/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/static/image/applicant/templatetags/__pycache__/__init__.cpython-312.pyc b/static/image/applicant/templatetags/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000..cc88fb9
Binary files /dev/null and b/static/image/applicant/templatetags/__pycache__/__init__.cpython-312.pyc differ
diff --git a/static/image/applicant/templatetags/__pycache__/mytags.cpython-312.pyc b/static/image/applicant/templatetags/__pycache__/mytags.cpython-312.pyc
new file mode 100644
index 0000000..eb9bf9e
Binary files /dev/null and b/static/image/applicant/templatetags/__pycache__/mytags.cpython-312.pyc differ
diff --git a/static/image/applicant/templatetags/__pycache__/signals.cpython-312.pyc b/static/image/applicant/templatetags/__pycache__/signals.cpython-312.pyc
new file mode 100644
index 0000000..0cae73f
Binary files /dev/null and b/static/image/applicant/templatetags/__pycache__/signals.cpython-312.pyc differ
diff --git a/static/image/applicant/templatetags/mytags.py b/static/image/applicant/templatetags/mytags.py
new file mode 100644
index 0000000..b60911d
--- /dev/null
+++ b/static/image/applicant/templatetags/mytags.py
@@ -0,0 +1,24 @@
+import json
+from django import template
+
+register = template.Library()
+
+@register.filter(name='from_json')
+def from_json(json_string):
+ """
+ Safely loads a JSON string into a Python object (list or dict).
+ """
+ try:
+ # The JSON string comes from the context and needs to be parsed
+ return json.loads(json_string)
+ except (TypeError, json.JSONDecodeError):
+ # Handle cases where the string is invalid or None/empty
+ return []
+
+
+@register.filter(name='split')
+def split_string(value, key=None):
+ """Splits a string by the given key (default is space)."""
+ if key is None:
+ return value.split()
+ return value.split(key)
\ No newline at end of file
diff --git a/static/image/applicant/templatetags/signals.py b/static/image/applicant/templatetags/signals.py
new file mode 100644
index 0000000..8d5f22f
--- /dev/null
+++ b/static/image/applicant/templatetags/signals.py
@@ -0,0 +1,14 @@
+# from django.db.models.signals import post_save
+# from django.dispatch import receiver
+# from . import models
+#
+# @receiver(post_save, sender=models.Candidate)
+# def parse_resume(sender, instance, created, **kwargs):
+# if instance.resume and not instance.summary:
+# from .utils import extract_summary_from_pdf,match_resume_with_job_description
+# summary = extract_summary_from_pdf(instance.resume.path)
+# if 'error' not in summary:
+# instance.summary = summary
+# instance.save()
+#
+# # match_resume_with_job_description
\ No newline at end of file
diff --git a/static/image/applicant/tests.py b/static/image/applicant/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/static/image/applicant/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/static/image/applicant/urls.py b/static/image/applicant/urls.py
new file mode 100644
index 0000000..fa4fe8a
--- /dev/null
+++ b/static/image/applicant/urls.py
@@ -0,0 +1,18 @@
+from django.urls import path
+from . import views
+
+app_name = 'applicant'
+
+urlpatterns = [
+ # Form Management
+ path('job//forms/', views.job_forms_list, name='job_forms_list'),
+ path('job//forms/create/', views.create_form_for_job, name='create_form'),
+ path('form//edit/', views.edit_form, name='edit_form'),
+ path('field//delete/', views.delete_field, name='delete_field'),
+ path('form//activate/', views.activate_form, name='activate_form'),
+
+ # Public Application
+ path('apply//', views.apply_form_view, name='apply_form'),
+ path('review/job/detail//',views.review_job_detail, name="review_job_detail"),
+ path('apply//thank-you/', views.thank_you_view, name='thank_you'),
+]
\ No newline at end of file
diff --git a/static/image/applicant/utils.py b/static/image/applicant/utils.py
new file mode 100644
index 0000000..4901d72
--- /dev/null
+++ b/static/image/applicant/utils.py
@@ -0,0 +1,34 @@
+import os
+import fitz # PyMuPDF
+import spacy
+import requests
+from recruitment import models
+from django.conf import settings
+
+nlp = spacy.load("en_core_web_sm")
+
+def extract_text_from_pdf(pdf_path):
+ text = ""
+ with fitz.open(pdf_path) as doc:
+ for page in doc:
+ text += page.get_text()
+ return text
+
+def extract_summary_from_pdf(pdf_path):
+ if not os.path.exists(pdf_path):
+ return {'error': 'File not found'}
+
+ text = extract_text_from_pdf(pdf_path)
+ doc = nlp(text)
+ summary = {
+ 'name': doc.ents[0].text if doc.ents else '',
+ 'skills': [chunk.text for chunk in doc.noun_chunks if len(chunk.text.split()) > 1],
+ 'summary': text[:500]
+ }
+ return summary
+
+def match_resume_with_job_description(resume, job_description,prompt=""):
+ resume_doc = nlp(resume)
+ job_doc = nlp(job_description)
+ similarity = resume_doc.similarity(job_doc)
+ return similarity
\ No newline at end of file
diff --git a/static/image/applicant/views.py b/static/image/applicant/views.py
new file mode 100644
index 0000000..2cb4dc3
--- /dev/null
+++ b/static/image/applicant/views.py
@@ -0,0 +1,175 @@
+# applicant/views.py (Updated edit_form function)
+
+from django.shortcuts import render, get_object_or_404, redirect
+from django.contrib import messages
+from django.http import Http404, JsonResponse # <-- Import JsonResponse
+from django.views.decorators.csrf import csrf_exempt # <-- Needed for JSON POST if not using FormData
+import json # <-- Import json
+from django.db import transaction # <-- Import transaction
+
+# (Keep all your existing imports)
+from .models import ApplicantForm, FormField, ApplicantSubmission
+from .forms import ApplicantFormCreateForm, FormFieldForm
+from jobs.models import JobPosting
+from .forms_builder import create_dynamic_form
+
+# ... (Keep all other functions like job_forms_list, create_form_for_job, etc.)
+# ...
+
+
+
+# === FORM MANAGEMENT VIEWS ===
+
+def job_forms_list(request, job_id):
+ """List all forms for a specific job"""
+ job = get_object_or_404(JobPosting, internal_job_id=job_id)
+ forms = job.applicant_forms.all()
+ return render(request, 'applicant/job_forms_list.html', {
+ 'job': job,
+ 'forms': forms
+ })
+
+def create_form_for_job(request, job_id):
+ """Create a new form for a job"""
+ job = get_object_or_404(JobPosting, internal_job_id=job_id)
+
+ if request.method == 'POST':
+ form = ApplicantFormCreateForm(request.POST)
+ if form.is_valid():
+ applicant_form = form.save(commit=False)
+ applicant_form.job_posting = job
+ applicant_form.save()
+ messages.success(request, 'Form created successfully!')
+ return redirect('applicant:job_forms_list', job_id=job_id)
+ else:
+ form = ApplicantFormCreateForm()
+
+ return render(request, 'applicant/create_form.html', {
+ 'job': job,
+ 'form': form
+ })
+
+
+@transaction.atomic # Ensures all fields are saved or none are
+def edit_form(request, form_id):
+ """Edit form details and manage fields, including dynamic builder save."""
+ applicant_form = get_object_or_404(ApplicantForm, id=form_id)
+ job = applicant_form.job_posting
+
+ if request.method == 'POST':
+ # --- 1. Handle JSON data from the Form Builder (JavaScript) ---
+ if request.content_type == 'application/json':
+ try:
+ field_data = json.loads(request.body)
+
+ # Clear existing fields for this form
+ applicant_form.fields.all().delete()
+
+ # Create new fields from the JSON data
+ for field_config in field_data:
+ # Sanitize/ensure required fields are present
+ FormField.objects.create(
+ form=applicant_form,
+ label=field_config.get('label', 'New Field'),
+ field_type=field_config.get('field_type', 'text'),
+ required=field_config.get('required', True),
+ help_text=field_config.get('help_text', ''),
+ choices=field_config.get('choices', ''),
+ order=field_config.get('order', 0),
+ # field_name will be auto-generated/re-generated on save() if needed
+ )
+
+ return JsonResponse({'status': 'success', 'message': 'Form structure saved successfully!'})
+ except json.JSONDecodeError:
+ return JsonResponse({'status': 'error', 'message': 'Invalid JSON data.'}, status=400)
+ except Exception as e:
+ return JsonResponse({'status': 'error', 'message': f'Server error: {str(e)}'}, status=500)
+
+ # --- 2. Handle standard POST requests (e.g., saving form details) ---
+ elif 'save_form_details' in request.POST: # Changed the button name for clarity
+ form_details = ApplicantFormCreateForm(request.POST, instance=applicant_form)
+ if form_details.is_valid():
+ form_details.save()
+ messages.success(request, 'Form details updated successfully!')
+ return redirect('applicant:edit_form', form_id=form_id)
+
+ # Note: The 'add_field' branch is now redundant since we use the builder,
+ # but you can keep it if you want the old manual way too.
+
+ # --- GET Request (or unsuccessful POST) ---
+ form_details = ApplicantFormCreateForm(instance=applicant_form)
+ # Get initial fields to load into the JS builder
+ initial_fields_json = list(applicant_form.fields.values(
+ 'label', 'field_type', 'required', 'help_text', 'choices', 'order', 'field_name'
+ ))
+
+ return render(request, 'applicant/edit_form.html', {
+ 'applicant_form': applicant_form,
+ 'job': job,
+ 'form_details': form_details,
+ 'initial_fields_json': json.dumps(initial_fields_json)
+ })
+
+def delete_field(request, field_id):
+ """Delete a form field"""
+ field = get_object_or_404(FormField, id=field_id)
+ form_id = field.form.id
+ field.delete()
+ messages.success(request, 'Field deleted successfully!')
+ return redirect('applicant:edit_form', form_id=form_id)
+
+def activate_form(request, form_id):
+ """Activate a form (deactivates others automatically)"""
+ applicant_form = get_object_or_404(ApplicantForm, id=form_id)
+ applicant_form.activate()
+ messages.success(request, f'Form "{applicant_form.name}" is now active!')
+ return redirect('applicant:job_forms_list', job_id=applicant_form.job_posting.internal_job_id)
+
+# === PUBLIC VIEWS (for applicants) ===
+
+def apply_form_view(request, job_id):
+ """Public application form - serves active form"""
+ job = get_object_or_404(JobPosting, internal_job_id=job_id, status='ACTIVE')
+
+ if job.is_expired():
+ raise Http404("Application deadline has passed")
+
+ try:
+ applicant_form = job.applicant_forms.get(is_active=True)
+ except ApplicantForm.DoesNotExist:
+ raise Http404("No active application form configured for this job")
+
+ DynamicForm = create_dynamic_form(applicant_form)
+
+ if request.method == 'POST':
+ form = DynamicForm(request.POST)
+ if form.is_valid():
+ ApplicantSubmission.objects.create(
+ job_posting=job,
+ form=applicant_form,
+ data=form.cleaned_data,
+ ip_address=request.META.get('REMOTE_ADDR')
+ )
+ return redirect('applicant:thank_you', job_id=job_id)
+ else:
+ form = DynamicForm()
+
+ return render(request, 'applicant/apply_form.html', {
+ 'form': form,
+ 'job': job,
+ 'applicant_form': applicant_form
+ })
+
+def review_job_detail(request,job_id):
+ """Public job detail view for applicants"""
+ job = get_object_or_404(JobPosting, internal_job_id=job_id, status='ACTIVE')
+ if job.is_expired():
+ raise Http404("This job posting has expired.")
+ return render(request,'applicant/review_job_detail.html',{'job':job})
+
+
+
+
+def thank_you_view(request, job_id):
+ job = get_object_or_404(JobPosting, internal_job_id=job_id)
+ return render(request, 'applicant/thank_you.html', {'job': job})
\ No newline at end of file
diff --git a/static/image/hospital_logo.png b/static/image/hospital_logo.png
new file mode 100644
index 0000000..4250a35
Binary files /dev/null and b/static/image/hospital_logo.png differ
diff --git a/static/image/hospital_logo_1.png b/static/image/hospital_logo_1.png
new file mode 100644
index 0000000..ff44820
Binary files /dev/null and b/static/image/hospital_logo_1.png differ
diff --git a/static/image/hospital_logo_2.png b/static/image/hospital_logo_2.png
new file mode 100644
index 0000000..a1698d4
Binary files /dev/null and b/static/image/hospital_logo_2.png differ
diff --git a/static/image/hospital_logo_3.png b/static/image/hospital_logo_3.png
new file mode 100644
index 0000000..5a9e883
Binary files /dev/null and b/static/image/hospital_logo_3.png differ
diff --git a/static/image/vision.svg b/static/image/vision.svg
new file mode 100644
index 0000000..43156d8
--- /dev/null
+++ b/static/image/vision.svg
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/static/media/form_uploads/Experience_-_Midwife_-_LD_-_King_Abdullah_bin_Abdulaziz_University_Hospital_BlxPqWL.pdf b/static/media/form_uploads/Experience_-_Midwife_-_LD_-_King_Abdullah_bin_Abdulaziz_University_Hospital_BlxPqWL.pdf
new file mode 100644
index 0000000..38165e1
Binary files /dev/null and b/static/media/form_uploads/Experience_-_Midwife_-_LD_-_King_Abdullah_bin_Abdulaziz_University_Hospital_BlxPqWL.pdf differ
diff --git a/static/media/form_uploads/jitendra.pdf b/static/media/form_uploads/jitendra.pdf
new file mode 100644
index 0000000..fae2754
Binary files /dev/null and b/static/media/form_uploads/jitendra.pdf differ
diff --git a/static/media/form_uploads/resume_juanjosecarin.pdf b/static/media/form_uploads/resume_juanjosecarin.pdf
new file mode 100644
index 0000000..81b6fd7
Binary files /dev/null and b/static/media/form_uploads/resume_juanjosecarin.pdf differ
diff --git a/templates/admin/index.html b/templates/admin/index.html
deleted file mode 100644
index b4c0bd5..0000000
--- a/templates/admin/index.html
+++ /dev/null
@@ -1,86 +0,0 @@
-{% extends 'unfold/layouts/base.html' %}
-{% load i18n unfold %}
-
-{% block breadcrumbs %}{% endblock %}
-
-{% block title %}
- {% trans 'Dashboard' %} | {{ site_title|default:_('Django site admin') }}
-{% endblock %}
-
-{% block branding %}
-
-{% endblock %}
-
-{% block content_before %}
- {% component "unfold/helpers/header.html" %}
- {% trans "Recruitment Dashboard" %}
- {% endcomponent %}
-{% endblock %}
-
-{% block content %}
-
- {% component "unfold/components/card.html" with title="Total Jobs Posted" %}
-
{{ total_jobs }}
- {% endcomponent %}
-
- {% component "unfold/components/card.html" with title="Total Candidates" %}
-
{{ total_candidates }}
- {% endcomponent %}
-
- {% component "unfold/components/card.html" with title="Average Applications/Job" %}
-
{{ average_applications }}
- {% endcomponent %}
-
-
-
-
-
Applications Per Job
-
-
-
-
-
-
-{% endblock %}
\ No newline at end of file
diff --git a/templates/base.html b/templates/base.html
index a3ec383..1897f5a 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -1,4 +1,4 @@
-{% load static i18n %}
+{% comment %} {% load static i18n %}
@@ -9,16 +9,16 @@
{% comment %} {% endcomment %}
-
+ {% comment %} {% endcomment %}
-
+ {% comment %}
{% block extra_css %}{% endblock %}
-
- {% endcomment %}
+{% comment %}
@@ -91,9 +91,9 @@
-
+ {% endcomment %}
-
+ {% comment %}
{% if messages %}
{% for message in messages %}
@@ -107,12 +107,12 @@
{% block content %}
{% endblock %}
-
+ {% endcomment %}
- {% include 'includes/delete_modal.html' %}
+ {% comment %} {% include 'includes/delete_modal.html' %} {% endcomment %}
-
+ {% comment %}
- {% block extra_js %}{% endblock %}
+ {% block extra_js %}{% endblock %} {% endcomment %}
+
+
+{% load static %}
+
+
+
+
+
+
+
{% block title %}University ATS{% endblock %}
+
+
+
+
+
+
+
+
+ {% block customCSS %}{% endblock %}
+
+
+
+
+
+
+
+
+
+
+ info@kaauh.edu.sa
+
+
+
+ +966 11 820 0000
+
+
+
+
+
+
+
+
+
+
+
+
+
+ KAAUH ATS
+
+
+
+
+
+
+
+
+ {% if not request.session.linkedin_authenticated %}
+
+ {% else %}
+
+
+
+ LinkedIn Connected
+
+
+
+ {% endif %}
+
+
+
+
+
+
+
+ {% if messages %}
+ {% for message in messages %}
+
+ {{ message }}
+
+
+ {% endfor %}
+ {% endif %}
+
+ {% block content %}
+
+ {% endblock %}
+
+
+ {% include 'includes/delete_modal.html' %}
+
+
+
+
+
+ {% block customJS %}{% endblock %}
+
+
\ No newline at end of file