diff --git a/NorahUniversity/settings.py b/NorahUniversity/settings.py
index 5c00084..d741e46 100644
--- a/NorahUniversity/settings.py
+++ b/NorahUniversity/settings.py
@@ -135,9 +135,9 @@ WSGI_APPLICATION = 'NorahUniversity.wsgi.application'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
- 'NAME': 'norahuniversity',
- 'USER': 'norahuniversity',
- 'PASSWORD': 'norahuniversity',
+ 'NAME': 'haikal_db',
+ 'USER': 'faheed',
+ 'PASSWORD': 'Faheed@215',
'HOST': '127.0.0.1',
'PORT': '5432',
}
diff --git a/recruitment/forms.py b/recruitment/forms.py
index 93d73a1..faaeefb 100644
--- a/recruitment/forms.py
+++ b/recruitment/forms.py
@@ -290,6 +290,7 @@ class TrainingMaterialForm(forms.ModelForm):
)
)
+
class JobPostingForm(forms.ModelForm):
"""Form for creating and editing job postings"""
@@ -298,12 +299,13 @@ class JobPostingForm(forms.ModelForm):
fields = [
'title', 'department', 'job_type', 'workplace_type',
'location_city', 'location_state', 'location_country',
- 'description', 'qualifications', 'salary_range', 'benefits','application_start_date'
- ,'application_deadline', 'application_instructions',
- 'position_number', 'reporting_to', 'joining_date',
- 'created_by','open_positions','hash_tags','max_applications'
+ 'description', 'qualifications', 'salary_range', 'benefits',
+ 'application_deadline', 'application_instructions',
+ 'position_number', 'reporting_to',
+ 'open_positions', 'hash_tags', 'max_applications'
]
widgets = {
+ # Basic Information
'title': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Assistant Professor of Computer Science',
@@ -321,6 +323,8 @@ class JobPostingForm(forms.ModelForm):
'class': 'form-select',
'required': True
}),
+
+ # Location
'location_city': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Boston'
@@ -333,18 +337,25 @@ class JobPostingForm(forms.ModelForm):
'class': 'form-control',
'value': 'United States'
}),
+
'salary_range': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': '$60,000 - $80,000'
}),
- 'application_start_date': forms.DateInput(attrs={
- 'class': 'form-control',
- 'type': 'date'
- }),
+
+ # Application Information
+ # 'application_url': forms.URLInput(attrs={
+ # 'class': 'form-control',
+ # 'placeholder': 'https://university.edu/careers/job123',
+ # 'required': True
+ # }),
+
'application_deadline': forms.DateInput(attrs={
'class': 'form-control',
- 'type': 'date'
+ 'type': 'date',
+ 'required': True
}),
+
'open_positions': forms.NumberInput(attrs={
'class': 'form-control',
'min': 1,
@@ -353,7 +364,10 @@ class JobPostingForm(forms.ModelForm):
'hash_tags': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': '#hiring,#jobopening',
+ # 'validators':validate_hash_tags, # Assuming this is available
}),
+
+ # Internal Information
'position_number': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'UNIV-2025-001'
@@ -362,14 +376,7 @@ class JobPostingForm(forms.ModelForm):
'class': 'form-control',
'placeholder': 'Department Chair, Director, etc.'
}),
- 'joining_date': forms.DateInput(attrs={
- 'class': 'form-control',
- 'type': 'date'
- }),
- 'created_by': forms.TextInput(attrs={
- 'class': 'form-control',
- 'placeholder': 'University Administrator'
- }),
+
'max_applications': forms.NumberInput(attrs={
'class': 'form-control',
'min': 1,
@@ -377,40 +384,51 @@ class JobPostingForm(forms.ModelForm):
}),
}
- def __init__(self,*args,**kwargs):
- self.is_anonymous_user = kwargs.pop('is_anonymous_user', False)
+ def __init__(self, *args, **kwargs):
+
+ # Now call the parent __init__ with remaining args
super().__init__(*args, **kwargs)
- if not self.instance.pk:
- if not self.is_anonymous_user:
- self.fields['created_by'].initial = 'University Administrator'
- self.fields['location_city'].initial='Riyadh'
- self.fields['location_state'].initial='Riyadh Province'
- self.fields['location_country'].initial='Saudi Arabia'
+ if not self.instance.pk: # Creating new job posting
+ # self.fields['status'].initial = 'Draft'
+ self.fields['location_city'].initial = 'Riyadh'
+ self.fields['location_state'].initial = 'Riyadh Province'
+ self.fields['location_country'].initial = 'Saudi Arabia'
def clean_hash_tags(self):
- hash_tags=self.cleaned_data.get('hash_tags')
+ hash_tags = self.cleaned_data.get('hash_tags')
if hash_tags:
- tags=[tag.strip() for tag in hash_tags.split(',') if tag.strip()]
+ tags = [tag.strip() for tag in hash_tags.split(',') if tag.strip()]
for tag in tags:
if not tag.startswith('#'):
- raise forms.ValidationError("Each hashtag must start with '#' symbol and must be comma(,) sepearted.")
+ raise forms.ValidationError(
+ "Each hashtag must start with '#' symbol and must be comma(,) sepearted.")
return ','.join(tags)
- return hash_tags
+ return hash_tags # Allow blank
def clean_title(self):
- title=self.cleaned_data.get('title')
- if not title or len(title.strip())<3:
+ title = self.cleaned_data.get('title')
+ if not title or len(title.strip()) < 3:
raise forms.ValidationError("Job title must be at least 3 characters long.")
- if len(title)>200:
+ if len(title) > 200:
raise forms.ValidationError("Job title cannot exceed 200 characters.")
return title.strip()
def clean_description(self):
- description=self.cleaned_data.get('description')
- if not description or len(description.strip())<20:
+ description = self.cleaned_data.get('description')
+ if not description or len(description.strip()) < 20:
raise forms.ValidationError("Job description must be at least 20 characters long.")
- return description.strip()
+ return description.strip() # to remove leading/trailing whitespace
+
+ def clean_application_url(self):
+ url = self.cleaned_data.get('application_url')
+ if url:
+ validator = URLValidator()
+ try:
+ validator(url)
+ except forms.ValidationError:
+ raise forms.ValidationError('Please enter a valid URL (e.g., https://example.com)')
+ return url
class JobPostingImageForm(forms.ModelForm):
class Meta:
diff --git a/recruitment/models.py b/recruitment/models.py
index 91a1fb6..abae095 100644
--- a/recruitment/models.py
+++ b/recruitment/models.py
@@ -33,6 +33,7 @@ class Profile(models.Model):
def __str__(self):
return f"image for user {self.user}"
+
class JobPosting(Base):
# Basic Job Information
JOB_TYPES = [
@@ -66,16 +67,16 @@ class JobPosting(Base):
# Job Details
description = CKEditor5Field(
'Description',
- config_name='extends' # Matches the config name you defined in settings.py
+ config_name='extends' # Matches the config name you defined in settings.py
)
- qualifications = CKEditor5Field(blank=True,null=True,
- config_name='extends'
- )
+ qualifications = CKEditor5Field(blank=True, null=True,
+ config_name='extends'
+ )
salary_range = models.CharField(
max_length=200, blank=True, help_text="e.g., $60,000 - $80,000"
)
- benefits = CKEditor5Field(blank=True,null=True,config_name='extends')
+ benefits = CKEditor5Field(blank=True, null=True, config_name='extends')
# Application Information ---job detail apply link for the candidates
application_url = models.URLField(
@@ -84,10 +85,10 @@ class JobPosting(Base):
null=True,
blank=True,
)
- application_start_date=models.DateField(null=True, blank=True)
- application_deadline = models.DateField(db_index=True, null=True, blank=True) # Added index
- application_instructions =CKEditor5Field(
- blank=True, null=True,config_name='extends'
+
+ application_deadline = models.DateField(db_index=True) # Added index
+ application_instructions = CKEditor5Field(
+ blank=True, null=True, config_name='extends'
)
# Internal Tracking
@@ -105,7 +106,7 @@ class JobPosting(Base):
("ARCHIVED", "Archived"),
]
status = models.CharField(
- db_index=True, max_length=20, choices=STATUS_CHOICES, default="DRAFT" # Added index
+ db_index=True, max_length=20, choices=STATUS_CHOICES, default="DRAFT" # Added index
)
# hashtags for social media
@@ -129,7 +130,7 @@ class JobPosting(Base):
)
linkedin_posted_at = models.DateTimeField(null=True, blank=True)
- published_at = models.DateTimeField(db_index=True, null=True, blank=True) # Added index
+ published_at = models.DateTimeField(db_index=True, null=True, blank=True) # Added index
# University Specific Fields
position_number = models.CharField(
max_length=50, blank=True, help_text="University position number"
@@ -137,7 +138,7 @@ class JobPosting(Base):
reporting_to = models.CharField(
max_length=100, blank=True, help_text="Who this position reports to"
)
- joining_date = models.DateField(null=True, blank=True, help_text="Desired start date")
+
open_positions = models.PositiveIntegerField(
default=1, help_text="Number of open positions for this job"
)
@@ -149,11 +150,11 @@ class JobPosting(Base):
null=True,
blank=True,
help_text="The system or channel from which this job posting originated or was first published.",
- db_index=True # Explicitly index ForeignKey
+ db_index=True # Explicitly index ForeignKey
)
max_applications = models.PositiveIntegerField(
- default=1000, help_text="Maximum number of applications allowed",null=True,blank=True
- )
+ default=1000, help_text="Maximum number of applications allowed", null=True, blank=True
+ )
hiring_agency = models.ManyToManyField(
"HiringAgency",
blank=True,
@@ -181,7 +182,7 @@ class JobPosting(Base):
verbose_name = "Job Posting"
verbose_name_plural = "Job Postings"
indexes = [
- models.Index(fields=['status', 'created_at','title']),
+ models.Index(fields=['status', 'created_at', 'title']),
models.Index(fields=['slug']),
]
@@ -258,7 +259,6 @@ class JobPosting(Base):
# Return True if any content remains after stripping tags and spaces.
return bool(final_content)
-
@property
def has_description_content(self):
"""Returns True if the description field has meaningful content."""
@@ -277,6 +277,7 @@ class JobPosting(Base):
@property
def has_application_instructions_content(self):
return self._check_content(self.application_instructions)
+
@property
def current_applications_count(self):
"""Returns the current number of candidates associated with this job."""
@@ -289,9 +290,13 @@ class JobPosting(Base):
return True
return self.current_applications_count >= self.max_applications
+
@property
def all_candidates(self):
- return self.candidates.annotate(sortable_score=Coalesce(Cast('ai_analysis_data__analysis_data__match_score',output_field=IntegerField()),0)).order_by('-sortable_score')
+ return self.candidates.annotate(
+ sortable_score=Coalesce(Cast('ai_analysis_data__analysis_data__match_score', output_field=IntegerField()),
+ 0)).order_by('-sortable_score')
+
@property
def screening_candidates(self):
return self.all_candidates.filter(stage="Applied")
@@ -299,6 +304,7 @@ class JobPosting(Base):
@property
def exam_candidates(self):
return self.all_candidates.filter(stage="Exam")
+
@property
def interview_candidates(self):
return self.all_candidates.filter(stage="Interview")
@@ -307,11 +313,13 @@ class JobPosting(Base):
def offer_candidates(self):
return self.all_candidates.filter(stage="Offer")
-
- #counts
+ # counts
@property
def all_candidates_count(self):
- return self.candidates.annotate(sortable_score=Cast('ai_analysis_data__match_score',output_field=CharField())).order_by('-sortable_score').count()
+ return self.candidates.annotate(
+ sortable_score=Cast('ai_analysis_data__match_score', output_field=CharField())).order_by(
+ '-sortable_score').count()
+
@property
def screening_candidates_count(self):
return self.all_candidates.filter(stage="Applied").count()
@@ -319,6 +327,7 @@ class JobPosting(Base):
@property
def exam_candidates_count(self):
return self.all_candidates.filter(stage="Exam").count()
+
@property
def interview_candidates_count(self):
return self.all_candidates.filter(stage="Interview").count()
@@ -327,6 +336,7 @@ class JobPosting(Base):
def offer_candidates_count(self):
return self.all_candidates.filter(stage="Offer").count()
+
class JobPostingImage(models.Model):
job=models.OneToOneField('JobPosting',on_delete=models.CASCADE,related_name='post_images')
post_image = models.ImageField(upload_to='post/',validators=[validate_image_size])
diff --git a/recruitment/views.py b/recruitment/views.py
index 73fc246..3565fd1 100644
--- a/recruitment/views.py
+++ b/recruitment/views.py
@@ -273,21 +273,12 @@ def create_job(request):
if request.method == "POST":
form = JobPostingForm(
- request.POST, is_anonymous_user=not request.user.is_authenticated
+ request.POST
)
# to check user is authenticated or not
if form.is_valid():
try:
job = form.save(commit=False)
- if request.user.is_authenticated:
- job.created_by = (
- request.user.get_full_name() or request.user.username
- )
- else:
- job.created_by = request.POST.get("created_by", "").strip()
- if not job.created_by:
- job.created_by = request.user.username
-
job.save()
job_apply_url_relative=reverse('job_detail_candidate',kwargs={'slug':job.slug})
job_apply_url_absolute=request.build_absolute_uri(job_apply_url_relative)
@@ -302,7 +293,7 @@ def create_job(request):
else:
messages.error(request, f"Please correct the errors below.{form.errors}")
else:
- form = JobPostingForm(is_anonymous_user=not request.user.is_authenticated)
+ form = JobPostingForm()
return render(request, "jobs/create_job.html", {"form": form})
@@ -313,21 +304,11 @@ def edit_job(request, slug):
if request.method == "POST":
form = JobPostingForm(
request.POST,
- instance=job,
- is_anonymous_user=not request.user.is_authenticated,
+ instance=job
)
if form.is_valid():
try:
- job = form.save(commit=False)
- if request.user.is_authenticated:
- job.created_by = (
- request.user.get_full_name() or request.user.username
- )
- else:
- job.created_by = request.POST.get("created_by", "").strip()
- if not job.created_by:
- job.created_by = "University Administrator"
- job.save()
+ form.save()
messages.success(request, f'Job "{job.title}" updated successfully!')
return redirect("job_list")
except Exception as e:
@@ -338,7 +319,7 @@ def edit_job(request, slug):
else:
job = get_object_or_404(JobPosting, slug=slug)
form = JobPostingForm(
- instance=job, is_anonymous_user=not request.user.is_authenticated
+ instance=job
)
return render(request, "jobs/edit_job.html", {"form": form, "job": job})
diff --git a/templates/jobs/create_job.html b/templates/jobs/create_job.html
index 5e3fa74..51cf403 100644
--- a/templates/jobs/create_job.html
+++ b/templates/jobs/create_job.html
@@ -71,7 +71,7 @@
border: 1px solid #ced4da;
width: 100%;
padding: 0.375rem 0.75rem;
- box-sizing: border-box;
+ box-sizing: border-box;
}
/* ================================================= */
@@ -100,7 +100,7 @@
{% endblock %}
{% block content %}
-
+
{% if form.instance.pk %} {% trans "Edit Job Posting" %} {% else %} {% trans "Create New Job Posting" %} {% endif %}
@@ -150,8 +150,113 @@
+
{# ================================================= #}
- {# SECTION 2: JOB CONTENT (CKEDITOR 5 Fields) #}
+ {# SECTION 2: INTERNAL AND PROMOTION #}
+ {# ================================================= #}
+
+
+
+
+
+
+
+
{% trans "Position Number" %}
+ {{ form.position_number }}
+ {% if form.position_number.errors %}
{{ form.position_number.errors }}
{% endif %}
+
+
+
+
+
{% trans "Reports To" %}
+ {{ form.reporting_to }}
+ {% if form.reporting_to.errors %}
{{ form.reporting_to.errors }}
{% endif %}
+
+
+
+
+
{% trans "Open Positions" %}
+ {{ form.open_positions }}
+ {% if form.open_positions.errors %}
{{ form.open_positions.errors }}
{% endif %}
+
+
+
+
+
{% trans "Max Applications" %}
+ {{ form.max_applications }}
+ {% if form.max_applications.errors %}
{{ form.max_applications.errors }}
{% endif %}
+
+
+
+
+
+
+
{% trans "Hashtags (For Promotion/Search on Linkedin)" %}
+ {{ form.hash_tags }}
+ {% if form.hash_tags.errors %}
{{ form.hash_tags.errors }}
{% endif %}
+
{% trans "Comma-separated list of hashtags, e.g., #hiring, #professor" %}
+
+
+
+
+
+
+
+ {# ================================================= #}
+ {# SECTION 3: LOCATION AND DATES #}
+ {# ================================================= #}
+
+
+
+
+
+
+
+
{% trans "City" %}
+ {{ form.location_city }}
+ {% if form.location_city.errors %}
{{ form.location_city.errors }}
{% endif %}
+
+
+
+
+
{% trans "State/Province" %}
+ {{ form.location_state }}
+ {% if form.location_state.errors %}
{{ form.location_state.errors }}
{% endif %}
+
+
+
+
+
{% trans "Country" %}
+ {{ form.location_country }}
+ {% if form.location_country.errors %}
{{ form.location_country.errors }}
{% endif %}
+
+
+
+
+
+
{% trans "Application Deadline" %}*
+ {{ form.application_deadline }}
+ {% if form.application_deadline.errors %}
{{ form.application_deadline.errors }}
{% endif %}
+
+
+
+
+
+
{% trans "Salary Range" %}
+ {{ form.salary_range }}
+ {% if form.salary_range.errors %}
{{ form.salary_range.errors }}
{% endif %}
+
+
+
+
+
+
+ {# ================================================= #}
+ {# SECTION 4: JOB CONTENT (CKEDITOR 5 Fields) #}
{# ================================================= #}
{# ================================================= #}
- {# SECTION 3: COMPENSATION AND APPLICATION #}
+ {# SECTION 5: APPLICATION Instructions #}
{# ================================================= #}
-
-
-
{% trans "Salary Range" %}
- {{ form.salary_range }}
- {% if form.salary_range.errors %}
{{ form.salary_range.errors }}
{% endif %}
-
-
+
{% comment %} (application_url comment removed for brevity) {% endcomment %}
@@ -215,117 +314,7 @@
- {# ================================================= #}
- {# SECTION 4: LOCATION AND DATES #}
- {# ================================================= #}
-
-
-
-
-
-
-
{% trans "City" %}
- {{ form.location_city }}
- {% if form.location_city.errors %}
{{ form.location_city.errors }}
{% endif %}
-
-
-
-
-
{% trans "State/Province" %}
- {{ form.location_state }}
- {% if form.location_state.errors %}
{{ form.location_state.errors }}
{% endif %}
-
-
-
-
-
{% trans "Country" %}
- {{ form.location_country }}
- {% if form.location_country.errors %}
{{ form.location_country.errors }}
{% endif %}
-
-
-
-
-
{% trans "Application Deadline" %}
- {{ form.application_deadline }}
- {% if form.application_deadline.errors %}
{{ form.application_deadline.errors }}
{% endif %}
-
-
-
-
-
{% trans "Application Start Date" %}
- {{ form.application_start_date }}
- {% if form.application_start_date.errors %}
{{ form.application_start_date.errors }}
{% endif %}
-
-
-
-
-
{% trans "Desired Joining Date" %}
- {{ form.joining_date }}
- {% if form.joining_date.errors %}
{{ form.joining_date.errors }}
{% endif %}
-
-
-
-
-
-
- {# ================================================= #}
- {# SECTION 5: INTERNAL AND PROMOTION #}
- {# ================================================= #}
-
-
-
-
-
-
-
{% trans "Position Number" %}
- {{ form.position_number }}
- {% if form.position_number.errors %}
{{ form.position_number.errors }}
{% endif %}
-
-
-
-
-
{% trans "Reports To" %}
- {{ form.reporting_to }}
- {% if form.reporting_to.errors %}
{{ form.reporting_to.errors }}
{% endif %}
-
-
-
-
-
{% trans "Open Positions" %}
- {{ form.open_positions }}
- {% if form.open_positions.errors %}
{{ form.open_positions.errors }}
{% endif %}
-
-
-
-
-
{% trans "Max Applications" %}
- {{ form.max_applications }}
- {% if form.max_applications.errors %}
{{ form.max_applications.errors }}
{% endif %}
-
-
-
-
-
{% trans "Created By" %}
- {{ form.created_by }}
- {% if form.created_by.errors %}
{{ form.created_by.errors }}
{% endif %}
-
-
-
-
-
{% trans "Hashtags (For Promotion/Search)" %}
- {{ form.hash_tags }}
- {% if form.hash_tags.errors %}
{{ form.hash_tags.errors }}
{% endif %}
-
{% trans "Comma-separated list of hashtags, e.g., #hiring, #professor" %}
-
-
-
-
-
{# ================================================= #}
{# ACTION BUTTONS #}
diff --git a/templates/jobs/edit_job.html b/templates/jobs/edit_job.html
index 5e3fa74..51cf403 100644
--- a/templates/jobs/edit_job.html
+++ b/templates/jobs/edit_job.html
@@ -71,7 +71,7 @@
border: 1px solid #ced4da;
width: 100%;
padding: 0.375rem 0.75rem;
- box-sizing: border-box;
+ box-sizing: border-box;
}
/* ================================================= */
@@ -100,7 +100,7 @@
{% endblock %}
{% block content %}
-
+
{% if form.instance.pk %} {% trans "Edit Job Posting" %} {% else %} {% trans "Create New Job Posting" %} {% endif %}
@@ -150,8 +150,113 @@
+
{# ================================================= #}
- {# SECTION 2: JOB CONTENT (CKEDITOR 5 Fields) #}
+ {# SECTION 2: INTERNAL AND PROMOTION #}
+ {# ================================================= #}
+
+
+
+
+
+
+
+
{% trans "Position Number" %}
+ {{ form.position_number }}
+ {% if form.position_number.errors %}
{{ form.position_number.errors }}
{% endif %}
+
+
+
+
+
{% trans "Reports To" %}
+ {{ form.reporting_to }}
+ {% if form.reporting_to.errors %}
{{ form.reporting_to.errors }}
{% endif %}
+
+
+
+
+
{% trans "Open Positions" %}
+ {{ form.open_positions }}
+ {% if form.open_positions.errors %}
{{ form.open_positions.errors }}
{% endif %}
+
+
+
+
+
{% trans "Max Applications" %}
+ {{ form.max_applications }}
+ {% if form.max_applications.errors %}
{{ form.max_applications.errors }}
{% endif %}
+
+
+
+
+
+
+
{% trans "Hashtags (For Promotion/Search on Linkedin)" %}
+ {{ form.hash_tags }}
+ {% if form.hash_tags.errors %}
{{ form.hash_tags.errors }}
{% endif %}
+
{% trans "Comma-separated list of hashtags, e.g., #hiring, #professor" %}
+
+
+
+
+
+
+
+ {# ================================================= #}
+ {# SECTION 3: LOCATION AND DATES #}
+ {# ================================================= #}
+
+
+
+
+
+
+
+
{% trans "City" %}
+ {{ form.location_city }}
+ {% if form.location_city.errors %}
{{ form.location_city.errors }}
{% endif %}
+
+
+
+
+
{% trans "State/Province" %}
+ {{ form.location_state }}
+ {% if form.location_state.errors %}
{{ form.location_state.errors }}
{% endif %}
+
+
+
+
+
{% trans "Country" %}
+ {{ form.location_country }}
+ {% if form.location_country.errors %}
{{ form.location_country.errors }}
{% endif %}
+
+
+
+
+
+
{% trans "Application Deadline" %}*
+ {{ form.application_deadline }}
+ {% if form.application_deadline.errors %}
{{ form.application_deadline.errors }}
{% endif %}
+
+
+
+
+
+
{% trans "Salary Range" %}
+ {{ form.salary_range }}
+ {% if form.salary_range.errors %}
{{ form.salary_range.errors }}
{% endif %}
+
+
+
+
+
+
+ {# ================================================= #}
+ {# SECTION 4: JOB CONTENT (CKEDITOR 5 Fields) #}
{# ================================================= #}
{# ================================================= #}
- {# SECTION 3: COMPENSATION AND APPLICATION #}
+ {# SECTION 5: APPLICATION Instructions #}
{# ================================================= #}
-
-
-
{% trans "Salary Range" %}
- {{ form.salary_range }}
- {% if form.salary_range.errors %}
{{ form.salary_range.errors }}
{% endif %}
-
-
+
{% comment %} (application_url comment removed for brevity) {% endcomment %}
@@ -215,117 +314,7 @@
- {# ================================================= #}
- {# SECTION 4: LOCATION AND DATES #}
- {# ================================================= #}
-
-
-
-
-
-
-
{% trans "City" %}
- {{ form.location_city }}
- {% if form.location_city.errors %}
{{ form.location_city.errors }}
{% endif %}
-
-
-
-
-
{% trans "State/Province" %}
- {{ form.location_state }}
- {% if form.location_state.errors %}
{{ form.location_state.errors }}
{% endif %}
-
-
-
-
-
{% trans "Country" %}
- {{ form.location_country }}
- {% if form.location_country.errors %}
{{ form.location_country.errors }}
{% endif %}
-
-
-
-
-
{% trans "Application Deadline" %}
- {{ form.application_deadline }}
- {% if form.application_deadline.errors %}
{{ form.application_deadline.errors }}
{% endif %}
-
-
-
-
-
{% trans "Application Start Date" %}
- {{ form.application_start_date }}
- {% if form.application_start_date.errors %}
{{ form.application_start_date.errors }}
{% endif %}
-
-
-
-
-
{% trans "Desired Joining Date" %}
- {{ form.joining_date }}
- {% if form.joining_date.errors %}
{{ form.joining_date.errors }}
{% endif %}
-
-
-
-
-
-
- {# ================================================= #}
- {# SECTION 5: INTERNAL AND PROMOTION #}
- {# ================================================= #}
-
-
-
-
-
-
-
{% trans "Position Number" %}
- {{ form.position_number }}
- {% if form.position_number.errors %}
{{ form.position_number.errors }}
{% endif %}
-
-
-
-
-
{% trans "Reports To" %}
- {{ form.reporting_to }}
- {% if form.reporting_to.errors %}
{{ form.reporting_to.errors }}
{% endif %}
-
-
-
-
-
{% trans "Open Positions" %}
- {{ form.open_positions }}
- {% if form.open_positions.errors %}
{{ form.open_positions.errors }}
{% endif %}
-
-
-
-
-
{% trans "Max Applications" %}
- {{ form.max_applications }}
- {% if form.max_applications.errors %}
{{ form.max_applications.errors }}
{% endif %}
-
-
-
-
-
{% trans "Created By" %}
- {{ form.created_by }}
- {% if form.created_by.errors %}
{{ form.created_by.errors }}
{% endif %}
-
-
-
-
-
{% trans "Hashtags (For Promotion/Search)" %}
- {{ form.hash_tags }}
- {% if form.hash_tags.errors %}
{{ form.hash_tags.errors }}
{% endif %}
-
{% trans "Comma-separated list of hashtags, e.g., #hiring, #professor" %}
-
-
-
-
-
{# ================================================= #}
{# ACTION BUTTONS #}
diff --git a/templates/jobs/job_detail.html b/templates/jobs/job_detail.html
index 06ecf71..4b4ff5d 100644
--- a/templates/jobs/job_detail.html
+++ b/templates/jobs/job_detail.html
@@ -1,4 +1,3 @@
-
{% extends "base.html" %}
{% load i18n static %}
@@ -217,8 +216,17 @@
padding-top: 0.5rem;
}
-
-
+ /* Custom CSS for simplified stat card (from previous answer) */
+ .stats-grid .kpi-card {
+ border-left: 4px solid var(--kaauh-teal);
+ background-color: #f0faff;
+ }
+ .stats-grid .card-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ }
{% endblock %}
@@ -236,7 +244,7 @@
{# LEFT COLUMN: JOB DETAILS WITH TABS #}
-
+
{# HEADER SECTION #}
@@ -248,6 +256,7 @@
+ {# Corrected status badge logic to close the span correctly #}
{% if job.status == "ACTIVE" %}
{% elif job.status == "DRAFT" %}
@@ -262,6 +271,7 @@
{% endif %}
{{ job.get_status_display }}
+
@@ -281,13 +291,12 @@
{% trans "Description & Requirements" %}
- {% if job.application_instructions %}
-
- {% trans "Application" %}
+
+ {% trans "Application KPIs" %}
- {% endif %}
+
@@ -323,21 +332,20 @@
-
-
-
-
- {% trans "Share Public Link" %}
-
+
+ {# Replaced bulky SVG with simpler Font Awesome icon #}
+
+ {% trans "Share Public Link" %}
+
-
- {% trans "Copied!" %}
-
-
+
+ {% trans "Copied!" %}
+
+
{% trans "Financial & Timeline" %}
@@ -392,19 +400,74 @@
{{ job.benefits|safe}}
{% endif %}
-
-
- {# TAB 3 CONTENT: APPLICATION INSTRUCTIONS #}
- {% if job.application_instructions %}
-
-
+ {% if job.application_instructions %}
+
{% trans "Application Instructions" %}
{{ job.application_instructions|safe }}
+ {% endif %}
+
+
+
+ {# TAB 3 CONTENT: APPLICATION KPIS #}
+
+
+
+ {# 1. Job Avg. Score #}
+
+
+
+
+
{{ avg_match_score|floatformat:1 }}
+
{% trans "Avg. AI Score" %}
+
+
+
+
+ {# 2. High Potential Count #}
+
+
+
+
+
{{ high_potential_count }}
+
{% trans "High Potential" %}
+
+
+
+
+ {# 3. Avg. Time to Interview #}
+
+
+
+
+
{{ avg_t2i_days|floatformat:1 }}d
+
{% trans "Time to Interview" %}
+
+
+
+
+ {# 4. Avg. Exam Review Time #}
+
+
+
+
+
{{ avg_t_in_exam_days|floatformat:1 }}d
+
{% trans "Avg. Exam Review" %}
+
+
+
+
+
+
+ {% trans "KPIs based on completed applicant data." %}
+
+
- {% endif %}
+
+
+
{# FOOTER ACTIONS #}
@@ -423,7 +486,7 @@
{# RIGHT COLUMN: TABBED CARDS #}
-
+
{# New Card for Candidate Category Chart #}
@@ -440,44 +503,9 @@
-
-
-
{% trans "Applicant Tracking" %}
- {% include 'jobs/partials/applicant_tracking.html' %}
-
-
+ {# REMOVED: Standalone Applicant Tracking Card (It is now in a tab) #}
-
-
-
-
{{ avg_match_score|floatformat:1 }}
-
{% trans "Average AI Match Score (0-100)" %}
-
-
-
-
{{ high_potential_count }}
-
{% trans "Candidates with Score ≥ 75%" %}
-
-
-
-
{{ avg_t2i_days|floatformat:1 }}d
-
{% trans "Applied to Interview (Total Funnel Speed)" %}
-
-
-
-
{{ avg_t_in_exam_days|floatformat:1 }}d
-
{% trans "Days spent between Exam and Interview" %}
-
-
-
+
{# RIGHT TABS NAVIGATION #}
@@ -486,14 +514,19 @@
{% trans "Applicants" %}
+
+
+ {% trans "Tracking" %}
+
+
{% trans "Form Template" %}
-
- {% trans "Linkedin" %}
+
+ {% trans "LinkedIn" %}
@@ -503,34 +536,6 @@
{# TAB 1: APPLICANTS CONTENT #}
{% trans "Total Applicants" %} ({{ total_applicants }} )
- {% comment %} {% if total_applicants > 0 %}
-
-
-
-
{{ applied_count }}
-
{% trans "Applied" %}
-
-
-
-
-
{{ interview_count }}
-
{% trans "Interview" %}
-
-
-
-
-
{{ offer_count }}
-
{% trans "Offer" %}
-
-
-
-
-
- {% endif %} {% endcomment %}
- {# TAB 2: MANAGEMENT (LinkedIn & Forms) CONTENT #}
+ {# NEW TAB 2: APPLICANT TRACKING CONTENT #}
+
+
{% trans "Pipeline Stages" %}
+ {% include 'jobs/partials/applicant_tracking.html' %}
+
{% trans "View the number of candidates currently in each stage of the hiring pipeline." %}
+
+
+ {# TAB 3: MANAGEMENT (Form Template) CONTENT #}
- {# LinkedIn Integration (Content from old card) #}
-
-
- {# Applicant Form Management (Content from old card) #}
{% trans "Form Management" %}
- {# TAB 3: INTERNAL INFO CONTENT #}
-
+ {# TAB 4: LINKEDIN INTEGRATION CONTENT #}
+
{% trans "LinkedIn Integration" %}
{% if job.posted_to_linkedin %}
@@ -611,48 +619,6 @@
{% endif %}
-
- {% comment %} {# Applicant Form Management (Content from old card) #}
-
{% trans "Form Management" %} {% endcomment %}
- {% comment %}
-
-
- {# TAB 3: INTERNAL INFO CONTENT #}
-
-
{% trans "Internal Information" %}
-
-
{% trans "Internal Job ID:" %} {{ job.internal_job_id }}
-
{% trans "Created:" %} {{ job.created_at|date:"M d, Y" }}
-
{% trans "Last Updated:" %} {{ job.updated_at|date:"M d, Y" }}
- {% if job.reporting_to %}
-
{% trans "Reports To:" %} {{ job.reporting_to }}
- {% endif %}
-
-
-
@@ -660,10 +626,8 @@
-
{% include "jobs/partials/image_upload.html" %}
-
@@ -811,4 +775,4 @@
});
-{% endblock %}
+{% endblock %}
\ No newline at end of file
diff --git a/templates/jobs/job_list.html b/templates/jobs/job_list.html
index 06f76dd..89ecd1c 100644
--- a/templates/jobs/job_list.html
+++ b/templates/jobs/job_list.html
@@ -278,7 +278,7 @@
- {% trans "All Applicants" %}
+ {% trans "All" %}
{% trans "Screened" %}
{% trans "Exam" %}
{% trans "Interview" %}
@@ -386,7 +386,7 @@
{# --- END OF JOB LIST CONTAINER --- #}
{% include "includes/paginator.html" %}
{% if not jobs and not job_list_data and not page_obj %}
-
+
{% trans "No job postings found" %}
diff --git a/templates/meetings/meeting_details.html b/templates/meetings/meeting_details.html
index 050cdf5..9216e1e 100644
--- a/templates/meetings/meeting_details.html
+++ b/templates/meetings/meeting_details.html
@@ -1,10 +1,9 @@
-
{% extends 'base.html' %}
{% load static i18n %}
{% block customCSS %}
{% endblock %}
{% block content %}
-
{{ comment.content|safe }}
-{{ comment.content|safe }}
+No comments yet. Be the first to comment!
+{% trans "No comments yet. Be the first to comment!" %}
{% endif %}