diff --git a/recruitment/forms.py b/recruitment/forms.py index 75d4f41..1e82e2c 100644 --- a/recruitment/forms.py +++ b/recruitment/forms.py @@ -2804,3 +2804,51 @@ class OnsiteInterviewForm(forms.Form): # instance.save() # return instance + +class ScheduledInterviewForm(forms.Form): + topic = forms.CharField( + max_length=255, + required=False, + widget=forms.TextInput(attrs={ + 'class': 'form-control', + 'placeholder': 'e.g., Interview Topic' + }), + label=_('Interview Topic') + ) + start_time = forms.DateTimeField( + widget=forms.DateTimeInput(attrs={ + 'class': 'form-control', + 'type': 'datetime-local', + 'required': True + }), + label=_('Start Time') + ) + duration = forms.IntegerField( + min_value=1, + required=False, + widget=forms.NumberInput(attrs={ + 'class': 'form-control', + 'placeholder': 'Duration in minutes' + }), + label=_('Duration (minutes)') + ) + + def clean_start_time(self): + """Validate start time is not in the past""" + start_time = self.cleaned_data.get('start_time') + if start_time and start_time < timezone.now(): + raise forms.ValidationError(_('Start time cannot be in the past.')) + return start_time + +class ScheduledInterviewUpdateStatusForm(forms.Form): + status = forms.ChoiceField( + choices=ScheduledInterview.InterviewStatus.choices, + widget=forms.Select(attrs={ + 'class': 'form-control', + 'required': True + }), + label=_('Interview Status') + ) + class Meta: + model = ScheduledInterview + fields = ['status'] \ No newline at end of file diff --git a/recruitment/models.py b/recruitment/models.py index df164c6..6684933 100644 --- a/recruitment/models.py +++ b/recruitment/models.py @@ -391,18 +391,18 @@ class JobPosting(Base): @property def all_applications(self): # 1. Define the safe JSON extraction and conversion expression - safe_score_expression = Cast( + safe_score_expression = Cast( Coalesce( # Extract the score explicitly as a text string (KeyTextTransform) KeyTextTransform( - 'match_score', + 'match_score', KeyTransform('analysis_data_en', 'ai_analysis_data') ), Value('0'), # Replace SQL NULL (from missing score) with the string '0' ), output_field=IntegerField() # Cast the resulting string ('90' or '0') to an integer ) - + # 2. Annotate the score using the safe expression return self.applications.annotate( sortable_score=safe_score_expression @@ -440,7 +440,7 @@ class JobPosting(Base): @property def all_applications_count(self): return self.all_applications.count() - + @property def screening_applications_count(self): @@ -795,7 +795,7 @@ class Application(Base): @property def resume_data_en(self): return self.ai_analysis_data.get("resume_data_en", {}) - + @property def resume_data_ar(self): return self.ai_analysis_data.get("resume_data_ar", {}) @@ -882,7 +882,7 @@ class Application(Base): def recommendation(self) -> str: """9. Provide a detailed final recommendation for the candidate.""" return self.analysis_data_en.get("recommendation", "") - + #for arabic @property @@ -939,7 +939,7 @@ class Application(Base): def recommendation_ar(self) -> str: """9. Provide a detailed final recommendation for the candidate.""" return self.analysis_data_ar.get("recommendation", "") - + # ==================================================================== # 🔄 HELPER METHODS @@ -1334,6 +1334,7 @@ class Interview(Base): ) password = models.CharField(max_length=20, blank=True, null=True) zoom_gateway_response = models.JSONField(blank=True, null=True) + details_url = models.JSONField(blank=True, null=True) participant_video = models.BooleanField(default=True) join_before_host = models.BooleanField(default=False) host_email = models.CharField(max_length=255, blank=True, null=True) diff --git a/recruitment/tasks.py b/recruitment/tasks.py index 6df188a..56f3960 100644 --- a/recruitment/tasks.py +++ b/recruitment/tasks.py @@ -363,7 +363,7 @@ def safe_cast_to_float(value, default=0.0): # return # # Ensure the result is parsed as a Python dict (if ai_handler returns a JSON string) # data = result['data'] - + # if isinstance(data, str): # data = json.loads(data) # print(data) @@ -634,7 +634,7 @@ def handle_resume_parsing_and_scoring(pk: int): logger.error(f"AI handler returned error for candidate {instance.pk}") print(f"AI handler returned error for candidate {instance.pk}") return - + # Ensure the result is parsed as a Python dict data = result['data'] if isinstance(data, str): @@ -711,8 +711,8 @@ def create_interview_and_meeting( password=result["meeting_details"]["password"], location_type="Remote" ) - schedule.interviews = interview - schedule.status = "Remote" + schedule.interview = interview + schedule.status = "scheduled" schedule.save() diff --git a/recruitment/urls.py b/recruitment/urls.py index 07d06db..e1a17c2 100644 --- a/recruitment/urls.py +++ b/recruitment/urls.py @@ -205,13 +205,11 @@ urlpatterns = [ views_frontend.test_source_connection, name="test_source_connection", ), - - - # path( - # "jobs///reschedule_meeting_for_application//", - # views.reschedule_meeting_for_application, - # name="reschedule_meeting_for_application", - # ), + path( + "jobs//reschedule_meeting_for_application/", + views.reschedule_meeting_for_application, + name="reschedule_meeting_for_application", + ), path( "jobs//update_application_exam_status/", views.update_application_exam_status, @@ -573,11 +571,13 @@ urlpatterns = [ # Interview URLs path('interviews/', views.interview_list, name='interview_list'), path('interviews//', views.interview_detail, name='interview_detail'), + path('interviews//update_interview_status', views.update_interview_status, name='update_interview_status'), + path('interviews//cancel_interview_for_application', views.cancel_interview_for_application, name='cancel_interview_for_application'), # Interview Creation URLs - path('interviews/create//', views.interview_create_type_selection, name='interview_create_type_selection'), - path('interviews/create//remote/', views.interview_create_remote, name='interview_create_remote'), - path('interviews/create//onsite/', views.interview_create_onsite, name='interview_create_onsite'), + path('interviews/create//', views.interview_create_type_selection, name='interview_create_type_selection'), + path('interviews/create//remote/', views.interview_create_remote, name='interview_create_remote'), + path('interviews/create//onsite/', views.interview_create_onsite, name='interview_create_onsite'), path('interviews//get_interview_list', views.get_interview_list, name='get_interview_list'), # # --- SCHEDULED INTERVIEW URLS (New Centralized Management) --- diff --git a/recruitment/utils.py b/recruitment/utils.py index 27e3b97..3e6a09a 100644 --- a/recruitment/utils.py +++ b/recruitment/utils.py @@ -586,7 +586,7 @@ def update_meeting(instance, updated_data): if result["status"] == "success": # Fetch the latest details from Zoom after successful update details_result = get_zoom_meeting_details(instance.meeting_id) - + if details_result["status"] == "success": zoom_details = details_result["meeting_details"] # Update instance with fetched details @@ -594,7 +594,7 @@ def update_meeting(instance, updated_data): instance.topic = zoom_details.get("topic", instance.topic) instance.duration = zoom_details.get("duration", instance.duration) - instance.details_url = zoom_details.get("join_url", instance.details_url) + # instance.details_url = zoom_details.get("join_url", instance.details_url) instance.password = zoom_details.get("password", instance.password) # Corrected status assignment: instance.status, not instance.password instance.status = zoom_details.get("status") diff --git a/recruitment/views.py b/recruitment/views.py index 3d4a870..7cefe72 100644 --- a/recruitment/views.py +++ b/recruitment/views.py @@ -78,6 +78,7 @@ from .forms import ( PortalLoginForm, MessageForm, PersonForm, + ScheduledInterviewForm # OnsiteLocationForm, # OnsiteReshuduleForm, # OnsiteScheduleForm, @@ -131,8 +132,7 @@ from .models import ( Source, Message, Document, - # InterviewLocation, - # InterviewNote, + Interview ) @@ -1207,6 +1207,19 @@ def application_submit_form(request, template_slug): ), ) return redirect("job_application_detail", slug=job.slug) + + + job = get_object_or_404(JobPosting, form_template__slug=template_slug) + if request.user.user_type == "candidate": + person=request.user.person_profile + if job.has_already_applied_to_this_job(person): + messages.error( + request, + _( + "You have already applied to this job: Multiple applications are not allowed." + ), + ) + return redirect("job_application_detail", slug=job.slug) template = get_object_or_404(FormTemplate, slug=template_slug, is_active=True) @@ -2034,43 +2047,34 @@ def applications_document_review_view(request, slug): return render(request, "recruitment/applications_document_review_view.html", context) -# @staff_user_required -# def reschedule_meeting_for_application(request, slug, application_id, meeting_id): -# job = get_object_or_404(JobPosting, slug=slug) -# application = get_object_or_404(Application, pk=application_id) -# meeting = get_object_or_404(ZoomMeetingDetails, pk=meeting_id) -# form = ZoomMeetingForm(instance=meeting) +@require_POST +@staff_user_required +def reschedule_meeting_for_application(request, slug): + schedule_interview = get_object_or_404(ScheduledInterview, slug=slug) + if request.method == "POST": + form = ScheduledInterviewForm(request.POST) + if form.is_valid(): + topic = form.cleaned_data.get("topic") + start_time = form.cleaned_data.get("start_time") + duration = form.cleaned_data.get("duration") + updated_data = { + "topic": topic, + "start_time": start_time.isoformat() + "Z", + "duration": duration, + } + result = update_meeting(schedule_interview.interview, updated_data) -# if request.method == "POST": -# form = ZoomMeetingForm(request.POST, instance=meeting) -# if form.is_valid(): -# instance = form.save(commit=False) -# updated_data = { -# "topic": instance.topic, -# "start_time": instance.start_time.isoformat() + "Z", -# "duration": instance.duration, -# } -# if instance.start_time < timezone.now(): -# messages.error(request, "Start time must be in the future.") -# return redirect( -# "reschedule_meeting_for_application", -# slug=job.slug, -# application_id=candidate_id, -# meeting_id=meeting_id, -# ) + if result["status"] == "success": + messages.success(request, result["message"]) + else: + messages.error(request, result["message"]) + else: + print(form.errors) + messages.error(request, "Invalid data submitted.") + return redirect("interview_detail", slug=schedule_interview.slug) -# result = update_meeting(instance, updated_data) - - # if result["status"] == "success": - # messages.success(request, result["message"]) - # else: - # messages.error(request, result["message"]) - # return redirect( - # reverse("applications_interview_view", kwargs={"slug": job.slug}) - # ) - -# context = {"job": job, "candidate": candidate, "meeting": meeting, "form": form} -# return render(request, "meetings/reschedule_meeting.html", context) + # context = {"job": job, "application": application, "meeting": meeting, "form": form} + # return render(request, "meetings/reschedule_meeting.html", context) # @staff_user_required @@ -4532,7 +4536,7 @@ def agency_assignment_detail_admin(request, slug): -#will check the changes application to appliaction in this function +#will check the changes application to appliaction in this function @agency_user_required def agency_portal_edit_application(request, application_id): """Edit a application for agency portal""" @@ -4888,6 +4892,15 @@ def message_mark_unread(request, message_id): @login_required def message_delete(request, message_id): + """Delete a message""" + """ + Deletes a message using a POST request, primarily designed for HTMX. + Redirects to the message list on success (either via standard redirect + or HTMX's hx-redirect header). + """ + + # 1. Retrieve the message + # Use select_related to fetch linked objects efficiently for checks/logging """ Deletes a message using a POST request, primarily designed for HTMX. Redirects to the message list on success (either via standard redirect @@ -4904,13 +4917,13 @@ def message_delete(request, message_id): # Only the sender or recipient can delete the message if message.sender != request.user and message.recipient != request.user: messages.error(request, "You don't have permission to delete this message.") - + # HTMX requests should handle redirection via client-side logic (hx-redirect) if "HX-Request" in request.headers: # Returning 403 or 400 is ideal, but 200 with an empty body is often accepted # by HTMX and the message is shown on the next page/refresh. - return HttpResponse(status=403) - + return HttpResponse(status=403) + # Standard navigation redirect return redirect("message_list") @@ -5108,6 +5121,26 @@ def document_upload(request, slug): def document_delete(request, document_id): """Delete a document using a POST request (ideal for HTMX).""" document = get_object_or_404(Document, id=document_id) + + # Initialize variables for redirection outside of the complex logic + is_htmx = "HX-Request" in request.headers + + # 1. Permission and Context Initialization + has_permission = False + + content_object = document.content_object + + # Case A: Document linked to an Application (via content_object) + if hasattr(content_object, "job"): + # Staff/Superuser checking against Application's Job assignment + if (content_object.job.assigned_to == request.user) or request.user.is_superuser: + has_permission = True + + # Candidate checking if the Application belongs to them + elif request.user.user_type == "candidate" and content_object.person.user == request.user: + has_permission = True + + # Determine redirect URL for non-HTMX requests (fallback) # Initialize variables for redirection outside of the complex logic is_htmx = "HX-Request" in request.headers @@ -5135,6 +5168,38 @@ def document_delete(request, document_id): # Assuming you redirect to the job detail page for staff redirect_view_name = "job_detail" redirect_args = [content_object.job.slug] # Pass the job slug + + # Case B: Document linked directly to a Person (e.g., profile document) + elif hasattr(content_object, "user"): + # Check if the document belongs to the requesting candidate + if request.user.user_type == "candidate" and content_object.user == request.user: + has_permission = True + redirect_view_name = "applicant_portal_dashboard" + # Check if the requesting user is staff/superuser (Staff can delete profile docs) + elif request.user.is_staff or request.user.is_superuser: + has_permission = True + # Staff should probably go to the person's profile detail, but defaulting to a safe spot. + redirect_view_name = "dashboard" + + # Case C: No clear content object linkage or unhandled type + else: + has_permission = request.user.is_superuser # Only superuser can delete unlinked docs + + + # 2. Enforce Permissions + if not has_permission: + messages.error(request, "Permission denied: You cannot delete this document.") + # Return a 403 response for HTMX/AJAX + return HttpResponse(status=403) + + + # 3. Handle POST Request (Deletion) + # Assuming you redirect to the candidate's main dashboard after deleting their app document + redirect_view_name = "applicant_portal_dashboard" + else: + # Assuming you redirect to the job detail page for staff + redirect_view_name = "job_detail" + redirect_args = [content_object.job.slug] # Pass the job slug # Case B: Document linked directly to a Person (e.g., profile document) elif hasattr(content_object, "user"): @@ -5166,6 +5231,13 @@ def document_delete(request, document_id): document.delete() messages.success(request, f'Document "{file_name}" deleted successfully!') + # --- HTMX / AJAX Response --- + if is_htmx or request.headers.get("X-Requested-With") == "XMLHttpRequest": + # For HTMX, return a 200 OK. The front-end is expected to use hx-swap='outerHTML' + # to remove the element, or hx-redirect to navigate. + return HttpResponse(status=200) + + # --- Standard Navigation Fallback --- # --- HTMX / AJAX Response --- if is_htmx or request.headers.get("X-Requested-With") == "XMLHttpRequest": # For HTMX, return a 200 OK. The front-end is expected to use hx-swap='outerHTML' @@ -5174,6 +5246,19 @@ def document_delete(request, document_id): # --- Standard Navigation Fallback --- else: + try: + # Use the calculated redirect view name and arguments + if 'redirect_args' in locals(): + return redirect(redirect_view_name, *redirect_args) + else: + return redirect(redirect_view_name) + except NameError: + # If no specific redirect_view_name was set (e.g., Case C failure) + return redirect("dashboard") + + # 4. Handle non-POST (e.g., GET) + # The delete view should not be accessed via GET. + return HttpResponse(status=405) # Method Not Allowed try: # Use the calculated redirect view name and arguments if 'redirect_args' in locals(): @@ -5231,6 +5316,7 @@ def document_download(request, document_id): + @login_required def portal_logout(request): """Logout from portal""" @@ -5242,6 +5328,9 @@ def portal_logout(request): # Interview Creation Views @staff_user_required +def interview_create_type_selection(request, application_slug): + """Show interview type selection page for a application""" + application = get_object_or_404(Application, slug=application_slug) def interview_create_type_selection(request, application_slug): """Show interview type selection page for a application""" application = get_object_or_404(Application, slug=application_slug) @@ -5254,11 +5343,16 @@ def interview_create_type_selection(request, application_slug): context = { 'application': application, 'job': application.job, + 'application': application, + 'job': application.job, } return render(request, 'interviews/interview_create_type_selection.html', context) @staff_user_required +def interview_create_remote(request, application_slug): + """Create remote interview for a candidate""" + application = get_object_or_404(Application, slug=application_slug) def interview_create_remote(request, application_slug): """Create remote interview for a application""" application = get_object_or_404(Application, slug=application_slug) @@ -5279,40 +5373,16 @@ def interview_create_remote(request, application_slug): "recruitment.tasks.create_interview_and_meeting", application.pk, application.job.pk, schedule.pk, schedule.interview_date,schedule.interview_time, form.cleaned_data['duration'] ) - # interview.interview_type = 'REMOTE' - # interview.status = 'SCHEDULED' - # interview.save() - - # Create ZoomMeetingDetails record - # from .models import ZoomMeetingDetails - # zoom_meeting = ZoomMeetingDetails.objects.create( - # topic=form.cleaned_data['topic'], - # start_time=timezone.make_aware( - # timezone.datetime.combine( - # form.cleaned_data['interview_date'], - # form.cleaned_data['interview_time'] - # ), - # timezone.get_current_timezone() - # ), - # duration=form.cleaned_data['duration'], - # meeting_id=f"KAUH-{interview.id}-{timezone.now().timestamp()}", - # join_url=f"https://zoom.us/j/{interview.id}", - # password=secrets.token_urlsafe(16), - # status='scheduled' - # ) - - # Link Zoom meeting to interview - # interview.interview_location = zoom_meeting - # interview.save() messages.success(request, f"Remote interview scheduled for {application.name}") - return redirect('interview_list') + return redirect('interview_detail', slug=schedule.slug) except Exception as e: messages.error(request, f"Error creating remote interview: {str(e)}") form = RemoteInterviewForm() form.initial['topic'] = f"Interview for {application.job.title} - {application.name}" context = { + 'application': application, 'application': application, 'job': application.job, 'form': form, @@ -5322,14 +5392,9 @@ def interview_create_remote(request, application_slug): @staff_user_required def interview_create_onsite(request, application_slug): - """Create onsite interview for a application""" + """Create onsite interview for a candidate""" application = get_object_or_404(Application, slug=application_slug) - # Validate application is in Interview stage - # if application.stage != 'Interview': - # messages.error(request, f"application {application.name} is not in Interview stage.") - # return redirect('application_interview_view', slug=application.job.slug) - if request.method == 'POST': from .models import Interview @@ -5343,33 +5408,6 @@ def interview_create_onsite(request, application_slug): duration=form.cleaned_data["duration"],location_type="Onsite",status="SCHEDULED") schedule = ScheduledInterview.objects.create(application=application,job=application.job,interview=interview,interview_date=form.cleaned_data["interview_date"],interview_time=form.cleaned_data["interview_time"]) - # Create ScheduledInterview record - # interview = form.save(commit=False) - # interview.interview_type = 'ONSITE' - # interview.status = 'SCHEDULED' - # interview.save() - - # Create OnsiteLocationDetails record - # from .models import OnsiteLocationDetails - # onsite_location = OnsiteLocationDetails.objects.create( - # topic=form.cleaned_data['topic'], - # start_time=timezone.make_aware( - # timezone.datetime.combine( - # form.cleaned_data['interview_date'], - # form.cleaned_data['interview_time'] - # ), - # timezone.get_current_timezone() - # ), - # duration=form.cleaned_data['duration'], - # physical_address=form.cleaned_data['physical_address'], - # room_number=form.cleaned_data.get('room_number', ''), - # location_type='ONSITE', - # status='scheduled' - # ) - - # # Link onsite location to interview - # interview.interview_location = onsite_location - # interview.save() messages.success(request, f"Onsite interview scheduled for {application.name}") return redirect('interview_detail', slug=schedule.slug) @@ -5379,9 +5417,13 @@ def interview_create_onsite(request, application_slug): else: # Pre-populate topic form.initial['topic'] = f"Interview for {application.job.title} - {application.name}" + form.initial['topic'] = f"Interview for {application.job.title} - {application.name}" form = OnsiteInterviewForm() + form.initial['topic'] = f"Interview for {application.job.title} - {application.name}" context = { + 'application': application, + 'job': application.job, 'application': application, 'job': application.job, 'form': form, @@ -5390,10 +5432,39 @@ def interview_create_onsite(request, application_slug): def get_interview_list(request, job_slug): + from .forms import ScheduledInterviewUpdateStatusForm application = Application.objects.get(slug=job_slug) interviews = ScheduledInterview.objects.filter(application=application).order_by("interview_date","interview_time").select_related('interview') - print(interviews) - return render(request, 'interviews/partials/interview_list.html', {'interviews': interviews, 'application': application}) + interview_status_form = ScheduledInterviewUpdateStatusForm() + return render(request, 'interviews/partials/interview_list.html', {'interviews': interviews, 'application': application,'interview_status_form':interview_status_form}) + +@require_POST +def update_interview_status(request,slug): + from .forms import ScheduledInterviewUpdateStatusForm + + if request.method == 'POST': + form = ScheduledInterviewUpdateStatusForm(request.POST) + if form.is_valid(): + scheduled_interview = get_object_or_404(ScheduledInterview, slug=slug) + scheduled_interview.status = form.cleaned_data['status'] + scheduled_interview.save(update_fields=['status']) + messages.success(request, "Interview status updated successfully.") + return redirect('interview_detail', slug=slug) + +@require_POST +def cancel_interview_for_application(request,slug): + scheduled_interview = get_object_or_404(ScheduledInterview, slug=slug) + if request.method == 'POST': + if scheduled_interview.interview_type == 'REMOTE': + result = delete_zoom_meeting(scheduled_interview.interview.meeting_id) + if result["status"] != "success": + messages.error(request, f"Error cancelling Zoom meeting: {result.get('message', 'Unknown error')}") + return redirect('interview_detail', slug=slug) + + scheduled_interview.delete() + messages.success(request, "Interview cancelled successfully.") + return redirect('interview_list') + @login_required def agency_access_link_deactivate(request, slug): @@ -5529,6 +5600,10 @@ def compose_application_email(request, job_slug): candidate_ids = request.POST.getlist('candidate_ids') print("candidate_ids from post:", candidate_ids) + + applications=Application.objects.filter(id__in=candidate_ids) + form = CandidateEmailForm(job, applications, request.POST) + print("candidate_ids from post:", candidate_ids) applications=Application.objects.filter(id__in=candidate_ids) form = CandidateEmailForm(job, applications, request.POST) @@ -5567,9 +5642,17 @@ def compose_application_email(request, job_slug): ) if email_result["success"]: + for application in applications: + if hasattr(application, 'person') and application.person: for application in applications: if hasattr(application, 'person') and application.person: try: + print(request.user) + print(application.person.user) + print(subject) + print(message) + print(job) + print(request.user) print(application.person.user) print(subject) @@ -5579,6 +5662,7 @@ def compose_application_email(request, job_slug): Message.objects.create( sender=request.user, recipient=application.person.user, + recipient=application.person.user, subject=subject, content=message, job=job, @@ -5923,10 +6007,16 @@ def interview_list(request): @staff_user_required def interview_detail(request, slug): """View details of a specific interview""" + from .forms import ScheduledInterviewUpdateStatusForm + interview = get_object_or_404(ScheduledInterview, slug=slug) + reschedule_form = ScheduledInterviewForm() + reschedule_form.initial['topic'] = interview.interview.topic context = { 'interview': interview, + 'reschedule_form':reschedule_form, + 'interview_status_form':ScheduledInterviewUpdateStatusForm() } return render(request, 'interviews/interview_detail.html', context) diff --git a/templates/interviews/interview_create_onsite.html b/templates/interviews/interview_create_onsite.html index 7e20298..17a8410 100644 --- a/templates/interviews/interview_create_onsite.html +++ b/templates/interviews/interview_create_onsite.html @@ -11,7 +11,7 @@

