third refactoring for candidate in ai_ovverview branch
This commit is contained in:
parent
8b51416e10
commit
2486eb791e
@ -25,7 +25,7 @@ urlpatterns = [
|
||||
|
||||
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: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>/success/', views.application_success, name='application_success'),
|
||||
# path('application/applicant/profile', views.applicant_profile, name='applicant_profile'),
|
||||
|
||||
@ -293,6 +293,7 @@ class ApplicationForm(forms.ModelForm):
|
||||
"resume",
|
||||
]
|
||||
labels = {
|
||||
"person":_("Applicant"),
|
||||
"resume": _("Resume"),
|
||||
"hiring_source": _("Hiring Type"),
|
||||
"hiring_agency": _("Hiring Agency"),
|
||||
@ -903,7 +904,7 @@ class FormTemplateIsActiveForm(forms.ModelForm):
|
||||
fields = ["is_active"]
|
||||
|
||||
|
||||
class CandidateExamDateForm(forms.ModelForm):
|
||||
class ApplicationExamDateForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Application
|
||||
fields = ["exam_date"]
|
||||
|
||||
@ -23,7 +23,7 @@ from .views import (
|
||||
applications_exam_view, applications_interview_view, api_schedule_application_meeting
|
||||
)
|
||||
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):
|
||||
@ -586,8 +586,8 @@ class UtilityFunctionTests(BaseTestCase):
|
||||
self.assertEqual(result['status'], 'success')
|
||||
self.assertIn('meeting_id', result['meeting_details'])
|
||||
|
||||
def test_get_candidates_from_request(self):
|
||||
"""Test the get_candidates_from_request utility function"""
|
||||
def get_applications_from_request(self):
|
||||
"""Test the get_applications_from_request utility function"""
|
||||
# This would be tested with a request that has candidate_ids
|
||||
pass
|
||||
|
||||
|
||||
@ -41,7 +41,7 @@ from .views import (
|
||||
# from .views_frontend import CandidateListView, JobListView, JobCreateView
|
||||
from .utils import (
|
||||
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
|
||||
)
|
||||
# from .zoom_api import ZoomAPIError
|
||||
|
||||
@ -194,7 +194,7 @@ urlpatterns = [
|
||||
views_frontend.update_application_status,
|
||||
name="update_application_status",
|
||||
),
|
||||
# Sync URLs
|
||||
# Sync URLs (check)
|
||||
path(
|
||||
"jobs/<slug:job_slug>/sync-hired-applications/",
|
||||
views_frontend.sync_hired_applications,
|
||||
@ -205,6 +205,8 @@ urlpatterns = [
|
||||
views_frontend.test_source_connection,
|
||||
name="test_source_connection",
|
||||
),
|
||||
|
||||
|
||||
path(
|
||||
"jobs/<slug:slug>/<int:application_id>/reschedule_meeting_for_application/<int:meeting_id>/",
|
||||
views.reschedule_meeting_for_application,
|
||||
@ -357,7 +359,7 @@ urlpatterns = [
|
||||
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>/delete/", views.agency_delete, name="agency_delete"),
|
||||
path(
|
||||
path( #check the html of this url it is not used anywhere
|
||||
"agencies/<slug:slug>/applications/",
|
||||
views.agency_applications,
|
||||
name="agency_applications",
|
||||
@ -369,12 +371,12 @@ urlpatterns = [
|
||||
views.agency_assignment_list,
|
||||
name="agency_assignment_list",
|
||||
),
|
||||
path(
|
||||
path( #check
|
||||
"agency-assignments/create/",
|
||||
views.agency_assignment_create,
|
||||
name="agency_assignment_create",
|
||||
),
|
||||
path(
|
||||
path(#check
|
||||
"agency-assignments/<slug:slug>/create/",
|
||||
views.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>/delete/', views.admin_delete_message, name='admin_delete_message'),
|
||||
# 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/dashboard/",
|
||||
|
||||
@ -571,10 +571,10 @@ def json_to_markdown_table(data_list):
|
||||
return markdown
|
||||
|
||||
|
||||
def get_candidates_from_request(request):
|
||||
def get_applications_from_request(request):
|
||||
for c in request.POST.items():
|
||||
try:
|
||||
yield models.Candidate.objects.get(pk=c[0])
|
||||
yield models.Application.objects.get(pk=c[0])
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
yield None
|
||||
|
||||
@ -61,7 +61,7 @@ from django.urls import reverse_lazy
|
||||
from django.db.models import Count, Avg, F, Q
|
||||
from .forms import (
|
||||
ZoomMeetingForm,
|
||||
CandidateExamDateForm,
|
||||
ApplicationExamDateForm,
|
||||
JobPostingForm,
|
||||
JobPostingImageForm,
|
||||
InterviewNoteForm,
|
||||
@ -98,7 +98,7 @@ from django.views.generic import (
|
||||
from .utils import (
|
||||
create_zoom_meeting,
|
||||
delete_zoom_meeting,
|
||||
get_candidates_from_request,
|
||||
get_applications_from_request,
|
||||
update_meeting,
|
||||
update_zoom_meeting,
|
||||
get_zoom_meeting_details,
|
||||
@ -864,9 +864,9 @@ def kaauh_career(request):
|
||||
|
||||
|
||||
# job detail facing the candidate:
|
||||
def application_detail(request, slug):
|
||||
def job_application_detail(request, 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
|
||||
@ -1854,18 +1854,18 @@ def applications_exam_view(request, slug):
|
||||
|
||||
@staff_user_required
|
||||
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":
|
||||
form = CandidateExamDateForm(request.POST, instance=candidate)
|
||||
form = ApplicationExamDateForm(request.POST, instance=application)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
return redirect("applications_exam_view", slug=candidate.job.slug)
|
||||
return redirect("applications_exam_view", slug=application.job.slug)
|
||||
else:
|
||||
form = CandidateExamDateForm(request.POST, instance=candidate)
|
||||
form = ApplicationExamDateForm(request.POST, instance=application)
|
||||
return render(
|
||||
request,
|
||||
"includes/candidate_exam_status_form.html",
|
||||
{"candidate": candidate, "form": form},
|
||||
"includes/application_exam_status_form.html",
|
||||
{"application": application, "form": form},
|
||||
)
|
||||
|
||||
|
||||
@ -1874,17 +1874,17 @@ def bulk_update_application_exam_status(request, slug):
|
||||
job = get_object_or_404(JobPosting, slug=slug)
|
||||
status = request.headers.get("status")
|
||||
if status:
|
||||
for candidate in get_candidates_from_request(request):
|
||||
for application in get_applications_from_request(request):
|
||||
try:
|
||||
if status == "pass":
|
||||
candidate.exam_status = "Passed"
|
||||
candidate.stage = "Interview"
|
||||
application.exam_status = "Passed"
|
||||
application.stage = "Interview"
|
||||
else:
|
||||
candidate.exam_status = "Failed"
|
||||
candidate.save()
|
||||
application.exam_status = "Failed"
|
||||
application.save()
|
||||
except Exception as 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)
|
||||
|
||||
|
||||
@ -1897,13 +1897,13 @@ def application_criteria_view_htmx(request, pk):
|
||||
|
||||
@staff_user_required
|
||||
def application_set_exam_date(request, slug):
|
||||
candidate = get_object_or_404(Application, slug=slug)
|
||||
candidate.exam_date = timezone.now()
|
||||
candidate.save()
|
||||
application = get_object_or_404(Application, slug=slug)
|
||||
application.exam_date = timezone.now()
|
||||
application.save()
|
||||
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
|
||||
@ -1911,9 +1911,9 @@ def application_update_status(request, slug):
|
||||
job = get_object_or_404(JobPosting, slug=slug)
|
||||
mark_as = request.POST.get("mark_as")
|
||||
if mark_as != "----------":
|
||||
candidate_ids = request.POST.getlist("candidate_ids")
|
||||
print(candidate_ids)
|
||||
if c := Application.objects.filter(pk__in=candidate_ids):
|
||||
application_ids = request.POST.getlist("candidate_ids")
|
||||
|
||||
if c := Application.objects.filter(pk__in=application_ids):
|
||||
if mark_as == "Exam":
|
||||
print("exam")
|
||||
c.update(
|
||||
@ -1979,7 +1979,7 @@ def application_update_status(request, slug):
|
||||
else "Applicant",
|
||||
)
|
||||
|
||||
messages.success(request, f"Candidates Updated")
|
||||
messages.success(request, f"Applications Updated")
|
||||
response = HttpResponse(redirect("applications_screening_view", slug=job.slug))
|
||||
response.headers["HX-Refresh"] = "true"
|
||||
return response
|
||||
@ -3710,22 +3710,22 @@ def agency_delete(request, slug):
|
||||
|
||||
@staff_user_required
|
||||
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)
|
||||
candidates = Application.objects.filter(hiring_agency=agency).order_by(
|
||||
applications = Application.objects.filter(hiring_agency=agency).order_by(
|
||||
"-created_at"
|
||||
)
|
||||
|
||||
# Filter by stage if provided
|
||||
stage_filter = request.GET.get("stage")
|
||||
if stage_filter:
|
||||
candidates = candidates.filter(stage=stage_filter)
|
||||
applications = applications.filter(stage=stage_filter)
|
||||
|
||||
# Get total candidates before pagination for accurate count
|
||||
total_candidates = candidates.count()
|
||||
# Get total applications before pagination for accurate count
|
||||
total_applications = applications.count()
|
||||
|
||||
# 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_obj = paginator.get_page(page_number)
|
||||
|
||||
@ -3733,9 +3733,9 @@ def agency_applications(request, slug):
|
||||
"agency": agency,
|
||||
"page_obj": page_obj,
|
||||
"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
|
||||
@ -3817,8 +3817,8 @@ def agency_assignment_detail(request, slug):
|
||||
AgencyJobAssignment.objects.select_related("agency", "job"), slug=slug
|
||||
)
|
||||
|
||||
# Get candidates submitted by this agency for this job
|
||||
candidates = Application.objects.filter(
|
||||
# Get applications submitted by this agency for this job
|
||||
applications = Application.objects.filter(
|
||||
hiring_agency=assignment.agency, job=assignment.job
|
||||
).order_by("-created_at")
|
||||
|
||||
@ -3827,21 +3827,21 @@ def agency_assignment_detail(request, slug):
|
||||
|
||||
# Get messages for this assignment
|
||||
|
||||
total_candidates = candidates.count()
|
||||
max_candidates = assignment.max_candidates
|
||||
total_applications = applications.count()
|
||||
max_applications = assignment.max_candidates
|
||||
circumference = 326.73 # 2 * π * r where r=52
|
||||
|
||||
if max_candidates > 0:
|
||||
progress_percentage = total_candidates / max_candidates
|
||||
if max_applications > 0:
|
||||
progress_percentage = total_applications / max_applications
|
||||
stroke_dashoffset = circumference - (circumference * progress_percentage)
|
||||
else:
|
||||
stroke_dashoffset = circumference
|
||||
|
||||
context = {
|
||||
"assignment": assignment,
|
||||
"candidates": candidates,
|
||||
"applications": applications,
|
||||
"access_link": access_link,
|
||||
"total_candidates": candidates.count(),
|
||||
"total_applications": applications.count(),
|
||||
"stroke_dashoffset": stroke_dashoffset,
|
||||
}
|
||||
return render(request, "recruitment/agency_assignment_detail.html", context)
|
||||
@ -3973,40 +3973,40 @@ def portal_password_reset(request,pk):
|
||||
for error in errors:
|
||||
messages.error(request, f"{field}: {error}")
|
||||
|
||||
# Agency Portal Views (for external agencies)
|
||||
def agency_portal_login(request):
|
||||
"""Agency login page"""
|
||||
# if request.session.get("agency_assignment_id"):
|
||||
# return redirect("agency_portal_dashboard")
|
||||
if request.method == "POST":
|
||||
form = AgencyLoginForm(request.POST)
|
||||
# # Agency Portal Views (for external agencies)
|
||||
# def agency_portal_login(request):
|
||||
# """Agency login page"""
|
||||
# # if request.session.get("agency_assignment_id"):
|
||||
# # return redirect("agency_portal_dashboard")
|
||||
# if request.method == "POST":
|
||||
# form = AgencyLoginForm(request.POST)
|
||||
|
||||
if form.is_valid():
|
||||
# Check if validated_access_link attribute exists
|
||||
# if form.is_valid():
|
||||
# # Check if validated_access_link attribute exists
|
||||
|
||||
# if hasattr(form, "validated_access_link"):
|
||||
# access_link = form.validated_access_link
|
||||
# access_link.record_access()
|
||||
# # if hasattr(form, "validated_access_link"):
|
||||
# # access_link = form.validated_access_link
|
||||
# # access_link.record_access()
|
||||
|
||||
# Store assignment in session
|
||||
# request.session["agency_assignment_id"] = access_link.assignment.id
|
||||
# request.session["agency_name"] = access_link.assignment.agency.name
|
||||
# # Store assignment in session
|
||||
# # request.session["agency_assignment_id"] = access_link.assignment.id
|
||||
# # request.session["agency_name"] = access_link.assignment.agency.name
|
||||
|
||||
messages.success(request, f"Welcome, {access_link.assignment.agency.name}!")
|
||||
return redirect("agency_portal_dashboard")
|
||||
else:
|
||||
messages.error(request, "Invalid token or password.")
|
||||
else:
|
||||
form = AgencyLoginForm()
|
||||
# messages.success(request, f"Welcome, {access_link.assignment.agency.name}!")
|
||||
# return redirect("agency_portal_dashboard")
|
||||
# else:
|
||||
# messages.error(request, "Invalid token or password.")
|
||||
# else:
|
||||
# form = AgencyLoginForm()
|
||||
|
||||
context = {
|
||||
"form": form,
|
||||
}
|
||||
return render(request, "recruitment/agency_portal_login.html", context)
|
||||
# context = {
|
||||
# "form": form,
|
||||
# }
|
||||
# return render(request, "recruitment/agency_portal_login.html", context)
|
||||
|
||||
|
||||
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.user_type == "agency":
|
||||
return redirect("agency_portal_dashboard")
|
||||
@ -4077,24 +4077,24 @@ def portal_login(request):
|
||||
@login_required
|
||||
@candidate_user_required
|
||||
def applicant_portal_dashboard(request):
|
||||
"""Candidate portal dashboard"""
|
||||
"""applicant portal dashboard"""
|
||||
if not request.user.is_authenticated:
|
||||
return redirect("account_login")
|
||||
|
||||
# Get candidate profile (Person record)
|
||||
try:
|
||||
candidate = request.user.person_profile
|
||||
applicant = request.user.person_profile
|
||||
except:
|
||||
messages.error(request, "No candidate profile found.")
|
||||
return redirect("account_login")
|
||||
|
||||
# Get candidate's applications with related job data
|
||||
applications = Application.objects.filter(
|
||||
person=candidate
|
||||
person=applicant
|
||||
).select_related('job').order_by('-created_at')
|
||||
|
||||
# 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
|
||||
password_form = PasswordResetForm()
|
||||
@ -4104,13 +4104,13 @@ def applicant_portal_dashboard(request):
|
||||
document_form = DocumentUploadForm()
|
||||
|
||||
context = {
|
||||
"candidate": candidate,
|
||||
"applicant": applicant,
|
||||
"applications": applications,
|
||||
"documents": documents,
|
||||
"password_form": password_form,
|
||||
"document_form": document_form,
|
||||
}
|
||||
return render(request, "recruitment/candidate_profile.html", context)
|
||||
return render(request, "recruitment/applicant_profile.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
@ -4163,7 +4163,7 @@ def applicant_application_detail(request, slug):
|
||||
"interviews": interviews,
|
||||
"documents": documents,
|
||||
}
|
||||
return render(request, "recruitment/candidate_application_detail.html", context)
|
||||
return render(request, "recruitment/applicant_application_detail.html", context)
|
||||
|
||||
|
||||
@agency_user_required
|
||||
@ -4243,17 +4243,17 @@ def agency_portal_dashboard(request):
|
||||
# Calculate statistics for each assignment
|
||||
assignment_stats = []
|
||||
for assignment in assignments:
|
||||
candidates = Application.objects.filter(
|
||||
applications = Application.objects.filter(
|
||||
hiring_agency=agency, job=assignment.job
|
||||
).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": assignment,
|
||||
"candidates": candidates,
|
||||
"candidate_count": candidates.count(),
|
||||
"applications": applications,
|
||||
"application_count": applications.count(),
|
||||
"unread_messages": unread_messages,
|
||||
"days_remaining": assignment.days_remaining,
|
||||
"is_active": assignment.is_currently_active,
|
||||
@ -4262,7 +4262,7 @@ def agency_portal_dashboard(request):
|
||||
)
|
||||
|
||||
# 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)
|
||||
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,
|
||||
"total_assignments": assignments.count(),
|
||||
"active_assignments": active_assignments,
|
||||
"total_candidates": total_candidates,
|
||||
"total_applications": total_applications,
|
||||
"total_unread_messages": total_unread_messages,
|
||||
}
|
||||
return render(request, "recruitment/agency_portal_dashboard.html", context)
|
||||
@ -4280,7 +4280,7 @@ def agency_portal_dashboard(request):
|
||||
|
||||
@agency_user_required
|
||||
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")
|
||||
# if not assignment_id:
|
||||
# return redirect("agency_portal_login")
|
||||
@ -4296,7 +4296,7 @@ def agency_portal_submit_application_page(request, slug):
|
||||
current_job=assignment.job
|
||||
|
||||
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)
|
||||
# Verify this assignment belongs to the same agency as the logged-in session
|
||||
if assignment.agency.id != assignment.agency.id:
|
||||
@ -4309,11 +4309,11 @@ def agency_portal_submit_application_page(request, slug):
|
||||
if not assignment.can_submit:
|
||||
messages.error(
|
||||
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)
|
||||
|
||||
# Get total submitted candidates for this assignment
|
||||
# Get total submitted applications for this assignment
|
||||
total_submitted = Application.objects.filter(
|
||||
hiring_agency=assignment.agency, job=assignment.job
|
||||
).count()
|
||||
|
||||
@ -25,7 +25,6 @@ from django.urls import reverse_lazy
|
||||
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.functions import Cast, Coalesce, TruncDate
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.shortcuts import render
|
||||
from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
@ -33,7 +32,7 @@ import json
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
# 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 (
|
||||
@ -907,7 +906,7 @@ def export_applications_csv(request, job_slug, stage):
|
||||
@login_required
|
||||
@staff_user_required
|
||||
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 .tasks import sync_hired_candidates_task
|
||||
|
||||
|
||||
@ -191,7 +191,7 @@
|
||||
|
||||
{% for job in active_jobs %}
|
||||
{# 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">
|
||||
|
||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{% 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 }}'}})">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
@ -75,7 +75,7 @@
|
||||
<div class="collapse navbar-collapse" id="agencyNavbar">
|
||||
|
||||
<div class="navbar-nav ms-auto">
|
||||
<li class="nav-item me-2">
|
||||
<li class="nav-item me-2">
|
||||
{% if LANGUAGE_CODE == 'en' %}
|
||||
<form action="{% url 'set_language' %}" method="post" class="d-inline">{% csrf_token %}
|
||||
<input name="next" type="hidden" value="{{ request.get_full_path }}">
|
||||
@ -120,7 +120,7 @@
|
||||
|
||||
|
||||
|
||||
|
||||
{% comment %}
|
||||
<li class="nav-item 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' %}">
|
||||
@ -145,8 +145,8 @@
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
{% endcomment %}
|
||||
</li> {% endcomment %}
|
||||
|
||||
{% if request.user.is_authenticated %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-white" href="{% url 'user_detail' request.user.pk %}">
|
||||
|
||||
@ -207,7 +207,7 @@
|
||||
|
||||
<div class="alert alert-info mx-2">
|
||||
<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>
|
||||
|
||||
{% if access_link %}
|
||||
@ -219,12 +219,12 @@
|
||||
</div>
|
||||
</div> {% endcomment %}
|
||||
|
||||
<!-- Candidates Card -->
|
||||
<!-- Applications Card -->
|
||||
<div class="kaauh-card p-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h5 class="mb-0" style="color: var(--kaauh-teal-dark);">
|
||||
<i class="fas fa-users me-2"></i>
|
||||
{% trans "Submitted Candidates" %} ({{ total_candidates }})
|
||||
{% trans "Submitted Applications" %} ({{ total_candidates }})
|
||||
</h5>
|
||||
{% if access_link %}
|
||||
<a href="{% url 'agency_portal_login' %}" target="_blank" class="btn btn-outline-info btn-sm">
|
||||
@ -233,12 +233,12 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if candidates %}
|
||||
{% if applications %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<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>
|
||||
<th class="px-4 py-3 text-uppercase small fw-bold text-muted">{% trans "Stage" %}
|
||||
@ -248,36 +248,36 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for candidate in candidates %}
|
||||
{% for application in applications %}
|
||||
<tr>
|
||||
<td>
|
||||
<div class="fw-bold">{{ candidate.name }}</div>
|
||||
<div class="fw-bold">{{ application.name }}</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="small">
|
||||
<div><i class="fas fa-envelope me-1"></i> {{ candidate.email }}</div>
|
||||
<div><i class="fas fa-phone me-1"></i> {{ candidate.phone }}</div>
|
||||
<div><i class="fas fa-envelope me-1"></i> {{ application.email }}</div>
|
||||
<div><i class="fas fa-phone me-1"></i> {{ application.phone }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-info">{{ candidate.get_stage_display }}</span>
|
||||
<span class="badge bg-info">{{ application.get_stage_display }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="small text-muted">
|
||||
<div class="mb-1"><i class="fas fa-envelope me-2 w-20"></i>
|
||||
{{candidate.email }}</div>
|
||||
<div><i class="fas fa-phone me-2 w-20"></i>{{ candidate.phone }}</div>
|
||||
{{application.email }}</div>
|
||||
<div><i class="fas fa-phone me-2 w-20"></i>{{ application.phone }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-4">
|
||||
<span class="badge bg-soft-info text-info rounded-pill px-3">
|
||||
{{candidate.get_stage_display }}</span>
|
||||
{{application.get_stage_display }}</span>
|
||||
</td>
|
||||
<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 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' %}">
|
||||
<i class="fas fa-eye"></i>
|
||||
</a>
|
||||
@ -290,9 +290,9 @@
|
||||
{% else %}
|
||||
<div class="text-center py-4">
|
||||
<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">
|
||||
{% 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>
|
||||
</div>
|
||||
{% endif %}
|
||||
@ -335,7 +335,7 @@
|
||||
|
||||
<div class="text-center">
|
||||
<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 class="progress mt-3" style="height: 8px;">
|
||||
|
||||
@ -50,7 +50,7 @@
|
||||
</div>
|
||||
<div>
|
||||
{% 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 href="#" class="btn btn-outline-secondary position-relative">
|
||||
<i class="fas fa-envelope me-1"></i> {% trans "Messages" %}
|
||||
@ -93,8 +93,8 @@
|
||||
<div class="text-info mb-2">
|
||||
<i class="fas fa-users fa-2x"></i>
|
||||
</div>
|
||||
<h4 class="card-title">{{ total_candidates }}</h4>
|
||||
<p class="card-text text-muted">{% trans "Total Candidates" %}</p>
|
||||
<h4 class="card-title">{{ total_applications }}</h4>
|
||||
<p class="card-text text-muted">{% trans "Total Applications" %}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -169,8 +169,8 @@
|
||||
</strong>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<small class="text-muted d-block">{% trans "Candidates" %}</small>
|
||||
<strong>{{ stats.candidate_count }} / {{ stats.assignment.max_candidates }}</strong>
|
||||
<small class="text-muted d-block">{% trans "Applications" %}</small>
|
||||
<strong>{{ stats.application_count }} / {{ stats.assignment.max_applications }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -178,10 +178,10 @@
|
||||
<div class="mb-3">
|
||||
<div class="d-flex justify-content-between mb-1">
|
||||
<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 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 %}"
|
||||
style="width: {{ progress|floatformat:0 }}%"></div>
|
||||
{% endwith %}
|
||||
@ -194,7 +194,7 @@
|
||||
{% if stats.can_submit %}
|
||||
<a href="{% url 'agency_portal_submit_application_page' stats.assignment.slug %}"
|
||||
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>
|
||||
{% else %}
|
||||
<button class="btn btn-sm btn-secondary" disabled>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{% extends 'portal_base.html' %}
|
||||
{% 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 %}
|
||||
<style>
|
||||
@ -97,13 +97,13 @@
|
||||
</li>
|
||||
|
||||
{% comment %} <li class="breadcrumb-item active" aria-current="page">
|
||||
{% trans "Submit Candidate" %}
|
||||
{% trans "Submit Application" %}
|
||||
</li> {% endcomment %}
|
||||
|
||||
<li class="breadcrumb-item active" aria-current="page" style="
|
||||
color: #F43B5E; /* Rosy Accent Color */
|
||||
font-weight: 600; ">
|
||||
{% trans "Submit Candidate" %}</li>
|
||||
{% trans "Submit Application" %}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
@ -112,11 +112,11 @@
|
||||
<div>
|
||||
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
||||
<i class="fas fa-user-plus me-2"></i>
|
||||
{% trans "Submit New Candidate" %}
|
||||
{% trans "Submit New Application" %}
|
||||
</h1>
|
||||
<p class="text-muted mb-0">
|
||||
<!-- Button trigger modal -->
|
||||
{% trans "Submit a candidate for" %}
|
||||
{% trans "Submit a Application for" %}
|
||||
{{ assignment.job.title }}
|
||||
|
||||
</p>
|
||||
@ -171,16 +171,16 @@
|
||||
<div class="kaauh-card p-4">
|
||||
<h5 class="mb-4" style="color: var(--kaauh-teal-dark);">
|
||||
<i class="fas fa-user-edit me-2"></i>
|
||||
{% trans "Candidate Information" %}
|
||||
{% trans "Application Information" %}
|
||||
</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 %}">
|
||||
{% csrf_token %}
|
||||
{{form|crispy}}
|
||||
<button type="submit" class="btn btn-main-action">
|
||||
<i class="fas fa-user-plus me-2"></i>
|
||||
{% trans "Submit Candidate" %}
|
||||
{% trans "Submit Application" %}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
@ -188,14 +188,14 @@
|
||||
<div class="kaauh-card p-4">
|
||||
<div class="text-center py-5">
|
||||
<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">
|
||||
{% if assignment.is_expired %}
|
||||
<i class="fas fa-clock me-2"></i>
|
||||
{% trans "This assignment has expired. Submissions are no longer accepted." %}
|
||||
{% elif assignment.is_full %}
|
||||
<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 %}
|
||||
<i class="fas fa-pause me-2"></i>
|
||||
{% trans "This assignment is not currently active." %}
|
||||
@ -222,7 +222,7 @@
|
||||
<div class="spinner-border text-primary mb-3" role="status">
|
||||
<span class="visually-hidden">{% trans "Loading..." %}</span>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
@ -240,7 +240,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const filePreview = document.getElementById('filePreview');
|
||||
const fileName = document.getElementById('fileName');
|
||||
const removeFileBtn = document.getElementById('removeFile');
|
||||
const form = document.getElementById('candidateForm');
|
||||
const form = document.getElementById('ApplicationForm');
|
||||
const submitBtn = document.getElementById('submitBtn');
|
||||
|
||||
// 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.innerHTML = `
|
||||
<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>
|
||||
`;
|
||||
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.innerHTML = `
|
||||
<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>
|
||||
`;
|
||||
document.body.appendChild(errorAlert);
|
||||
@ -401,7 +401,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
.finally(() => {
|
||||
// Re-enable submit button
|
||||
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" %}';
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -292,23 +292,23 @@
|
||||
{# 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">
|
||||
<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>
|
||||
{% 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" %}
|
||||
</a> {% endcomment %}
|
||||
</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="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' %}"
|
||||
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);">
|
||||
<div>
|
||||
<h3 class="card-title mb-1 fw-bold text-dark">{{ candidate.full_name|default:"Candidate Name" }}</h3>
|
||||
<p class="text-gray-subtle mb-0">{{ candidate.email }}</p>
|
||||
<h3 class="card-title mb-1 fw-bold text-dark">{{ applicant.full_name|default:"Applicant Name" }}</h3>
|
||||
<p class="text-gray-subtle mb-0">{{ applicant.email }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -356,21 +356,21 @@
|
||||
<ul class="list-unstyled profile-data-list p-0">
|
||||
<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>
|
||||
<span class="text-end">{{ candidate.first_name|default:"" }}</span>
|
||||
<span class="text-end">{{ applicant.first_name|default:"" }}</span>
|
||||
</li>
|
||||
<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>
|
||||
<span class="text-end">{{ candidate.last_name|default:"" }}</span>
|
||||
<span class="text-end">{{ applicant.last_name|default:"" }}</span>
|
||||
</li>
|
||||
{% if candidate.middle_name %}
|
||||
{% if applicant.middle_name %}
|
||||
<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>
|
||||
<span class="text-end">{{ candidate.middle_name }}</span>
|
||||
<span class="text-end">{{ applicant.middle_name }}</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
<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>
|
||||
<span class="text-end">{{ candidate.email|default:"" }}</span>
|
||||
<span class="text-end">{{ applicant.email|default:"" }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@ -383,19 +383,19 @@
|
||||
<ul class="list-unstyled profile-data-list p-0">
|
||||
<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>
|
||||
<span class="text-end">{{ candidate.phone|default:"" }}</span>
|
||||
<span class="text-end">{{ applicant.phone|default:"" }}</span>
|
||||
</li>
|
||||
{% if candidate.address %}
|
||||
{% if applicant.address %}
|
||||
<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>
|
||||
<span class="text-end text-break">{{ candidate.address|linebreaksbr }}</span>
|
||||
<span class="text-end text-break">{{ applicant.address|linebreaksbr }}</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if candidate.linkedin_profile %}
|
||||
{% if applicant.linkedin_profile %}
|
||||
<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>
|
||||
<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>
|
||||
</a>
|
||||
</span>
|
||||
@ -412,15 +412,15 @@
|
||||
<ul class="list-unstyled profile-data-list p-0">
|
||||
<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>
|
||||
<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 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>
|
||||
<span class="text-end">{{ candidate.get_gender_display|default:"" }}</span>
|
||||
<span class="text-end">{{ applicant.get_gender_display|default:"" }}</span>
|
||||
</li>
|
||||
<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>
|
||||
<span class="text-end">{{ candidate.get_nationality_display|default:"" }}</span>
|
||||
<span class="text-end">{{ applicant.get_nationality_display|default:"" }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@ -431,16 +431,16 @@
|
||||
<i class="fas fa-briefcase me-2 text-primary-theme"></i>{% trans "Professional Information" %}
|
||||
</h4>
|
||||
<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">
|
||||
<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>
|
||||
{% endif %}
|
||||
{% if candidate.gpa %}
|
||||
{% if applicant.gpa %}
|
||||
<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>
|
||||
<span class="text-end">{{ candidate.gpa }}</span>
|
||||
<span class="text-end">{{ applicant.gpa }}</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
@ -626,7 +626,7 @@
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<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 %}
|
||||
{{ password_reset_form|crispy }}
|
||||
<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>
|
||||
</div>
|
||||
<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 %}
|
||||
|
||||
<div class="mb-3">
|
||||
@ -709,7 +709,7 @@
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<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">
|
||||
{% csrf_token %}
|
||||
<div class="mb-3">
|
||||
Loading…
x
Reference in New Issue
Block a user