diff --git a/NorahUniversity/urls.py b/NorahUniversity/urls.py index fe505e6..2b03c22 100644 --- a/NorahUniversity/urls.py +++ b/NorahUniversity/urls.py @@ -25,7 +25,7 @@ urlpatterns = [ path('application//', views.application_submit_form, name='application_submit_form'), path('application//submit/', views.application_submit, name='application_submit'), - path('application//apply/', views.application_detail, name='application_detail'), + path('application//apply/', views.job_application_detail, name='job_application_detail'), path('application//signup/', views.application_signup, name='application_signup'), path('application//success/', views.application_success, name='application_success'), # path('application/applicant/profile', views.applicant_profile, name='applicant_profile'), diff --git a/recruitment/forms.py b/recruitment/forms.py index ee9e610..0438cc3 100644 --- a/recruitment/forms.py +++ b/recruitment/forms.py @@ -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"] diff --git a/recruitment/tests.py b/recruitment/tests.py index 51d14b3..43c615e 100644 --- a/recruitment/tests.py +++ b/recruitment/tests.py @@ -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 diff --git a/recruitment/tests_advanced.py b/recruitment/tests_advanced.py index 81df19c..16ee992 100644 --- a/recruitment/tests_advanced.py +++ b/recruitment/tests_advanced.py @@ -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 diff --git a/recruitment/urls.py b/recruitment/urls.py index 8a436a6..9ab1d9b 100644 --- a/recruitment/urls.py +++ b/recruitment/urls.py @@ -194,7 +194,7 @@ urlpatterns = [ views_frontend.update_application_status, name="update_application_status", ), - # Sync URLs + # Sync URLs (check) path( "jobs//sync-hired-applications/", views_frontend.sync_hired_applications, @@ -205,6 +205,8 @@ urlpatterns = [ views_frontend.test_source_connection, name="test_source_connection", ), + + path( "jobs///reschedule_meeting_for_application//", views.reschedule_meeting_for_application, @@ -357,7 +359,7 @@ urlpatterns = [ path("agencies//", views.agency_detail, name="agency_detail"), path("agencies//update/", views.agency_update, name="agency_update"), path("agencies//delete/", views.agency_delete, name="agency_delete"), - path( + path( #check the html of this url it is not used anywhere "agencies//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//create/", views.agency_assignment_create, name="agency_assignment_create", @@ -423,7 +425,7 @@ urlpatterns = [ # path('admin/messages//mark-read/', views.admin_mark_message_read, name='admin_mark_message_read'), # path('admin/messages//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//reset/", views.portal_password_reset, name="portal_password_reset"), path( "portal/dashboard/", diff --git a/recruitment/utils.py b/recruitment/utils.py index 3ef5dd4..27e3b97 100644 --- a/recruitment/utils.py +++ b/recruitment/utils.py @@ -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 diff --git a/recruitment/views.py b/recruitment/views.py index 768c148..19a22a5 100644 --- a/recruitment/views.py +++ b/recruitment/views.py @@ -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() diff --git a/recruitment/views_frontend.py b/recruitment/views_frontend.py index 83fae93..d833e49 100644 --- a/recruitment/views_frontend.py +++ b/recruitment/views_frontend.py @@ -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 diff --git a/templates/applicant/career.html b/templates/applicant/career.html index e0853fc..2d6e22f 100644 --- a/templates/applicant/career.html +++ b/templates/applicant/career.html @@ -191,7 +191,7 @@ {% for job in active_jobs %} {# Optimized Job Listing Card #} -
diff --git a/templates/applicant/application_detail.html b/templates/applicant/job_application_detail.html similarity index 100% rename from templates/applicant/application_detail.html rename to templates/applicant/job_application_detail.html diff --git a/templates/includes/candidate_exam_status_form.html b/templates/includes/application_exam_status_form.html similarity index 75% rename from templates/includes/candidate_exam_status_form.html rename to templates/includes/application_exam_status_form.html index 48395d5..4efdf6d 100644 --- a/templates/includes/candidate_exam_status_form.html +++ b/templates/includes/application_exam_status_form.html @@ -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 %}
{% csrf_token %} {{ form.as_p }} diff --git a/templates/portal_base.html b/templates/portal_base.html index e924969..b4a8dd5 100644 --- a/templates/portal_base.html +++ b/templates/portal_base.html @@ -75,7 +75,7 @@
{% endcomment %} - +
- {% if candidates %} + {% if applications %}
- + - {% for candidate in candidates %} + {% for application in applications %}
{% trans "Candidate"%}{% trans "Application"%} {% trans "Contact" %} {% trans "Stage" %} @@ -248,36 +248,36 @@
-
{{ candidate.name }}
+
{{ application.name }}
-
{{ candidate.email }}
-
{{ candidate.phone }}
+
{{ application.email }}
+
{{ application.phone }}
- {{ candidate.get_stage_display }} + {{ application.get_stage_display }}
- {{candidate.email }}
-
{{ candidate.phone }}
+ {{application.email }}
+
{{ application.phone }}
- {{candidate.get_stage_display }} + {{application.get_stage_display }} - {{ candidate.created_at|date:"M d, Y" }} + {{ application.created_at|date:"M d, Y" }} - @@ -290,9 +290,9 @@ {% else %}
-
{% trans "No candidates submitted yet" %}
+
{% trans "No applications submitted yet" %}

- {% 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." %}

{% endif %} @@ -335,7 +335,7 @@
{{ total_candidates }}
-
/ {{ assignment.max_candidates }} {% trans "candidates" %}
+
/ {{ assignment.max_candidates }} {% trans "applications" %}
diff --git a/templates/recruitment/agency_portal_dashboard.html b/templates/recruitment/agency_portal_dashboard.html index 35f9e3f..bbb1ae4 100644 --- a/templates/recruitment/agency_portal_dashboard.html +++ b/templates/recruitment/agency_portal_dashboard.html @@ -50,7 +50,7 @@
@@ -169,8 +169,8 @@
- {% trans "Candidates" %} - {{ stats.candidate_count }} / {{ stats.assignment.max_candidates }} + {% trans "Applications" %} + {{ stats.application_count }} / {{ stats.assignment.max_applications }}
@@ -178,10 +178,10 @@
{% trans "Submission Progress" %} - {{ stats.candidate_count }}/{{ stats.assignment.max_candidates }} + {{ stats.application_count }}/{{ stats.assignment.max_applications }}
- {% with progress=stats.candidate_count %} + {% with progress=stats.application_count %}
{% endwith %} @@ -194,7 +194,7 @@ {% if stats.can_submit %}
- {% trans "Submit Candidate" %} + {% trans "Submit Application" %} {% else %}