third refactoring for candidate in ai_ovverview branch

This commit is contained in:
Faheed 2025-11-27 15:44:39 +03:00
parent 8b51416e10
commit 2486eb791e
18 changed files with 174 additions and 172 deletions

View File

@ -25,7 +25,7 @@ urlpatterns = [
path('application/<slug:template_slug>/', views.application_submit_form, name='application_submit_form'), path('application/<slug:template_slug>/', views.application_submit_form, name='application_submit_form'),
path('application/<slug:template_slug>/submit/', views.application_submit, name='application_submit'), path('application/<slug:template_slug>/submit/', views.application_submit, name='application_submit'),
path('application/<slug:slug>/apply/', views.application_detail, name='application_detail'), path('application/<slug:slug>/apply/', views.job_application_detail, name='job_application_detail'),
path('application/<slug:slug>/signup/', views.application_signup, name='application_signup'), path('application/<slug:slug>/signup/', views.application_signup, name='application_signup'),
path('application/<slug:slug>/success/', views.application_success, name='application_success'), path('application/<slug:slug>/success/', views.application_success, name='application_success'),
# path('application/applicant/profile', views.applicant_profile, name='applicant_profile'), # path('application/applicant/profile', views.applicant_profile, name='applicant_profile'),

View File

@ -293,6 +293,7 @@ class ApplicationForm(forms.ModelForm):
"resume", "resume",
] ]
labels = { labels = {
"person":_("Applicant"),
"resume": _("Resume"), "resume": _("Resume"),
"hiring_source": _("Hiring Type"), "hiring_source": _("Hiring Type"),
"hiring_agency": _("Hiring Agency"), "hiring_agency": _("Hiring Agency"),
@ -903,7 +904,7 @@ class FormTemplateIsActiveForm(forms.ModelForm):
fields = ["is_active"] fields = ["is_active"]
class CandidateExamDateForm(forms.ModelForm): class ApplicationExamDateForm(forms.ModelForm):
class Meta: class Meta:
model = Application model = Application
fields = ["exam_date"] fields = ["exam_date"]

View File

@ -23,7 +23,7 @@ from .views import (
applications_exam_view, applications_interview_view, api_schedule_application_meeting applications_exam_view, applications_interview_view, api_schedule_application_meeting
) )
from .views_frontend import CandidateListView, JobListView from .views_frontend import CandidateListView, JobListView
from .utils import create_zoom_meeting, get_candidates_from_request from .utils import create_zoom_meeting, get_applications_from_request
class BaseTestCase(TestCase): class BaseTestCase(TestCase):
@ -586,8 +586,8 @@ class UtilityFunctionTests(BaseTestCase):
self.assertEqual(result['status'], 'success') self.assertEqual(result['status'], 'success')
self.assertIn('meeting_id', result['meeting_details']) self.assertIn('meeting_id', result['meeting_details'])
def test_get_candidates_from_request(self): def get_applications_from_request(self):
"""Test the get_candidates_from_request utility function""" """Test the get_applications_from_request utility function"""
# This would be tested with a request that has candidate_ids # This would be tested with a request that has candidate_ids
pass pass

View File

@ -41,7 +41,7 @@ from .views import (
# from .views_frontend import CandidateListView, JobListView, JobCreateView # from .views_frontend import CandidateListView, JobListView, JobCreateView
from .utils import ( from .utils import (
create_zoom_meeting, delete_zoom_meeting, update_zoom_meeting, create_zoom_meeting, delete_zoom_meeting, update_zoom_meeting,
get_zoom_meeting_details, get_candidates_from_request, get_zoom_meeting_details, get_applications_from_request,
get_available_time_slots get_available_time_slots
) )
# from .zoom_api import ZoomAPIError # from .zoom_api import ZoomAPIError

View File

@ -194,7 +194,7 @@ urlpatterns = [
views_frontend.update_application_status, views_frontend.update_application_status,
name="update_application_status", name="update_application_status",
), ),
# Sync URLs # Sync URLs (check)
path( path(
"jobs/<slug:job_slug>/sync-hired-applications/", "jobs/<slug:job_slug>/sync-hired-applications/",
views_frontend.sync_hired_applications, views_frontend.sync_hired_applications,
@ -205,6 +205,8 @@ urlpatterns = [
views_frontend.test_source_connection, views_frontend.test_source_connection,
name="test_source_connection", name="test_source_connection",
), ),
path( path(
"jobs/<slug:slug>/<int:application_id>/reschedule_meeting_for_application/<int:meeting_id>/", "jobs/<slug:slug>/<int:application_id>/reschedule_meeting_for_application/<int:meeting_id>/",
views.reschedule_meeting_for_application, views.reschedule_meeting_for_application,
@ -357,7 +359,7 @@ urlpatterns = [
path("agencies/<slug:slug>/", views.agency_detail, name="agency_detail"), path("agencies/<slug:slug>/", views.agency_detail, name="agency_detail"),
path("agencies/<slug:slug>/update/", views.agency_update, name="agency_update"), path("agencies/<slug:slug>/update/", views.agency_update, name="agency_update"),
path("agencies/<slug:slug>/delete/", views.agency_delete, name="agency_delete"), path("agencies/<slug:slug>/delete/", views.agency_delete, name="agency_delete"),
path( path( #check the html of this url it is not used anywhere
"agencies/<slug:slug>/applications/", "agencies/<slug:slug>/applications/",
views.agency_applications, views.agency_applications,
name="agency_applications", name="agency_applications",
@ -369,12 +371,12 @@ urlpatterns = [
views.agency_assignment_list, views.agency_assignment_list,
name="agency_assignment_list", name="agency_assignment_list",
), ),
path( path( #check
"agency-assignments/create/", "agency-assignments/create/",
views.agency_assignment_create, views.agency_assignment_create,
name="agency_assignment_create", name="agency_assignment_create",
), ),
path( path(#check
"agency-assignments/<slug:slug>/create/", "agency-assignments/<slug:slug>/create/",
views.agency_assignment_create, views.agency_assignment_create,
name="agency_assignment_create", name="agency_assignment_create",
@ -423,7 +425,7 @@ urlpatterns = [
# path('admin/messages/<int:message_id>/mark-read/', views.admin_mark_message_read, name='admin_mark_message_read'), # path('admin/messages/<int:message_id>/mark-read/', views.admin_mark_message_read, name='admin_mark_message_read'),
# path('admin/messages/<int:message_id>/delete/', views.admin_delete_message, name='admin_delete_message'), # path('admin/messages/<int:message_id>/delete/', views.admin_delete_message, name='admin_delete_message'),
# Agency Portal URLs (for external agencies) # Agency Portal URLs (for external agencies)
path("portal/login/", views.agency_portal_login, name="agency_portal_login"), # path("portal/login/", views.agency_portal_login, name="agency_portal_login"),
path("portal/<int:pk>/reset/", views.portal_password_reset, name="portal_password_reset"), path("portal/<int:pk>/reset/", views.portal_password_reset, name="portal_password_reset"),
path( path(
"portal/dashboard/", "portal/dashboard/",

View File

@ -571,10 +571,10 @@ def json_to_markdown_table(data_list):
return markdown return markdown
def get_candidates_from_request(request): def get_applications_from_request(request):
for c in request.POST.items(): for c in request.POST.items():
try: try:
yield models.Candidate.objects.get(pk=c[0]) yield models.Application.objects.get(pk=c[0])
except Exception as e: except Exception as e:
logger.error(e) logger.error(e)
yield None yield None

View File

@ -61,7 +61,7 @@ from django.urls import reverse_lazy
from django.db.models import Count, Avg, F, Q from django.db.models import Count, Avg, F, Q
from .forms import ( from .forms import (
ZoomMeetingForm, ZoomMeetingForm,
CandidateExamDateForm, ApplicationExamDateForm,
JobPostingForm, JobPostingForm,
JobPostingImageForm, JobPostingImageForm,
InterviewNoteForm, InterviewNoteForm,
@ -98,7 +98,7 @@ from django.views.generic import (
from .utils import ( from .utils import (
create_zoom_meeting, create_zoom_meeting,
delete_zoom_meeting, delete_zoom_meeting,
get_candidates_from_request, get_applications_from_request,
update_meeting, update_meeting,
update_zoom_meeting, update_zoom_meeting,
get_zoom_meeting_details, get_zoom_meeting_details,
@ -864,9 +864,9 @@ def kaauh_career(request):
# job detail facing the candidate: # job detail facing the candidate:
def application_detail(request, slug): def job_application_detail(request, slug):
job = get_object_or_404(JobPosting, slug=slug) job = get_object_or_404(JobPosting, slug=slug)
return render(request, "applicant/application_detail.html", {"job": job}) return render(request, "applicant/job_application_detail.html", {"job": job})
@login_required @login_required
@ -1854,18 +1854,18 @@ def applications_exam_view(request, slug):
@staff_user_required @staff_user_required
def update_application_exam_status(request, slug): def update_application_exam_status(request, slug):
candidate = get_object_or_404(Application, slug=slug) application = get_object_or_404(Application, slug=slug)
if request.method == "POST": if request.method == "POST":
form = CandidateExamDateForm(request.POST, instance=candidate) form = ApplicationExamDateForm(request.POST, instance=application)
if form.is_valid(): if form.is_valid():
form.save() form.save()
return redirect("applications_exam_view", slug=candidate.job.slug) return redirect("applications_exam_view", slug=application.job.slug)
else: else:
form = CandidateExamDateForm(request.POST, instance=candidate) form = ApplicationExamDateForm(request.POST, instance=application)
return render( return render(
request, request,
"includes/candidate_exam_status_form.html", "includes/application_exam_status_form.html",
{"candidate": candidate, "form": form}, {"application": application, "form": form},
) )
@ -1874,17 +1874,17 @@ def bulk_update_application_exam_status(request, slug):
job = get_object_or_404(JobPosting, slug=slug) job = get_object_or_404(JobPosting, slug=slug)
status = request.headers.get("status") status = request.headers.get("status")
if status: if status:
for candidate in get_candidates_from_request(request): for application in get_applications_from_request(request):
try: try:
if status == "pass": if status == "pass":
candidate.exam_status = "Passed" application.exam_status = "Passed"
candidate.stage = "Interview" application.stage = "Interview"
else: else:
candidate.exam_status = "Failed" application.exam_status = "Failed"
candidate.save() application.save()
except Exception as e: except Exception as e:
print(e) print(e)
messages.success(request, f"Updated exam status selected candidates") messages.success(request, f"Updated exam status selected applications")
return redirect("applications_exam_view", slug=job.slug) return redirect("applications_exam_view", slug=job.slug)
@ -1897,13 +1897,13 @@ def application_criteria_view_htmx(request, pk):
@staff_user_required @staff_user_required
def application_set_exam_date(request, slug): def application_set_exam_date(request, slug):
candidate = get_object_or_404(Application, slug=slug) application = get_object_or_404(Application, slug=slug)
candidate.exam_date = timezone.now() application.exam_date = timezone.now()
candidate.save() application.save()
messages.success( messages.success(
request, f"Set exam date for {candidate.name} to {candidate.exam_date}" request, f"Set exam date for {application.name} to {application.exam_date}"
) )
return redirect("applications_screening_view", slug=candidate.job.slug) return redirect("applications_screening_view", slug=application.job.slug)
@staff_user_required @staff_user_required
@ -1911,9 +1911,9 @@ def application_update_status(request, slug):
job = get_object_or_404(JobPosting, slug=slug) job = get_object_or_404(JobPosting, slug=slug)
mark_as = request.POST.get("mark_as") mark_as = request.POST.get("mark_as")
if mark_as != "----------": if mark_as != "----------":
candidate_ids = request.POST.getlist("candidate_ids") application_ids = request.POST.getlist("candidate_ids")
print(candidate_ids)
if c := Application.objects.filter(pk__in=candidate_ids): if c := Application.objects.filter(pk__in=application_ids):
if mark_as == "Exam": if mark_as == "Exam":
print("exam") print("exam")
c.update( c.update(
@ -1979,7 +1979,7 @@ def application_update_status(request, slug):
else "Applicant", else "Applicant",
) )
messages.success(request, f"Candidates Updated") messages.success(request, f"Applications Updated")
response = HttpResponse(redirect("applications_screening_view", slug=job.slug)) response = HttpResponse(redirect("applications_screening_view", slug=job.slug))
response.headers["HX-Refresh"] = "true" response.headers["HX-Refresh"] = "true"
return response return response
@ -3710,22 +3710,22 @@ def agency_delete(request, slug):
@staff_user_required @staff_user_required
def agency_applications(request, slug): def agency_applications(request, slug):
"""View all candidates from a specific agency""" """View all applications from a specific agency"""
agency = get_object_or_404(HiringAgency, slug=slug) agency = get_object_or_404(HiringAgency, slug=slug)
candidates = Application.objects.filter(hiring_agency=agency).order_by( applications = Application.objects.filter(hiring_agency=agency).order_by(
"-created_at" "-created_at"
) )
# Filter by stage if provided # Filter by stage if provided
stage_filter = request.GET.get("stage") stage_filter = request.GET.get("stage")
if stage_filter: if stage_filter:
candidates = candidates.filter(stage=stage_filter) applications = applications.filter(stage=stage_filter)
# Get total candidates before pagination for accurate count # Get total applications before pagination for accurate count
total_candidates = candidates.count() total_applications = applications.count()
# Pagination # Pagination
paginator = Paginator(candidates, 20) # Show 20 candidates per page paginator = Paginator(applications, 20) # Show 20 applications per page
page_number = request.GET.get("page") page_number = request.GET.get("page")
page_obj = paginator.get_page(page_number) page_obj = paginator.get_page(page_number)
@ -3733,9 +3733,9 @@ def agency_applications(request, slug):
"agency": agency, "agency": agency,
"page_obj": page_obj, "page_obj": page_obj,
"stage_filter": stage_filter, "stage_filter": stage_filter,
"total_candidates": total_candidates, "total_applications": total_applications,
} }
return render(request, "recruitment/agency_candidates.html", context) return render(request, "recruitment/agency_applications.html", context)
# Agency Portal Management Views # Agency Portal Management Views
@ -3817,8 +3817,8 @@ def agency_assignment_detail(request, slug):
AgencyJobAssignment.objects.select_related("agency", "job"), slug=slug AgencyJobAssignment.objects.select_related("agency", "job"), slug=slug
) )
# Get candidates submitted by this agency for this job # Get applications submitted by this agency for this job
candidates = Application.objects.filter( applications = Application.objects.filter(
hiring_agency=assignment.agency, job=assignment.job hiring_agency=assignment.agency, job=assignment.job
).order_by("-created_at") ).order_by("-created_at")
@ -3827,21 +3827,21 @@ def agency_assignment_detail(request, slug):
# Get messages for this assignment # Get messages for this assignment
total_candidates = candidates.count() total_applications = applications.count()
max_candidates = assignment.max_candidates max_applications = assignment.max_candidates
circumference = 326.73 # 2 * π * r where r=52 circumference = 326.73 # 2 * π * r where r=52
if max_candidates > 0: if max_applications > 0:
progress_percentage = total_candidates / max_candidates progress_percentage = total_applications / max_applications
stroke_dashoffset = circumference - (circumference * progress_percentage) stroke_dashoffset = circumference - (circumference * progress_percentage)
else: else:
stroke_dashoffset = circumference stroke_dashoffset = circumference
context = { context = {
"assignment": assignment, "assignment": assignment,
"candidates": candidates, "applications": applications,
"access_link": access_link, "access_link": access_link,
"total_candidates": candidates.count(), "total_applications": applications.count(),
"stroke_dashoffset": stroke_dashoffset, "stroke_dashoffset": stroke_dashoffset,
} }
return render(request, "recruitment/agency_assignment_detail.html", context) return render(request, "recruitment/agency_assignment_detail.html", context)
@ -3973,40 +3973,40 @@ def portal_password_reset(request,pk):
for error in errors: for error in errors:
messages.error(request, f"{field}: {error}") messages.error(request, f"{field}: {error}")
# Agency Portal Views (for external agencies) # # Agency Portal Views (for external agencies)
def agency_portal_login(request): # def agency_portal_login(request):
"""Agency login page""" # """Agency login page"""
# if request.session.get("agency_assignment_id"): # # if request.session.get("agency_assignment_id"):
# return redirect("agency_portal_dashboard") # # return redirect("agency_portal_dashboard")
if request.method == "POST": # if request.method == "POST":
form = AgencyLoginForm(request.POST) # form = AgencyLoginForm(request.POST)
if form.is_valid(): # if form.is_valid():
# Check if validated_access_link attribute exists # # Check if validated_access_link attribute exists
# if hasattr(form, "validated_access_link"): # # if hasattr(form, "validated_access_link"):
# access_link = form.validated_access_link # # access_link = form.validated_access_link
# access_link.record_access() # # access_link.record_access()
# Store assignment in session # # Store assignment in session
# request.session["agency_assignment_id"] = access_link.assignment.id # # request.session["agency_assignment_id"] = access_link.assignment.id
# request.session["agency_name"] = access_link.assignment.agency.name # # request.session["agency_name"] = access_link.assignment.agency.name
messages.success(request, f"Welcome, {access_link.assignment.agency.name}!") # messages.success(request, f"Welcome, {access_link.assignment.agency.name}!")
return redirect("agency_portal_dashboard") # return redirect("agency_portal_dashboard")
else: # else:
messages.error(request, "Invalid token or password.") # messages.error(request, "Invalid token or password.")
else: # else:
form = AgencyLoginForm() # form = AgencyLoginForm()
context = { # context = {
"form": form, # "form": form,
} # }
return render(request, "recruitment/agency_portal_login.html", context) # return render(request, "recruitment/agency_portal_login.html", context)
def portal_login(request): def portal_login(request):
"""Unified portal login for agency and candidate""" """Unified portal login for agency and applicant"""
if request.user.is_authenticated: if request.user.is_authenticated:
if request.user.user_type == "agency": if request.user.user_type == "agency":
return redirect("agency_portal_dashboard") return redirect("agency_portal_dashboard")
@ -4077,24 +4077,24 @@ def portal_login(request):
@login_required @login_required
@candidate_user_required @candidate_user_required
def applicant_portal_dashboard(request): def applicant_portal_dashboard(request):
"""Candidate portal dashboard""" """applicant portal dashboard"""
if not request.user.is_authenticated: if not request.user.is_authenticated:
return redirect("account_login") return redirect("account_login")
# Get candidate profile (Person record) # Get candidate profile (Person record)
try: try:
candidate = request.user.person_profile applicant = request.user.person_profile
except: except:
messages.error(request, "No candidate profile found.") messages.error(request, "No candidate profile found.")
return redirect("account_login") return redirect("account_login")
# Get candidate's applications with related job data # Get candidate's applications with related job data
applications = Application.objects.filter( applications = Application.objects.filter(
person=candidate person=applicant
).select_related('job').order_by('-created_at') ).select_related('job').order_by('-created_at')
# Get candidate's documents using the Person documents property # Get candidate's documents using the Person documents property
documents = candidate.documents.order_by('-created_at') documents = applicant.documents.order_by('-created_at')
# Add password change form for modal # Add password change form for modal
password_form = PasswordResetForm() password_form = PasswordResetForm()
@ -4104,13 +4104,13 @@ def applicant_portal_dashboard(request):
document_form = DocumentUploadForm() document_form = DocumentUploadForm()
context = { context = {
"candidate": candidate, "applicant": applicant,
"applications": applications, "applications": applications,
"documents": documents, "documents": documents,
"password_form": password_form, "password_form": password_form,
"document_form": document_form, "document_form": document_form,
} }
return render(request, "recruitment/candidate_profile.html", context) return render(request, "recruitment/applicant_profile.html", context)
@login_required @login_required
@ -4163,7 +4163,7 @@ def applicant_application_detail(request, slug):
"interviews": interviews, "interviews": interviews,
"documents": documents, "documents": documents,
} }
return render(request, "recruitment/candidate_application_detail.html", context) return render(request, "recruitment/applicant_application_detail.html", context)
@agency_user_required @agency_user_required
@ -4243,17 +4243,17 @@ def agency_portal_dashboard(request):
# Calculate statistics for each assignment # Calculate statistics for each assignment
assignment_stats = [] assignment_stats = []
for assignment in assignments: for assignment in assignments:
candidates = Application.objects.filter( applications = Application.objects.filter(
hiring_agency=agency, job=assignment.job hiring_agency=agency, job=assignment.job
).order_by("-created_at") ).order_by("-created_at")
unread_messages = 0 unread_messages = Message.objects.filter(job=assignment.job,recipient=agency.user,is_read=False).count()
assignment_stats.append( assignment_stats.append(
{ {
"assignment": assignment, "assignment": assignment,
"candidates": candidates, "applications": applications,
"candidate_count": candidates.count(), "application_count": applications.count(),
"unread_messages": unread_messages, "unread_messages": unread_messages,
"days_remaining": assignment.days_remaining, "days_remaining": assignment.days_remaining,
"is_active": assignment.is_currently_active, "is_active": assignment.is_currently_active,
@ -4262,7 +4262,7 @@ def agency_portal_dashboard(request):
) )
# Get overall statistics # Get overall statistics
total_candidates = sum(stats["candidate_count"] for stats in assignment_stats) total_applications = sum(stats["application_count"] for stats in assignment_stats)
total_unread_messages = sum(stats["unread_messages"] for stats in assignment_stats) total_unread_messages = sum(stats["unread_messages"] for stats in assignment_stats)
active_assignments = sum(1 for stats in assignment_stats if stats["is_active"]) active_assignments = sum(1 for stats in assignment_stats if stats["is_active"])
@ -4272,7 +4272,7 @@ def agency_portal_dashboard(request):
"assignment_stats": assignment_stats, "assignment_stats": assignment_stats,
"total_assignments": assignments.count(), "total_assignments": assignments.count(),
"active_assignments": active_assignments, "active_assignments": active_assignments,
"total_candidates": total_candidates, "total_applications": total_applications,
"total_unread_messages": total_unread_messages, "total_unread_messages": total_unread_messages,
} }
return render(request, "recruitment/agency_portal_dashboard.html", context) return render(request, "recruitment/agency_portal_dashboard.html", context)
@ -4280,7 +4280,7 @@ def agency_portal_dashboard(request):
@agency_user_required @agency_user_required
def agency_portal_submit_application_page(request, slug): def agency_portal_submit_application_page(request, slug):
"""Dedicated page for submitting a candidate""" """Dedicated page for submitting a application """
# assignment_id = request.session.get("agency_assignment_id") # assignment_id = request.session.get("agency_assignment_id")
# if not assignment_id: # if not assignment_id:
# return redirect("agency_portal_login") # return redirect("agency_portal_login")
@ -4296,7 +4296,7 @@ def agency_portal_submit_application_page(request, slug):
current_job=assignment.job current_job=assignment.job
if assignment.is_full: if assignment.is_full:
messages.error(request, "Maximum candidate limit reached for this assignment.") messages.error(request, "Maximum Application limit reached for this assignment.")
return redirect("agency_portal_assignment_detail", slug=assignment.slug) return redirect("agency_portal_assignment_detail", slug=assignment.slug)
# Verify this assignment belongs to the same agency as the logged-in session # Verify this assignment belongs to the same agency as the logged-in session
if assignment.agency.id != assignment.agency.id: if assignment.agency.id != assignment.agency.id:
@ -4309,11 +4309,11 @@ def agency_portal_submit_application_page(request, slug):
if not assignment.can_submit: if not assignment.can_submit:
messages.error( messages.error(
request, request,
"Cannot submit candidates: Assignment is not active, expired, or full.", "Cannot submit applications: Assignment is not active, expired, or full.",
) )
return redirect("agency_portal_assignment_detail", slug=assignment.slug) return redirect("agency_portal_assignment_detail", slug=assignment.slug)
# Get total submitted candidates for this assignment # Get total submitted applications for this assignment
total_submitted = Application.objects.filter( total_submitted = Application.objects.filter(
hiring_agency=assignment.agency, job=assignment.job hiring_agency=assignment.agency, job=assignment.job
).count() ).count()

View File

@ -25,7 +25,6 @@ from django.urls import reverse_lazy
from django.db.models import FloatField from django.db.models import FloatField
from django.db.models import F, IntegerField, Count, Avg, Sum, Q, ExpressionWrapper, fields, Value,CharField from django.db.models import F, IntegerField, Count, Avg, Sum, Q, ExpressionWrapper, fields, Value,CharField
from django.db.models.functions import Cast, Coalesce, TruncDate from django.db.models.functions import Cast, Coalesce, TruncDate
from django.contrib.auth.decorators import login_required
from django.shortcuts import render from django.shortcuts import render
from django.utils import timezone from django.utils import timezone
from datetime import timedelta from datetime import timedelta
@ -33,7 +32,7 @@ import json
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
# Add imports for user type restrictions # Add imports for user type restrictions
from recruitment.decorators import StaffRequiredMixin, staff_user_required from recruitment.decorators import StaffRequiredMixin, staff_user_required,candidate_user_required,staff_or_candidate_required
from datastar_py.django import ( from datastar_py.django import (
@ -907,7 +906,7 @@ def export_applications_csv(request, job_slug, stage):
@login_required @login_required
@staff_user_required @staff_user_required
def sync_hired_applications(request, job_slug): def sync_hired_applications(request, job_slug):
"""Sync hired candidates to external sources using Django-Q""" """Sync hired applications to external sources using Django-Q"""
from django_q.tasks import async_task from django_q.tasks import async_task
from .tasks import sync_hired_candidates_task from .tasks import sync_hired_candidates_task

View File

@ -191,7 +191,7 @@
{% for job in active_jobs %} {% for job in active_jobs %}
{# Optimized Job Listing Card #} {# Optimized Job Listing Card #}
<a href="{% url 'application_detail' job.slug %}" <a href="{% url 'job_application_detail' job.slug %}"
class="card d-block text-decoration-none text-dark job-listing-card p-4 border-2 shadow-hover transition-all"> class="card d-block text-decoration-none text-dark job-listing-card p-4 border-2 shadow-hover transition-all">
<div class="d-flex justify-content-between align-items-start mb-2"> <div class="d-flex justify-content-between align-items-start mb-2">

View File

@ -1,5 +1,5 @@
{% load i18n %} {% load i18n %}
{% url 'update_application_exam_status' slug=candidate.slug as url %} {% url 'update_application_exam_status' slug=application.slug as url %}
<form data-on-submit="@post('{{url}}', {contentType: 'form', headers: {'X-CSRFToken': '{{ csrf_token }}'}})"> <form data-on-submit="@post('{{url}}', {contentType: 'form', headers: {'X-CSRFToken': '{{ csrf_token }}'}})">
{% csrf_token %} {% csrf_token %}
{{ form.as_p }} {{ form.as_p }}

View File

@ -75,7 +75,7 @@
<div class="collapse navbar-collapse" id="agencyNavbar"> <div class="collapse navbar-collapse" id="agencyNavbar">
<div class="navbar-nav ms-auto"> <div class="navbar-nav ms-auto">
<li class="nav-item me-2"> <li class="nav-item me-2">
{% if LANGUAGE_CODE == 'en' %} {% if LANGUAGE_CODE == 'en' %}
<form action="{% url 'set_language' %}" method="post" class="d-inline">{% csrf_token %} <form action="{% url 'set_language' %}" method="post" class="d-inline">{% csrf_token %}
<input name="next" type="hidden" value="{{ request.get_full_path }}"> <input name="next" type="hidden" value="{{ request.get_full_path }}">
@ -120,7 +120,7 @@
{% comment %}
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<a class="nav-link dropdown-toggle text-white" href="#" role="button" data-bs-toggle="dropdown" <a class="nav-link dropdown-toggle text-white" href="#" role="button" data-bs-toggle="dropdown"
data-bs-offset="0, 8" aria-expanded="false" aria-label="{% trans 'Toggle language menu' %}"> data-bs-offset="0, 8" aria-expanded="false" aria-label="{% trans 'Toggle language menu' %}">
@ -145,8 +145,8 @@
</form> </form>
</li> </li>
</ul> </ul>
</li> </li> {% endcomment %}
{% endcomment %}
{% if request.user.is_authenticated %} {% if request.user.is_authenticated %}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link text-white" href="{% url 'user_detail' request.user.pk %}"> <a class="nav-link text-white" href="{% url 'user_detail' request.user.pk %}">

View File

@ -207,7 +207,7 @@
<div class="alert alert-info mx-2"> <div class="alert alert-info mx-2">
<i class="fas fa-info-circle me-2"></i> <i class="fas fa-info-circle me-2"></i>
{% trans "Share these credentials securely with the agency. They can use this information to log in and submit candidates." %} {% trans "Share these credentials securely with the agency. They can use this information to log in and submit applications." %}
</div> </div>
{% if access_link %} {% if access_link %}
@ -219,12 +219,12 @@
</div> </div>
</div> {% endcomment %} </div> {% endcomment %}
<!-- Candidates Card --> <!-- Applications Card -->
<div class="kaauh-card p-4"> <div class="kaauh-card p-4">
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="mb-0" style="color: var(--kaauh-teal-dark);"> <h5 class="mb-0" style="color: var(--kaauh-teal-dark);">
<i class="fas fa-users me-2"></i> <i class="fas fa-users me-2"></i>
{% trans "Submitted Candidates" %} ({{ total_candidates }}) {% trans "Submitted Applications" %} ({{ total_candidates }})
</h5> </h5>
{% if access_link %} {% if access_link %}
<a href="{% url 'agency_portal_login' %}" target="_blank" class="btn btn-outline-info btn-sm"> <a href="{% url 'agency_portal_login' %}" target="_blank" class="btn btn-outline-info btn-sm">
@ -233,12 +233,12 @@
{% endif %} {% endif %}
</div> </div>
{% if candidates %} {% if applications %}
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-hover"> <table class="table table-hover">
<thead> <thead>
<tr> <tr>
<th class="px-4 py-3 text-uppercase small fw-bold text-muted">{% trans "Candidate"%}</th> <th class="px-4 py-3 text-uppercase small fw-bold text-muted">{% trans "Application"%}</th>
<th class="px-4 py-3 text-uppercase small fw-bold text-muted">{% trans "Contact" %} <th class="px-4 py-3 text-uppercase small fw-bold text-muted">{% trans "Contact" %}
</th> </th>
<th class="px-4 py-3 text-uppercase small fw-bold text-muted">{% trans "Stage" %} <th class="px-4 py-3 text-uppercase small fw-bold text-muted">{% trans "Stage" %}
@ -248,36 +248,36 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for candidate in candidates %} {% for application in applications %}
<tr> <tr>
<td> <td>
<div class="fw-bold">{{ candidate.name }}</div> <div class="fw-bold">{{ application.name }}</div>
</td> </td>
<td> <td>
<div class="small"> <div class="small">
<div><i class="fas fa-envelope me-1"></i> {{ candidate.email }}</div> <div><i class="fas fa-envelope me-1"></i> {{ application.email }}</div>
<div><i class="fas fa-phone me-1"></i> {{ candidate.phone }}</div> <div><i class="fas fa-phone me-1"></i> {{ application.phone }}</div>
</div> </div>
</td> </td>
<td> <td>
<span class="badge bg-info">{{ candidate.get_stage_display }}</span> <span class="badge bg-info">{{ application.get_stage_display }}</span>
</td> </td>
<td> <td>
<div class="small text-muted"> <div class="small text-muted">
<div class="mb-1"><i class="fas fa-envelope me-2 w-20"></i> <div class="mb-1"><i class="fas fa-envelope me-2 w-20"></i>
{{candidate.email }}</div> {{application.email }}</div>
<div><i class="fas fa-phone me-2 w-20"></i>{{ candidate.phone }}</div> <div><i class="fas fa-phone me-2 w-20"></i>{{ application.phone }}</div>
</div> </div>
</td> </td>
<td class="px-4"> <td class="px-4">
<span class="badge bg-soft-info text-info rounded-pill px-3"> <span class="badge bg-soft-info text-info rounded-pill px-3">
{{candidate.get_stage_display }}</span> {{application.get_stage_display }}</span>
</td> </td>
<td class="px-4"> <td class="px-4">
<span class="small text-muted">{{ candidate.created_at|date:"M d, Y" }}</span> <span class="small text-muted">{{ application.created_at|date:"M d, Y" }}</span>
</td> </td>
<td class="px-4 text-end"> <td class="px-4 text-end">
<a href="{% url 'application_detail' candidate.slug %}" <a href="{% url 'application_detail' application.slug %}"
class="btn btn-sm btn-outline-primary" title="{% trans 'View Details' %}"> class="btn btn-sm btn-outline-primary" title="{% trans 'View Details' %}">
<i class="fas fa-eye"></i> <i class="fas fa-eye"></i>
</a> </a>
@ -290,9 +290,9 @@
{% else %} {% else %}
<div class="text-center py-4"> <div class="text-center py-4">
<i class="fas fa-users fa-2x text-muted mb-3"></i> <i class="fas fa-users fa-2x text-muted mb-3"></i>
<h6 class="text-muted">{% trans "No candidates submitted yet" %}</h6> <h6 class="text-muted">{% trans "No applications submitted yet" %}</h6>
<p class="text-muted small"> <p class="text-muted small">
{% trans "Candidates will appear here once the agency submits them through their portal." %} {% trans "Applications will appear here once the agency submits them through their portal." %}
</p> </p>
</div> </div>
{% endif %} {% endif %}
@ -335,7 +335,7 @@
<div class="text-center"> <div class="text-center">
<div class="h4 mb-1">{{ total_candidates }}</div> <div class="h4 mb-1">{{ total_candidates }}</div>
<div class="text-muted">/ {{ assignment.max_candidates }} {% trans "candidates" %}</div> <div class="text-muted">/ {{ assignment.max_candidates }} {% trans "applications" %}</div>
</div> </div>
<div class="progress mt-3" style="height: 8px;"> <div class="progress mt-3" style="height: 8px;">

View File

@ -50,7 +50,7 @@
</div> </div>
<div> <div>
{% comment %} <a href="{% url 'agency_portal_submit_application' %}" class="btn btn-main-action me-2"> {% comment %} <a href="{% url 'agency_portal_submit_application' %}" class="btn btn-main-action me-2">
<i class="fas fa-user-plus me-1"></i> {% trans "Submit Candidate" %} <i class="fas fa-user-plus me-1"></i> {% trans "Submit Application" %}
</a> </a>
<a href="#" class="btn btn-outline-secondary position-relative"> <a href="#" class="btn btn-outline-secondary position-relative">
<i class="fas fa-envelope me-1"></i> {% trans "Messages" %} <i class="fas fa-envelope me-1"></i> {% trans "Messages" %}
@ -93,8 +93,8 @@
<div class="text-info mb-2"> <div class="text-info mb-2">
<i class="fas fa-users fa-2x"></i> <i class="fas fa-users fa-2x"></i>
</div> </div>
<h4 class="card-title">{{ total_candidates }}</h4> <h4 class="card-title">{{ total_applications }}</h4>
<p class="card-text text-muted">{% trans "Total Candidates" %}</p> <p class="card-text text-muted">{% trans "Total Applications" %}</p>
</div> </div>
</div> </div>
</div> </div>
@ -169,8 +169,8 @@
</strong> </strong>
</div> </div>
<div class="col-6"> <div class="col-6">
<small class="text-muted d-block">{% trans "Candidates" %}</small> <small class="text-muted d-block">{% trans "Applications" %}</small>
<strong>{{ stats.candidate_count }} / {{ stats.assignment.max_candidates }}</strong> <strong>{{ stats.application_count }} / {{ stats.assignment.max_applications }}</strong>
</div> </div>
</div> </div>
@ -178,10 +178,10 @@
<div class="mb-3"> <div class="mb-3">
<div class="d-flex justify-content-between mb-1"> <div class="d-flex justify-content-between mb-1">
<small class="text-muted">{% trans "Submission Progress" %}</small> <small class="text-muted">{% trans "Submission Progress" %}</small>
<small class="text-muted">{{ stats.candidate_count }}/{{ stats.assignment.max_candidates }}</small> <small class="text-muted">{{ stats.application_count }}/{{ stats.assignment.max_applications }}</small>
</div> </div>
<div class="progress" style="height: 6px;"> <div class="progress" style="height: 6px;">
{% with progress=stats.candidate_count %} {% with progress=stats.application_count %}
<div class="progress-bar {% if progress >= 90 %}bg-danger{% elif progress >= 70 %}bg-warning{% else %}bg-success{% endif %}" <div class="progress-bar {% if progress >= 90 %}bg-danger{% elif progress >= 70 %}bg-warning{% else %}bg-success{% endif %}"
style="width: {{ progress|floatformat:0 }}%"></div> style="width: {{ progress|floatformat:0 }}%"></div>
{% endwith %} {% endwith %}
@ -194,7 +194,7 @@
{% if stats.can_submit %} {% if stats.can_submit %}
<a href="{% url 'agency_portal_submit_application_page' stats.assignment.slug %}" <a href="{% url 'agency_portal_submit_application_page' stats.assignment.slug %}"
class="btn btn-sm btn-main-action"> class="btn btn-sm btn-main-action">
<i class="fas fa-user-plus me-1"></i> {% trans "Submit Candidate" %} <i class="fas fa-user-plus me-1"></i> {% trans "Submit Application" %}
</a> </a>
{% else %} {% else %}
<button class="btn btn-sm btn-secondary" disabled> <button class="btn btn-sm btn-secondary" disabled>

View File

@ -1,7 +1,7 @@
{% extends 'portal_base.html' %} {% extends 'portal_base.html' %}
{% load static i18n crispy_forms_tags %} {% load static i18n crispy_forms_tags %}
{% block title %}{% trans "Submit Candidate" %} - {{ assignment.job.title }} - Agency Portal{% endblock %} {% block title %}{% trans "Submit Application" %} - {{ assignment.job.title }} - Agency Portal{% endblock %}
{% block customCSS %} {% block customCSS %}
<style> <style>
@ -97,13 +97,13 @@
</li> </li>
{% comment %} <li class="breadcrumb-item active" aria-current="page"> {% comment %} <li class="breadcrumb-item active" aria-current="page">
{% trans "Submit Candidate" %} {% trans "Submit Application" %}
</li> {% endcomment %} </li> {% endcomment %}
<li class="breadcrumb-item active" aria-current="page" style=" <li class="breadcrumb-item active" aria-current="page" style="
color: #F43B5E; /* Rosy Accent Color */ color: #F43B5E; /* Rosy Accent Color */
font-weight: 600; "> font-weight: 600; ">
{% trans "Submit Candidate" %}</li> {% trans "Submit Application" %}</li>
</ol> </ol>
</nav> </nav>
@ -112,11 +112,11 @@
<div> <div>
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;"> <h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;">
<i class="fas fa-user-plus me-2"></i> <i class="fas fa-user-plus me-2"></i>
{% trans "Submit New Candidate" %} {% trans "Submit New Application" %}
</h1> </h1>
<p class="text-muted mb-0"> <p class="text-muted mb-0">
<!-- Button trigger modal --> <!-- Button trigger modal -->
{% trans "Submit a candidate for" %} {% trans "Submit a Application for" %}
{{ assignment.job.title }} {{ assignment.job.title }}
</p> </p>
@ -171,16 +171,16 @@
<div class="kaauh-card p-4"> <div class="kaauh-card p-4">
<h5 class="mb-4" style="color: var(--kaauh-teal-dark);"> <h5 class="mb-4" style="color: var(--kaauh-teal-dark);">
<i class="fas fa-user-edit me-2"></i> <i class="fas fa-user-edit me-2"></i>
{% trans "Candidate Information" %} {% trans "Application Information" %}
</h5> </h5>
<form method="post" enctype="multipart/form-data" id="candidateForm" <form method="post" enctype="multipart/form-data" id="ApplicationForm"
action="{% url 'agency_portal_submit_application_page' assignment.slug %}"> action="{% url 'agency_portal_submit_application_page' assignment.slug %}">
{% csrf_token %} {% csrf_token %}
{{form|crispy}} {{form|crispy}}
<button type="submit" class="btn btn-main-action"> <button type="submit" class="btn btn-main-action">
<i class="fas fa-user-plus me-2"></i> <i class="fas fa-user-plus me-2"></i>
{% trans "Submit Candidate" %} {% trans "Submit Application" %}
</button> </button>
</form> </form>
</div> </div>
@ -188,14 +188,14 @@
<div class="kaauh-card p-4"> <div class="kaauh-card p-4">
<div class="text-center py-5"> <div class="text-center py-5">
<i class="fas fa-exclamation-triangle fa-4x text-warning mb-4"></i> <i class="fas fa-exclamation-triangle fa-4x text-warning mb-4"></i>
<h4 class="text-warning mb-3">{% trans "Cannot Submit Candidates" %}</h4> <h4 class="text-warning mb-3">{% trans "Cannot Submit Applications" %}</h4>
<div class="alert alert-warning d-inline-block"> <div class="alert alert-warning d-inline-block">
{% if assignment.is_expired %} {% if assignment.is_expired %}
<i class="fas fa-clock me-2"></i> <i class="fas fa-clock me-2"></i>
{% trans "This assignment has expired. Submissions are no longer accepted." %} {% trans "This assignment has expired. Submissions are no longer accepted." %}
{% elif assignment.is_full %} {% elif assignment.is_full %}
<i class="fas fa-users me-2"></i> <i class="fas fa-users me-2"></i>
{% trans "Maximum candidate limit reached for this assignment." %} {% trans "Maximum Application limit reached for this assignment." %}
{% else %} {% else %}
<i class="fas fa-pause me-2"></i> <i class="fas fa-pause me-2"></i>
{% trans "This assignment is not currently active." %} {% trans "This assignment is not currently active." %}
@ -222,7 +222,7 @@
<div class="spinner-border text-primary mb-3" role="status"> <div class="spinner-border text-primary mb-3" role="status">
<span class="visually-hidden">{% trans "Loading..." %}</span> <span class="visually-hidden">{% trans "Loading..." %}</span>
</div> </div>
<h6>{% trans "Submitting candidate..." %}</h6> <h6>{% trans "Submitting Application..." %}</h6>
<p class="text-muted small">{% trans "Please wait while we process your submission." %}</p> <p class="text-muted small">{% trans "Please wait while we process your submission." %}</p>
</div> </div>
</div> </div>
@ -240,7 +240,7 @@ document.addEventListener('DOMContentLoaded', function() {
const filePreview = document.getElementById('filePreview'); const filePreview = document.getElementById('filePreview');
const fileName = document.getElementById('fileName'); const fileName = document.getElementById('fileName');
const removeFileBtn = document.getElementById('removeFile'); const removeFileBtn = document.getElementById('removeFile');
const form = document.getElementById('candidateForm'); const form = document.getElementById('ApplicationForm');
const submitBtn = document.getElementById('submitBtn'); const submitBtn = document.getElementById('submitBtn');
// File upload area click handler // File upload area click handler
@ -340,7 +340,7 @@ document.addEventListener('DOMContentLoaded', function() {
successAlert.style.cssText = 'position: fixed; top: 20px; right: 20px; z-index: 9999; min-width: 300px;'; successAlert.style.cssText = 'position: fixed; top: 20px; right: 20px; z-index: 9999; min-width: 300px;';
successAlert.innerHTML = ` successAlert.innerHTML = `
<i class="fas fa-check-circle me-2"></i> <i class="fas fa-check-circle me-2"></i>
{% trans "Candidate submitted successfully!" %} {% trans "Application submitted successfully!" %}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button> <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`; `;
document.body.appendChild(successAlert); document.body.appendChild(successAlert);
@ -371,7 +371,7 @@ document.addEventListener('DOMContentLoaded', function() {
errorAlert.style.cssText = 'position: fixed; top: 20px; right: 20px; z-index: 9999; min-width: 300px;'; errorAlert.style.cssText = 'position: fixed; top: 20px; right: 20px; z-index: 9999; min-width: 300px;';
errorAlert.innerHTML = ` errorAlert.innerHTML = `
<i class="fas fa-exclamation-triangle me-2"></i> <i class="fas fa-exclamation-triangle me-2"></i>
${data.message || '{% trans "Error submitting candidate. Please try again." %}'} ${data.message || '{% trans "Error submitting Application. Please try again." %}'}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button> <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`; `;
document.body.appendChild(errorAlert); document.body.appendChild(errorAlert);
@ -401,7 +401,7 @@ document.addEventListener('DOMContentLoaded', function() {
.finally(() => { .finally(() => {
// Re-enable submit button // Re-enable submit button
submitBtn.disabled = false; submitBtn.disabled = false;
submitBtn.innerHTML = '<i class="fas fa-paper-plane me-1"></i> {% trans "Submit Candidate" %}'; submitBtn.innerHTML = '<i class="fas fa-paper-plane me-1"></i> {% trans "Submit Application" %}';
}); });
}); });

View File

@ -292,23 +292,23 @@
{# Header: Larger, more dynamic on large screens. Stacks cleanly on mobile. #} {# Header: Larger, more dynamic on large screens. Stacks cleanly on mobile. #}
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center mb-5"> <div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center mb-5">
<h1 class="display-6 display-md-5 fw-extrabold mb-3 mb-md-0" style="color: var(--kaauh-teal-dark);"> <h1 class="display-6 display-md-5 fw-extrabold mb-3 mb-md-0" style="color: var(--kaauh-teal-dark);">
{% trans "Your Candidate Dashboard" %} {% trans "Your Applicant Dashboard" %}
</h1> </h1>
{% comment %} <a href="#profile-details" data-bs-toggle="tab" class="btn btn-main-action btn-sm btn-md-lg px-4 py-2 rounded-pill shadow-sm shadow-md-lg"> {% comment %} <a href="#profile-details" data-bs-toggle="tab" class="btn btn-main-action btn-sm btn-md-lg px-4 py-2 rounded-pill shadow-sm shadow-md-lg">
<i class="fas fa-edit me-2"></i> {% trans "Update Profile" %} <i class="fas fa-edit me-2"></i> {% trans "Update Profile" %}
</a> {% endcomment %} </a> {% endcomment %}
</div> </div>
{# Candidate Quick Overview Card: Use a softer background color #} {# Applicant Quick Overview Card: Use a softer background color #}
<div class="card kaauh-card mb-5 p-4 bg-white"> <div class="card kaauh-card mb-5 p-4 bg-white">
<div class="d-flex align-items-center flex-column flex-sm-row text-center text-sm-start"> <div class="d-flex align-items-center flex-column flex-sm-row text-center text-sm-start">
<img src="{% if candidate.user.profile_image %}{{ candidate.user.profile_image.url }}{% else %}{% static 'image/default_avatar.png' %}{% endif %}" <img src="{% if applicant.user.profile_image %}{{ applicant.user.profile_image.url }}{% else %}{% static 'image/default_avatar.png' %}{% endif %}"
alt="{% trans 'Profile Picture' %}" alt="{% trans 'Profile Picture' %}"
class="rounded-circle me-sm-4 mb-3 mb-sm-0 shadow-lg" class="rounded-circle me-sm-4 mb-3 mb-sm-0 shadow-lg"
style="width: 80px; height: 80px; object-fit: cover; border: 4px solid var(--kaauh-teal-accent);"> style="width: 80px; height: 80px; object-fit: cover; border: 4px solid var(--kaauh-teal-accent);">
<div> <div>
<h3 class="card-title mb-1 fw-bold text-dark">{{ candidate.full_name|default:"Candidate Name" }}</h3> <h3 class="card-title mb-1 fw-bold text-dark">{{ applicant.full_name|default:"Applicant Name" }}</h3>
<p class="text-gray-subtle mb-0">{{ candidate.email }}</p> <p class="text-gray-subtle mb-0">{{ applicant.email }}</p>
</div> </div>
</div> </div>
</div> </div>
@ -356,21 +356,21 @@
<ul class="list-unstyled profile-data-list p-0"> <ul class="list-unstyled profile-data-list p-0">
<li class="d-flex justify-content-between align-items-center"> <li class="d-flex justify-content-between align-items-center">
<div><i class="fas fa-id-card me-2 text-primary-theme"></i> <strong>{% trans "First Name" %}</strong></div> <div><i class="fas fa-id-card me-2 text-primary-theme"></i> <strong>{% trans "First Name" %}</strong></div>
<span class="text-end">{{ candidate.first_name|default:"" }}</span> <span class="text-end">{{ applicant.first_name|default:"" }}</span>
</li> </li>
<li class="d-flex justify-content-between align-items-center"> <li class="d-flex justify-content-between align-items-center">
<div><i class="fas fa-id-card me-2 text-primary-theme"></i> <strong>{% trans "Last Name" %}</strong></div> <div><i class="fas fa-id-card me-2 text-primary-theme"></i> <strong>{% trans "Last Name" %}</strong></div>
<span class="text-end">{{ candidate.last_name|default:"" }}</span> <span class="text-end">{{ applicant.last_name|default:"" }}</span>
</li> </li>
{% if candidate.middle_name %} {% if applicant.middle_name %}
<li class="d-flex justify-content-between align-items-center"> <li class="d-flex justify-content-between align-items-center">
<div><i class="fas fa-id-card me-2 text-primary-theme"></i> <strong>{% trans "Middle Name" %}</strong></div> <div><i class="fas fa-id-card me-2 text-primary-theme"></i> <strong>{% trans "Middle Name" %}</strong></div>
<span class="text-end">{{ candidate.middle_name }}</span> <span class="text-end">{{ applicant.middle_name }}</span>
</li> </li>
{% endif %} {% endif %}
<li class="d-flex justify-content-between align-items-center"> <li class="d-flex justify-content-between align-items-center">
<div><i class="fas fa-envelope me-2 text-primary-theme"></i> <strong>{% trans "Email" %}</strong></div> <div><i class="fas fa-envelope me-2 text-primary-theme"></i> <strong>{% trans "Email" %}</strong></div>
<span class="text-end">{{ candidate.email|default:"" }}</span> <span class="text-end">{{ applicant.email|default:"" }}</span>
</li> </li>
</ul> </ul>
</div> </div>
@ -383,19 +383,19 @@
<ul class="list-unstyled profile-data-list p-0"> <ul class="list-unstyled profile-data-list p-0">
<li class="d-flex justify-content-between align-items-center"> <li class="d-flex justify-content-between align-items-center">
<div><i class="fas fa-phone-alt me-2 text-primary-theme"></i> <strong>{% trans "Phone" %}</strong></div> <div><i class="fas fa-phone-alt me-2 text-primary-theme"></i> <strong>{% trans "Phone" %}</strong></div>
<span class="text-end">{{ candidate.phone|default:"" }}</span> <span class="text-end">{{ applicant.phone|default:"" }}</span>
</li> </li>
{% if candidate.address %} {% if applicant.address %}
<li class="d-flex align-items-start"> <li class="d-flex align-items-start">
<div class="mb-1"><i class="fas fa-map-marker-alt me-2 text-primary-theme"></i> <strong>{% trans "Address" %}</strong></div> <div class="mb-1"><i class="fas fa-map-marker-alt me-2 text-primary-theme"></i> <strong>{% trans "Address" %}</strong></div>
<span class="text-end text-break">{{ candidate.address|linebreaksbr }}</span> <span class="text-end text-break">{{ applicant.address|linebreaksbr }}</span>
</li> </li>
{% endif %} {% endif %}
{% if candidate.linkedin_profile %} {% if applicant.linkedin_profile %}
<li class="d-flex justify-content-between align-items-center"> <li class="d-flex justify-content-between align-items-center">
<div><i class="fab fa-linkedin me-2 text-primary-theme"></i> <strong>{% trans "LinkedIn Profile" %}</strong></div> <div><i class="fab fa-linkedin me-2 text-primary-theme"></i> <strong>{% trans "LinkedIn Profile" %}</strong></div>
<span class="text-end"> <span class="text-end">
<a href="{{ candidate.linkedin_profile }}" target="_blank" class="text-primary-theme text-decoration-none"> <a href="{{ applicant.linkedin_profile }}" target="_blank" class="text-primary-theme text-decoration-none">
{% trans "View Profile" %} <i class="fas fa-external-link-alt ms-1"></i> {% trans "View Profile" %} <i class="fas fa-external-link-alt ms-1"></i>
</a> </a>
</span> </span>
@ -412,15 +412,15 @@
<ul class="list-unstyled profile-data-list p-0"> <ul class="list-unstyled profile-data-list p-0">
<li class="d-flex justify-content-between align-items-center"> <li class="d-flex justify-content-between align-items-center">
<div><i class="fas fa-calendar-alt me-2 text-primary-theme"></i> <strong>{% trans "Date of Birth" %}</strong></div> <div><i class="fas fa-calendar-alt me-2 text-primary-theme"></i> <strong>{% trans "Date of Birth" %}</strong></div>
<span class="text-end">{{ candidate.date_of_birth|date:"M d, Y"|default:"" }}</span> <span class="text-end">{{ applicant.date_of_birth|date:"M d, Y"|default:"" }}</span>
</li> </li>
<li class="d-flex justify-content-between align-items-center"> <li class="d-flex justify-content-between align-items-center">
<div><i class="fas fa-venus-mars me-2 text-primary-theme"></i> <strong>{% trans "Gender" %}</strong></div> <div><i class="fas fa-venus-mars me-2 text-primary-theme"></i> <strong>{% trans "Gender" %}</strong></div>
<span class="text-end">{{ candidate.get_gender_display|default:"" }}</span> <span class="text-end">{{ applicant.get_gender_display|default:"" }}</span>
</li> </li>
<li class="d-flex justify-content-between align-items-center"> <li class="d-flex justify-content-between align-items-center">
<div><i class="fas fa-globe me-2 text-primary-theme"></i> <strong>{% trans "Nationality" %}</strong></div> <div><i class="fas fa-globe me-2 text-primary-theme"></i> <strong>{% trans "Nationality" %}</strong></div>
<span class="text-end">{{ candidate.get_nationality_display|default:"" }}</span> <span class="text-end">{{ applicant.get_nationality_display|default:"" }}</span>
</li> </li>
</ul> </ul>
</div> </div>
@ -431,16 +431,16 @@
<i class="fas fa-briefcase me-2 text-primary-theme"></i>{% trans "Professional Information" %} <i class="fas fa-briefcase me-2 text-primary-theme"></i>{% trans "Professional Information" %}
</h4> </h4>
<ul class="list-unstyled profile-data-list p-0"> <ul class="list-unstyled profile-data-list p-0">
{% if candidate.user.designation %} {% if applicant.user.designation %}
<li class="d-flex justify-content-between align-items-center"> <li class="d-flex justify-content-between align-items-center">
<div><i class="fas fa-user-tie me-2 text-primary-theme"></i> <strong>{% trans "Designation" %}</strong></div> <div><i class="fas fa-user-tie me-2 text-primary-theme"></i> <strong>{% trans "Designation" %}</strong></div>
<span class="text-end">{{ candidate.user.designation }}</span> <span class="text-end">{{ applicant.user.designation }}</span>
</li> </li>
{% endif %} {% endif %}
{% if candidate.gpa %} {% if applicant.gpa %}
<li class="d-flex justify-content-between align-items-center"> <li class="d-flex justify-content-between align-items-center">
<div><i class="fas fa-graduation-cap me-2 text-primary-theme"></i> <strong>{% trans "GPA" %}</strong></div> <div><i class="fas fa-graduation-cap me-2 text-primary-theme"></i> <strong>{% trans "GPA" %}</strong></div>
<span class="text-end">{{ candidate.gpa }}</span> <span class="text-end">{{ applicant.gpa }}</span>
</li> </li>
{% endif %} {% endif %}
</ul> </ul>
@ -626,7 +626,7 @@
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body" id="passwordModalBody"> <div class="modal-body" id="passwordModalBody">
<form action="{% url 'portal_password_reset' candidate.pk %}" method="post"> <form action="{% url 'portal_password_reset' applicant.pk %}" method="post">
{% csrf_token %} {% csrf_token %}
{{ password_reset_form|crispy }} {{ password_reset_form|crispy }}
<button type="submit" class="btn btn-main-action">{% trans "Change Password" %}</button> <button type="submit" class="btn btn-main-action">{% trans "Change Password" %}</button>
@ -645,7 +645,7 @@
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<form method="post" action="{% url 'user_profile_image_update' candidate.pk %}" enctype="multipart/form-data" > <form method="post" action="{% url 'user_profile_image_update' applicant.pk %}" enctype="multipart/form-data" >
{% csrf_token %} {% csrf_token %}
<div class="mb-3"> <div class="mb-3">
@ -709,7 +709,7 @@
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<form method="post" action="{% url 'document_upload' candidate.id %}" enctype="multipart/form-data" id="documentUploadForm"> <form method="post" action="{% url 'document_upload' applicant.id %}" enctype="multipart/form-data" id="documentUploadForm">
<input type="hidden" name="upload_target" value="person"> <input type="hidden" name="upload_target" value="person">
{% csrf_token %} {% csrf_token %}
<div class="mb-3"> <div class="mb-3">