working on translation
This commit is contained in:
parent
93eac071ab
commit
e1a55d7633
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1310,7 +1310,18 @@ class ScheduledInterview(Base):
|
||||
models.Index(fields=["application", "job"]),
|
||||
]
|
||||
|
||||
|
||||
@property
|
||||
def get_schedule_type(self):
|
||||
if self.schedule:
|
||||
return self.schedule.schedule_interview_type
|
||||
else:
|
||||
return self.interview_location.location_type
|
||||
@property
|
||||
def get_schedule_status(self):
|
||||
return self.status
|
||||
@property
|
||||
def get_meeting_details(self):
|
||||
return self.interview_location
|
||||
# --- 3. Interview Notes Model (Fixed) ---
|
||||
|
||||
class InterviewNote(Base):
|
||||
|
||||
@ -442,6 +442,11 @@ urlpatterns = [
|
||||
views.candidate_application_detail,
|
||||
name="candidate_application_detail",
|
||||
),
|
||||
# path(
|
||||
# "candidate/<slug:application_slug>/applications/<slug:person_slug>/detail/<slug:agency_slug>/",
|
||||
# views.candidate_application_detail,
|
||||
# name="candidate_application_detail",
|
||||
# ),
|
||||
path(
|
||||
"portal/dashboard/",
|
||||
views.agency_portal_dashboard,
|
||||
@ -661,4 +666,8 @@ urlpatterns = [
|
||||
# Detail View (assuming slug is on ScheduledInterview)
|
||||
path("interviews/meetings/<slug:slug>/", views.meeting_details, name="meeting_details"),
|
||||
|
||||
# Email invitation URLs
|
||||
path("interviews/meetings/<slug:slug>/send-candidate-invitation/", views.send_candidate_invitation, name="send_candidate_invitation"),
|
||||
path("interviews/meetings/<slug:slug>/send-participants-invitation/", views.send_participants_invitation, name="send_participants_invitation"),
|
||||
|
||||
]
|
||||
|
||||
@ -129,7 +129,6 @@ from .models import (
|
||||
Source,
|
||||
Message,
|
||||
Document,
|
||||
OnsiteLocationDetails,
|
||||
InterviewLocation,
|
||||
InterviewNote
|
||||
)
|
||||
@ -1964,8 +1963,6 @@ def candidate_update_status(request, slug):
|
||||
@staff_user_required
|
||||
def candidate_interview_view(request, slug):
|
||||
job = get_object_or_404(JobPosting, slug=slug)
|
||||
|
||||
|
||||
context = {
|
||||
"job": job,
|
||||
"candidates": job.interview_candidates,
|
||||
@ -1975,8 +1972,6 @@ def candidate_interview_view(request, slug):
|
||||
return render(request, "recruitment/candidate_interview_view.html", context)
|
||||
|
||||
|
||||
|
||||
|
||||
@staff_user_required
|
||||
def candidate_document_review_view(request, slug):
|
||||
"""
|
||||
@ -4096,6 +4091,11 @@ def candidate_application_detail(request, slug):
|
||||
return redirect("account_login")
|
||||
|
||||
# Get candidate profile (Person record)
|
||||
agency = getattr(request.user,"agency_profile",None)
|
||||
if agency:
|
||||
candidate = get_object_or_404(Application,slug=slug)
|
||||
# if Application.objects.filter(person=candidate,hirin).exists()
|
||||
else:
|
||||
try:
|
||||
candidate = request.user.person_profile
|
||||
except:
|
||||
@ -4110,7 +4110,7 @@ def candidate_application_detail(request, slug):
|
||||
'scheduled_interviews' # Only prefetch interviews, not documents (Generic FK)
|
||||
),
|
||||
slug=slug,
|
||||
person=candidate
|
||||
person=candidate.person if agency else candidate
|
||||
)
|
||||
|
||||
# Get AI analysis data if available
|
||||
@ -4989,7 +4989,7 @@ def document_upload(request, slug):
|
||||
if upload_target == 'person':
|
||||
return redirect("candidate_portal_dashboard")
|
||||
else:
|
||||
return redirect("candidate_application_detail", slug=application.slug)
|
||||
return redirect("candidate_application_detail", application_slug=application.slug)
|
||||
|
||||
# Handle GET request for AJAX
|
||||
if request.headers.get("X-Requested-With") == "XMLHttpRequest":
|
||||
@ -6108,3 +6108,152 @@ def meeting_details(request, slug):
|
||||
|
||||
return render(request, 'interviews/detail_interview.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
def send_candidate_invitation(request, slug):
|
||||
"""Send invitation email to the candidate"""
|
||||
meeting = get_object_or_404(InterviewLocation, slug=slug)
|
||||
|
||||
try:
|
||||
interview = meeting.scheduled_interview
|
||||
except ScheduledInterview.DoesNotExist:
|
||||
raise Http404("No interview is associated with this meeting.")
|
||||
|
||||
candidate = interview.application
|
||||
job = interview.job
|
||||
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
from django.core.mail import send_mail
|
||||
from django.conf import settings
|
||||
|
||||
# Simple email content
|
||||
subject = f"Interview Invitation - {job.title}"
|
||||
message = f"""
|
||||
Dear {candidate.person.first_name} {candidate.person.last_name},
|
||||
|
||||
You are invited for an interview for the position of {job.title}.
|
||||
|
||||
Meeting Details:
|
||||
- Date: {interview.interview_date}
|
||||
- Time: {interview.interview_time}
|
||||
- Duration: {meeting.duration or 60} minutes
|
||||
"""
|
||||
|
||||
# Add join URL if it's a Zoom meeting
|
||||
if hasattr(meeting, 'zoommeetingdetails') and meeting.zoommeetingdetails.join_url:
|
||||
message += f"- Join URL: {meeting.zoommeetingdetails.join_url}\n"
|
||||
|
||||
# Add physical address if it's an onsite meeting
|
||||
if hasattr(meeting, 'onsitelocationdetails') and meeting.onsitelocationdetails.physical_address:
|
||||
message += f"- Location: {meeting.onsitelocationdetails.physical_address}\n"
|
||||
if meeting.onsitelocationdetails.room_number:
|
||||
message += f"- Room: {meeting.onsitelocationdetails.room_number}\n"
|
||||
|
||||
message += """
|
||||
Please confirm your attendance.
|
||||
|
||||
Best regards,
|
||||
KAAUH Recruitment Team
|
||||
"""
|
||||
|
||||
# Send email
|
||||
send_mail(
|
||||
subject,
|
||||
message,
|
||||
settings.DEFAULT_FROM_EMAIL,
|
||||
[candidate.person.email],
|
||||
fail_silently=False,
|
||||
)
|
||||
|
||||
messages.success(request, f"Invitation email sent to {candidate.person.email}")
|
||||
|
||||
except Exception as e:
|
||||
messages.error(request, f"Failed to send invitation email: {str(e)}")
|
||||
|
||||
return redirect('meeting_details', slug=slug)
|
||||
|
||||
|
||||
@login_required
|
||||
def send_participants_invitation(request, slug):
|
||||
"""Send invitation email to all participants"""
|
||||
meeting = get_object_or_404(InterviewLocation, slug=slug)
|
||||
|
||||
try:
|
||||
interview = meeting.scheduled_interview
|
||||
except ScheduledInterview.DoesNotExist:
|
||||
raise Http404("No interview is associated with this meeting.")
|
||||
|
||||
candidate = interview.application
|
||||
job = interview.job
|
||||
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
from django.core.mail import send_mail
|
||||
from django.conf import settings
|
||||
|
||||
# Get all participants
|
||||
participants = list(interview.participants.all())
|
||||
system_users = list(interview.system_users.all())
|
||||
all_participants = participants + system_users
|
||||
|
||||
if not all_participants:
|
||||
messages.warning(request, "No participants found to send invitation to.")
|
||||
return redirect('meeting_details', slug=slug)
|
||||
|
||||
# Simple email content
|
||||
subject = f"Interview Invitation - {job.title} with {candidate.person.first_name} {candidate.person.last_name}"
|
||||
message = f"""
|
||||
Dear Team Member,
|
||||
|
||||
You are invited to participate in an interview session.
|
||||
|
||||
Interview Details:
|
||||
- Candidate: {candidate.person.first_name} {candidate.person.last_name}
|
||||
- Position: {job.title}
|
||||
- Date: {interview.interview_date}
|
||||
- Time: {interview.interview_time}
|
||||
- Duration: {meeting.duration or 60} minutes
|
||||
"""
|
||||
|
||||
# Add join URL if it's a Zoom meeting
|
||||
if hasattr(meeting, 'zoommeetingdetails') and meeting.zoommeetingdetails.join_url:
|
||||
message += f"- Join URL: {meeting.zoommeetingdetails.join_url}\n"
|
||||
|
||||
# Add physical address if it's an onsite meeting
|
||||
if hasattr(meeting, 'onsitelocationdetails') and meeting.onsitelocationdetails.physical_address:
|
||||
message += f"- Location: {meeting.onsitelocationdetails.physical_address}\n"
|
||||
if meeting.onsitelocationdetails.room_number:
|
||||
message += f"- Room: {meeting.onsitelocationdetails.room_number}\n"
|
||||
|
||||
message += """
|
||||
Please confirm your availability.
|
||||
|
||||
Best regards,
|
||||
KAAUH Recruitment Team
|
||||
"""
|
||||
|
||||
# Get email addresses of all participants
|
||||
recipient_emails = []
|
||||
for participant in all_participants:
|
||||
if hasattr(participant, 'email') and participant.email:
|
||||
recipient_emails.append(participant.email)
|
||||
|
||||
if recipient_emails:
|
||||
# Send email to all participants
|
||||
send_mail(
|
||||
subject,
|
||||
message,
|
||||
settings.DEFAULT_FROM_EMAIL,
|
||||
recipient_emails,
|
||||
fail_silently=False,
|
||||
)
|
||||
|
||||
messages.success(request, f"Invitation emails sent to {len(recipient_emails)} participants")
|
||||
else:
|
||||
messages.warning(request, "No valid email addresses found for participants.")
|
||||
|
||||
except Exception as e:
|
||||
messages.error(request, f"Failed to send invitation emails: {str(e)}")
|
||||
|
||||
return redirect('meeting_details', slug=slug)
|
||||
|
||||
@ -171,12 +171,12 @@
|
||||
title="{% trans 'Your account' %}">
|
||||
{% else %}
|
||||
<div class="profile-avatar" title="{% trans 'Your account' %}">
|
||||
{{ user.username|first|upper }}
|
||||
{{ user.first_name }} {{ user.last_name }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</button>
|
||||
<ul
|
||||
|
||||
<ul
|
||||
class="dropdown-menu dropdown-menu-end py-0 shadow border-0 rounded-3"
|
||||
style="min-width: 240px;"
|
||||
>
|
||||
@ -242,10 +242,6 @@
|
||||
<span style="color:red;">{% trans "Sign Out" %}</span>
|
||||
</button>
|
||||
</form>
|
||||
{% comment %} <a class="d-inline text-decoration-none px-4 d-flex align-items-center border-0 bg-transparent text-start text-center" href={% url "account_logout" %}>
|
||||
<i class="fas fa-sign-out-alt me-3 fs-5 " style="color:red;"></i>
|
||||
<span style="color:red;">{% trans "Sign Out" %}</span>
|
||||
</a> {% endcomment %}
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
@ -226,7 +226,7 @@ body { background-color: #f0f2f5; font-family: 'Inter', sans-serif; }
|
||||
</div>
|
||||
|
||||
{# --- PARTICIPANTS --- #}
|
||||
<div class="row g-4 mt-1 mb-5">
|
||||
{% comment %} <div class="row g-4 mt-1 mb-5">
|
||||
<div class="col-lg-12">
|
||||
<div class="p-3 bg-white rounded shadow-sm">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
@ -275,7 +275,7 @@ body { background-color: #f0f2f5; font-family: 'Inter', sans-serif; }
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> {% endcomment %}
|
||||
|
||||
{# --- COMMENTS --- #}
|
||||
<div class="row g-4 mt-1">
|
||||
|
||||
@ -216,6 +216,15 @@ body {
|
||||
<a href="{% url 'update_meeting' meeting.slug %}" class="btn btn-primary-teal btn-sm">
|
||||
<i class="fas fa-edit me-1"></i> {% trans "Edit Meeting" %}
|
||||
</a>
|
||||
|
||||
{# Send Candidate Invitation Button #}
|
||||
<form method="post" action="{% url 'send_candidate_invitation' meeting.slug %}" style="display: inline;">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-outline-info btn-sm" onclick="return confirm('{% trans "Send invitation email to the candidate?" %}')">
|
||||
<i class="fas fa-envelope me-1"></i> {% trans "Send Candidate Invitation" %}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{# DELETE MEETING FORM #}
|
||||
<form method="post" action="{% url 'delete_meeting' meeting.slug %}" style="display: inline;">
|
||||
{% csrf_token %}
|
||||
@ -298,7 +307,7 @@ body {
|
||||
|
||||
|
||||
{# --- PARTICIPANTS TABLE --- #}
|
||||
<div class="col-lg-12">
|
||||
{% comment %} <div class="col-lg-12">
|
||||
<div class="p-3 bg-white rounded shadow-sm">
|
||||
<div class="d-flex justify-content-between align-item-center" >
|
||||
<h2 class="text-start"><i class="fas fa-users-cog me-2"></i> {% trans "Assigned Participants" %}</h2>
|
||||
@ -310,6 +319,14 @@ body {
|
||||
<i class="fas fa-users-cog me-1"></i> {% trans "Manage Participants" %} ({{total_participants}})
|
||||
</button>
|
||||
|
||||
{# Send Participants Invitation Button #}
|
||||
<form method="post" action="{% url 'send_participants_invitation' meeting.slug %}" style="display: inline;">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-outline-info btn-sm me-2" onclick="return confirm('{% trans "Send invitation email to all participants?" %}')">
|
||||
<i class="fas fa-envelope me-1"></i> {% trans "Send Participants Invitation" %}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<button type="button" class="btn btn-outline-info"
|
||||
data-bs-toggle="modal"
|
||||
title="Send Interview Emails"
|
||||
@ -352,7 +369,7 @@ body {
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div> {% endcomment %}
|
||||
</div>
|
||||
|
||||
{# ========================================================= #}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{% extends "portal_base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}{% if form.instance.pk %}Reply to Message{% else %}Compose Message{% endif %}{% endblock %}
|
||||
{% block title %}{% if form.instance.pk %}{% trans "Reply to Message"%}{% else %}{% trans"Compose Message"%}{% endif %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
|
||||
@ -194,7 +194,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
function updateCounter() {
|
||||
const remaining = maxLength - subjectField.value.length;
|
||||
counter.textContent = `${subjectField.value.length}/${maxLength} characters`;
|
||||
counter.textContent = `{% blocktrans %}{{ remaining }}/{{ maxLength }} characters{% endblocktrans %}`;
|
||||
if (remaining < 20) {
|
||||
counter.className = 'text-warning';
|
||||
} else {
|
||||
@ -236,3 +236,4 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@ -228,3 +228,4 @@ setInterval(() => {
|
||||
}, 30000);
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@ -264,6 +264,9 @@
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{% url 'candidate_application_detail' candidate.slug %}" class="btn btn-sm btn-outline-primary" title="{% trans 'View Profile' %}">
|
||||
<i class="fas fa-eye"></i>
|
||||
</a>
|
||||
<button class="btn btn-sm btn-outline-primary" onclick="editCandidate({{ candidate.id }})" title="{% trans 'Edit Candidate' %}">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
|
||||
@ -178,7 +178,9 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid py-4">
|
||||
<div class="container-fluid">
|
||||
<!-- Breadcrumb Navigation -->
|
||||
{% if not application.hiring_agency %}
|
||||
<nav aria-label="breadcrumb" class="mb-4">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item">
|
||||
@ -195,6 +197,7 @@
|
||||
</li>
|
||||
</ol>
|
||||
</nav>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<div class="row mb-4">
|
||||
@ -355,7 +358,7 @@
|
||||
<div class="row mb-5">
|
||||
<div class="col-12">
|
||||
<div class="kaauh-card">
|
||||
<div class="card-header bg-success text-white">
|
||||
<div class="card-header bg-primary-theme text-white">
|
||||
<h5 class="mb-0">
|
||||
<i class="fas fa-video me-2"></i>
|
||||
{% trans "Interview Schedule" %}
|
||||
@ -381,28 +384,28 @@
|
||||
<td>{{ interview.interview_date|date:"M d, Y" }}</td>
|
||||
<td>{{ interview.interview_time|time:"H:i" }}</td>
|
||||
<td>
|
||||
{% if interview.zoom_meeting %}
|
||||
{% if interview.get_schedule_type == 'Remote' %}
|
||||
<span class="badge bg-primary-theme">
|
||||
<i class="fas fa-laptop me-1"></i>
|
||||
{% trans "Remote" %}
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">
|
||||
<span class="badge bg-primary-theme">
|
||||
<i class="fas fa-building me-1"></i>
|
||||
{% trans "On-site" %}
|
||||
</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-{{ interview.status|lower }} text-white">
|
||||
{{ interview.get_status_display }}
|
||||
<span class="badg ">
|
||||
{{ interview.get_schedule_status }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
{% if interview.zoom_meeting and interview.zoom_meeting.join_url %}
|
||||
<a href="{{ interview.zoom_meeting.join_url }}"
|
||||
{% if interview.get_meeting_details and interview.get_schedule_type == 'Remote' %}
|
||||
<a href="{{ interview.get_meeting_details }}"
|
||||
target="_blank"
|
||||
class="btn btn-sm btn-primary">
|
||||
class="btn btn-sm bg-primary-theme text-white">
|
||||
<i class="fas fa-video me-1"></i>
|
||||
{% trans "Join" %}
|
||||
</a>
|
||||
@ -533,6 +536,62 @@
|
||||
{% endif %}
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-3 mb-3">
|
||||
<div class="kaauh-card h-100">
|
||||
<div class="card-body text-center">
|
||||
<i class="fas fa-arrow-left fa-2x text-primary-theme mb-3"></i>
|
||||
<h6>{% trans "Back to Dashboard" %}</h6>
|
||||
<p class="text-muted small">{% trans "View all your applications" %}</p>
|
||||
{% if application.hiring_agency %}
|
||||
<a href="{% url 'agency_portal_dashboard' %}" class="btn btn-main-action w-100">
|
||||
{% trans "Go Back" %}
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{% url 'candidate_portal_dashboard' %}" class="btn btn-main-action w-100">
|
||||
{% trans "Go to Dashboard" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if application.resume %}
|
||||
<div class="col-md-3 mb-3">
|
||||
<div class="kaauh-card h-100">
|
||||
<div class="card-body text-center">
|
||||
<i class="fas fa-download fa-2x text-primary-theme mb-3"></i>
|
||||
<h6>{% trans "Download Resume" %}</h6>
|
||||
<p class="text-muted small">{% trans "Get your submitted resume" %}</p>
|
||||
<a href="{{ application.resume.url }}"
|
||||
target="_blank"
|
||||
class="btn btn-main-action w-100">
|
||||
<i class="fas fa-download me-2"></i>
|
||||
{% trans "Download" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
{% comment %} <div class="col-md-3 mb-3">
|
||||
<div class="kaauh-card h-100">
|
||||
<div class="card-body text-center">
|
||||
<i class="fas fa-edit fa-2x text-warning mb-3"></i>
|
||||
<h6>{% trans "Update Profile" %}</h6>
|
||||
<p class="text-muted small">{% trans "Edit your personal information" %}</p>
|
||||
<a href="" class="btn btn-main-action w-100">
|
||||
<i class="fas fa-edit me-2"></i>
|
||||
{% trans "Update" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div> {% endcomment %}
|
||||
</div>
|
||||
|
||||
<!-- Next Steps Section -->
|
||||
{% comment %} <div class="row">
|
||||
<div class="col-12">
|
||||
<div class="kaauh-card">
|
||||
<div class="card-header bg-primary-theme text-white">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user