- {% blocktrans %}Create Onsite Interview for {{ application.name }}{% endblocktrans %} + Create Onsite Interview for {{ application.name }}

@@ -21,8 +21,8 @@

- {% blocktrans %}Schedule an onsite interview for {{ application.name }} - for the position of {{ job.title }}.{% endblocktrans %} + Schedule an onsite interview for {{ application.name }} + for the position of {{ job.title }}.

{% if messages %} @@ -34,7 +34,7 @@ {% endfor %} {% endif %} -
+ {% csrf_token %}
@@ -100,36 +100,8 @@ {% endif %}
- - {% comment %}
-
- - {{ form.interviewer }} - {% if form.interviewer.errors %} -
- {{ form.interviewer.errors }} -
- {% endif %} -
-
{% endcomment %} - {% comment %}
- - {{ form.topic }} - {% if form.topic.errors %} -
- {{ form.topic.errors }} -
- {% endif %} -
{% endcomment %} -
- {% comment %}
-
- - {{ form.floor_number }} - {% if form.floor_number.errors %} -
- {{ form.floor_number.errors }} -
- {% endif %} -
-
{% endcomment %} - {% comment %} {% endcomment %} - - {% comment %}
- - {{ form.parking_info }} - {% if form.parking_info.errors %} -
- {{ form.parking_info.errors }} -
- {% endif %} -
{% endcomment %} - - {% comment %}
- - {{ form.notes }} - {% if form.notes.errors %} -
- {{ form.notes.errors }} -
- {% endif %} -
{% endcomment %} -
@@ -245,7 +250,7 @@ @@ -323,6 +328,13 @@ {% trans "Duration:" %} {{ interview.interview.duration }} {% trans "minutes" %} +
+ {% trans "Status:" %} + + + {{ interview.status }} + +
@@ -643,21 +655,21 @@
+ + + {% endblock %} {% block customJS %} diff --git a/templates/interviews/interview_list.html b/templates/interviews/interview_list.html index e329ffa..2d8b9cc 100644 --- a/templates/interviews/interview_list.html +++ b/templates/interviews/interview_list.html @@ -230,9 +230,8 @@ - {{meetings}} {# Using 'meetings' based on the context_object_name provided #} - {% if meetings %} + {% if interviews %}
{# View Switcher (kept the name for simplicity) #} {% include "includes/_list_view_switcher.html" with list_id="meetings-list" %} diff --git a/templates/interviews/partials/interview_list.html b/templates/interviews/partials/interview_list.html index 04cc425..8683408 100644 --- a/templates/interviews/partials/interview_list.html +++ b/templates/interviews/partials/interview_list.html @@ -2,12 +2,12 @@ - + - - + + - + diff --git a/templates/recruitment/applications_interview_view.html b/templates/recruitment/applications_interview_view.html index 9b0f75b..50b7487 100644 --- a/templates/recruitment/applications_interview_view.html +++ b/templates/recruitment/applications_interview_view.html @@ -227,7 +227,7 @@
{# Form 2: Schedule Interviews #} - + @@ -266,12 +266,13 @@ - + {% comment %} - - + {% endcomment %} + + @@ -301,7 +302,7 @@ {{ application.phone }} - - {% endcomment %} + {% comment %} - {% endcomment %} + {% comment %} + {% endcomment %} {% endfor %}
{% trans "Topic" %} {% trans "Topic" %} {% trans "Date" %} {% trans "Duration" %} {% trans "Location" %} {% trans "Duration" %} {% trans "Location" %} {% trans "Status" %} {% trans "Actions" %} {% trans "Actions" %}
{% trans "Name" %} {% trans "Contact" %} {% trans "Topic" %} {% trans "Topic" %} {% trans "Duration" %} {% trans "Meeting Date" %} {% trans "Link" %} {% trans "Meeting Status" %} {% trans "Interview Result"%} {% trans "Meeting Status" %} {% trans "Interview Result"%} {% trans "Interview List"%} {% trans "Actions" %}
+ {% comment %} {% if application.get_latest_meeting %} {{ application.get_latest_meeting }} {% else %} @@ -323,8 +324,8 @@ -- {% endif %} {% endwith %} - + {% with latest_meeting=application.get_latest_meeting %} {% if latest_meeting and latest_meeting.details_url %} -- {% endif %} {% endwith %} - + {{ latest_meeting.status }} {% with latest_meeting=application.get_latest_meeting %} {% if latest_meeting %} @@ -351,7 +352,7 @@ -- {% endif %} {% endwith %} - {% if not application.interview_status %} + + {% if application.get_latest_meeting %} @@ -422,37 +434,19 @@ {% endif %} {% else %} - {% comment %} - - Schedule - {% endcomment %} + - {% comment %} - - Schedule - {% endcomment %} + {% endif %} - - {{candidate.get_interviews}}