interview results added
This commit is contained in:
parent
b72ab43a5e
commit
fcc382d8f3
@ -1496,6 +1496,7 @@ class MessageForm(forms.ModelForm):
|
||||
self.helper.form_class = "g-3"
|
||||
|
||||
self._filter_recipient_field()
|
||||
self._filter_job_field()
|
||||
|
||||
self.helper.layout = Layout(
|
||||
Row(
|
||||
@ -1516,6 +1517,7 @@ class MessageForm(forms.ModelForm):
|
||||
"""Filter job options based on user type"""
|
||||
|
||||
if self.user.user_type == "agency":
|
||||
print("jhjkshfjksd")
|
||||
|
||||
job_assignments =AgencyJobAssignment.objects.filter(
|
||||
agency__user=self.user,
|
||||
@ -1528,11 +1530,18 @@ class MessageForm(forms.ModelForm):
|
||||
|
||||
print("Agency user job queryset:", self.fields["job"].queryset)
|
||||
elif self.user.user_type == "candidate":
|
||||
print("sjhdakjhsdkjashkdjhskd")
|
||||
# Candidates can only see jobs they applied for
|
||||
person=self.user.person_profile
|
||||
print(person)
|
||||
applications=person.applications.all()
|
||||
print(applications)
|
||||
|
||||
self.fields["job"].queryset = JobPosting.objects.filter(
|
||||
applications__person=self.user.person_profile,
|
||||
applications__in=applications,
|
||||
).distinct().order_by("-created_at")
|
||||
else:
|
||||
print("shhadjkhkd")
|
||||
# Staff can see all jobs
|
||||
self.fields["job"].queryset = JobPosting.objects.filter(
|
||||
status="ACTIVE"
|
||||
@ -2145,3 +2154,22 @@ KAAUH Hiring Team
|
||||
|
||||
self.fields['message'].initial = initial_message
|
||||
|
||||
|
||||
|
||||
class InterviewResultForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Interview
|
||||
|
||||
fields = ['interview_result', 'result_comments']
|
||||
widgets = {
|
||||
'interview_result': forms.Select(attrs={
|
||||
'class': 'form-select', # Standard Bootstrap class
|
||||
'required': 'required'
|
||||
}),
|
||||
'result_comments': forms.Textarea(attrs={
|
||||
'class': 'form-control',
|
||||
'rows': 3,
|
||||
'placeholder': 'Enter setting value',
|
||||
'required': True
|
||||
}),
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.2.7 on 2025-12-15 12:03
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('recruitment', '0002_alter_source_name_alter_source_source_type'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='interview',
|
||||
name='interview_result',
|
||||
field=models.CharField(blank=True, choices=[('passed', 'Passed'), ('failed', 'Failed'), ('on_hold', 'ON Hold')], default='on_hold', max_length=10, null=True, verbose_name='Interview Result'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='interview',
|
||||
name='result_comments',
|
||||
field=models.TextField(blank=True, null=True),
|
||||
),
|
||||
]
|
||||
@ -1122,6 +1122,11 @@ class Interview(Base):
|
||||
STARTED = "started", _("Started")
|
||||
ENDED = "ended", _("Ended")
|
||||
CANCELLED = "cancelled", _("Cancelled")
|
||||
|
||||
class InterviewResult(models.TextChoices):
|
||||
PASSED="passed",_("Passed")
|
||||
FAILED="failed",_("Failed")
|
||||
ON_HOLD="on_hold",_("ON Hold")
|
||||
|
||||
location_type = models.CharField(
|
||||
max_length=10,
|
||||
@ -1129,6 +1134,18 @@ class Interview(Base):
|
||||
verbose_name=_("Location Type"),
|
||||
db_index=True,
|
||||
)
|
||||
interview_result=models.CharField(
|
||||
max_length=10,
|
||||
choices=InterviewResult.choices,
|
||||
verbose_name=_("Interview Result"),
|
||||
null=True,
|
||||
blank=True,
|
||||
default='on_hold'
|
||||
)
|
||||
result_comments=models.TextField(
|
||||
null=True,
|
||||
blank=True
|
||||
)
|
||||
|
||||
# Common fields
|
||||
topic = models.CharField(
|
||||
|
||||
@ -56,34 +56,34 @@ class EmailService:
|
||||
return 0
|
||||
|
||||
|
||||
def send_single_email(
|
||||
self,
|
||||
user: User,
|
||||
subject: str,
|
||||
template_name: str,
|
||||
context: dict,
|
||||
from_email: str = settings.DEFAULT_FROM_EMAIL
|
||||
) -> int:
|
||||
"""
|
||||
Sends a single, template-based email to one user.
|
||||
"""
|
||||
recipient_list = [user.email]
|
||||
# def send_single_email(
|
||||
# self,
|
||||
# user: User,
|
||||
# subject: str,
|
||||
# template_name: str,
|
||||
# context: dict,
|
||||
# from_email: str = settings.DEFAULT_FROM_EMAIL
|
||||
# ) -> int:
|
||||
# """
|
||||
# Sends a single, template-based email to one user.
|
||||
# """
|
||||
# recipient_list = [user.email]
|
||||
|
||||
# 1. Render content from template
|
||||
html_content = render_to_string(template_name, context)
|
||||
# You can optionally render a plain text version as well:
|
||||
# text_content = strip_tags(html_content)
|
||||
# # 1. Render content from template
|
||||
# html_content = render_to_string(template_name, context)
|
||||
# # You can optionally render a plain text version as well:
|
||||
# # text_content = strip_tags(html_content)
|
||||
|
||||
# 2. Call internal sender
|
||||
return self._send_email_internal(
|
||||
subject=subject,
|
||||
body="", # Can be empty if html_content is provided
|
||||
recipient_list=recipient_list,
|
||||
from_email=from_email,
|
||||
html_content=html_content
|
||||
)
|
||||
# # 2. Call internal sender
|
||||
# return self._send_email_internal(
|
||||
# subject=subject,
|
||||
# body="", # Can be empty if html_content is provided
|
||||
# recipient_list=recipient_list,
|
||||
# from_email=from_email,
|
||||
# html_content=html_content
|
||||
# )
|
||||
|
||||
def send_bulk_email(
|
||||
def send_email_service(
|
||||
self,
|
||||
recipient_emails: List[str],
|
||||
subject: str,
|
||||
|
||||
@ -1534,7 +1534,7 @@ def send_job_closed_notification(job_id):
|
||||
)
|
||||
|
||||
|
||||
def send_bulk_email_task(
|
||||
def send_email_task(
|
||||
recipient_emails,
|
||||
subject: str,
|
||||
template_name: str,
|
||||
@ -1551,7 +1551,7 @@ def send_bulk_email_task(
|
||||
service = EmailService()
|
||||
|
||||
# Execute the bulk sending method
|
||||
processed_count = service.send_bulk_email(
|
||||
processed_count = service.send_email_service(
|
||||
recipient_emails=recipient_emails,
|
||||
subject=subject,
|
||||
template_name=template_name,
|
||||
@ -1565,33 +1565,33 @@ def send_bulk_email_task(
|
||||
"message": f"Attempted to send email to {len(recipient_emails)} recipients. Service reported processing {processed_count}."
|
||||
})
|
||||
|
||||
def send_single_email_task(
|
||||
recipient_emails,
|
||||
subject: str,
|
||||
template_name: str,
|
||||
context: dict,
|
||||
) -> str:
|
||||
"""
|
||||
Django-Q task to send a bulk email asynchronously.
|
||||
"""
|
||||
from .services.email_service import EmailService
|
||||
# def send_single_email_task(
|
||||
# recipient_emails,
|
||||
# subject: str,
|
||||
# template_name: str,
|
||||
# context: dict,
|
||||
# ) -> str:
|
||||
# """
|
||||
# Django-Q task to send a bulk email asynchronously.
|
||||
# """
|
||||
# from .services.email_service import EmailService
|
||||
|
||||
if not recipient_emails:
|
||||
return json.dumps({"status": "error", "message": "No recipients provided."})
|
||||
# if not recipient_emails:
|
||||
# return json.dumps({"status": "error", "message": "No recipients provided."})
|
||||
|
||||
service = EmailService()
|
||||
# service = EmailService()
|
||||
|
||||
# Execute the bulk sending method
|
||||
processed_count = service.send_bulk_email(
|
||||
recipient_emails=recipient_emails,
|
||||
subject=subject,
|
||||
template_name=template_name,
|
||||
context=context,
|
||||
)
|
||||
# # Execute the bulk sending method
|
||||
# processed_count = service.send_bulk_email(
|
||||
# recipient_emails=recipient_emails,
|
||||
# subject=subject,
|
||||
# template_name=template_name,
|
||||
# context=context,
|
||||
# )
|
||||
|
||||
# The return value is stored in the result object for monitoring
|
||||
return json.dumps({
|
||||
"status": "success",
|
||||
"count": processed_count,
|
||||
"message": f"Attempted to send email to {len(recipient_emails)} recipients. Service reported processing {processed_count}."
|
||||
})
|
||||
# # The return value is stored in the result object for monitoring
|
||||
# return json.dumps({
|
||||
# "status": "success",
|
||||
# "count": processed_count,
|
||||
# "message": f"Attempted to send email to {len(recipient_emails)} recipients. Service reported processing {processed_count}."
|
||||
# })
|
||||
@ -83,6 +83,8 @@ urlpatterns = [
|
||||
path("interviews/", views.interview_list, name="interview_list"),
|
||||
path("interviews/<slug:slug>/", views.interview_detail, name="interview_detail"),
|
||||
path("interviews/<slug:slug>/update_interview_status", views.update_interview_status, name="update_interview_status"),
|
||||
path("interviews/<slug:slug>/update_interview_result", views.update_interview_result, name="update_interview_result"),
|
||||
|
||||
path("interviews/<slug:slug>/cancel_interview_for_application", views.cancel_interview_for_application, name="cancel_interview_for_application"),
|
||||
path("interview/<slug:slug>/interview-email/",views.send_interview_email,name="send_interview_email"),
|
||||
|
||||
|
||||
@ -94,6 +94,7 @@ from .forms import (
|
||||
InterviewCancelForm,
|
||||
InterviewEmailForm,
|
||||
ApplicationStageForm,
|
||||
InterviewResultForm
|
||||
)
|
||||
from .utils import generate_random_password
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
@ -1764,6 +1765,7 @@ def _handle_confirm_schedule(request, slug, job):
|
||||
for i, application in enumerate(applications):
|
||||
if i < len(available_slots):
|
||||
slot = available_slots[i]
|
||||
# schedule=ScheduledInterview.objects.create(application=application,job=job)
|
||||
async_task(
|
||||
"recruitment.tasks.create_interview_and_meeting",
|
||||
application.pk,
|
||||
@ -2141,6 +2143,7 @@ def reschedule_meeting_for_application(request, slug):
|
||||
|
||||
if request.method == "POST":
|
||||
if interview.location_type == "Remote":
|
||||
|
||||
form = ScheduledInterviewForm(request.POST)
|
||||
else:
|
||||
form = OnsiteScheduleInterviewUpdateForm(request.POST)
|
||||
@ -3055,6 +3058,8 @@ def applicant_portal_dashboard(request):
|
||||
|
||||
# Get candidate's documents using the Person documents property
|
||||
documents = applicant.documents.order_by("-created_at")
|
||||
|
||||
print(documents)
|
||||
|
||||
# Add password change form for modal
|
||||
password_form = PasswordResetForm()
|
||||
@ -3683,7 +3688,7 @@ def message_create(request):
|
||||
subject=message.subject
|
||||
|
||||
email_result=async_task(
|
||||
"recruitment.tasks.send_bulk_email_task",
|
||||
"recruitment.tasks.send_email_task",
|
||||
email_addresses,
|
||||
subject,
|
||||
# message,
|
||||
@ -3750,7 +3755,7 @@ def message_create(request):
|
||||
and "HX-Request" in request.headers
|
||||
and request.user.user_type in ["candidate", "agency"]
|
||||
):
|
||||
print()
|
||||
|
||||
job_id = request.GET.get("job")
|
||||
if job_id:
|
||||
job = get_object_or_404(JobPosting, id=job_id)
|
||||
@ -4252,7 +4257,7 @@ def cancel_interview_for_application(request, slug):
|
||||
Handles POST request to cancel an interview, setting the status
|
||||
and saving the form data (likely a reason for cancellation).
|
||||
"""
|
||||
scheduled_interview = get_object_or_404(ScheduledInterview)
|
||||
scheduled_interview = get_object_or_404(ScheduledInterview,slug=slug)
|
||||
form = InterviewCancelForm(request.POST, instance=scheduled_interview)
|
||||
|
||||
if form.is_valid():
|
||||
@ -4276,6 +4281,33 @@ def cancel_interview_for_application(request, slug):
|
||||
return redirect("interview_detail", slug=scheduled_interview.slug)
|
||||
|
||||
|
||||
@require_POST
|
||||
@login_required # Assuming this should be protected
|
||||
@staff_user_required # Assuming only staff can cancel
|
||||
def update_interview_result(request,slug):
|
||||
interview = get_object_or_404(Interview,slug=slug)
|
||||
schedule=interview.scheduled_interview
|
||||
form = InterviewResultForm(request.POST, instance=interview)
|
||||
|
||||
if form.is_valid():
|
||||
|
||||
interview.save(update_fields=['interview_result', 'result_comments'])
|
||||
|
||||
form.save() # Saves form data
|
||||
|
||||
messages.success(request, _("Interview cancelled successfully."))
|
||||
return redirect("interview_detail", slug=schedule.slug)
|
||||
else:
|
||||
error_list = [
|
||||
f"{field}: {', '.join(errors)}" for field, errors in form.errors.items()
|
||||
]
|
||||
error_message = _("Please correct the following errors: ") + " ".join(
|
||||
error_list
|
||||
)
|
||||
messages.error(request, error_message)
|
||||
|
||||
return redirect("interview_detail", slug=schedule.slug)
|
||||
|
||||
@login_required
|
||||
@staff_user_required
|
||||
def agency_access_link_deactivate(request, slug):
|
||||
@ -4724,6 +4756,7 @@ def application_signup(request, slug):
|
||||
@login_required
|
||||
@staff_user_required
|
||||
def interview_list(request):
|
||||
|
||||
"""List all interviews with filtering and pagination"""
|
||||
interviews = ScheduledInterview.objects.select_related(
|
||||
"application",
|
||||
@ -4750,7 +4783,7 @@ def interview_list(request):
|
||||
interviews = interviews.filter(
|
||||
Q(application__person__first_name=search_query)
|
||||
| Q(application__person__last_name__icontains=search_query)
|
||||
| Q(application__person__email=search_query)
|
||||
| Q(application__person__email__icontains=search_query)
|
||||
| Q(job__title__icontains=search_query)
|
||||
)
|
||||
|
||||
@ -4779,8 +4812,11 @@ def interview_detail(request, slug):
|
||||
OnsiteScheduleInterviewUpdateForm,
|
||||
)
|
||||
|
||||
|
||||
|
||||
schedule = get_object_or_404(ScheduledInterview, slug=slug)
|
||||
interview = schedule.interview
|
||||
interview_result_form=InterviewResultForm(instance=interview)
|
||||
application = schedule.application
|
||||
job = schedule.job
|
||||
print(interview.location_type)
|
||||
@ -4803,6 +4839,7 @@ def interview_detail(request, slug):
|
||||
"interview_status_form": ScheduledInterviewUpdateStatusForm(),
|
||||
"cancel_form": InterviewCancelForm(instance=meeting),
|
||||
"interview_email_form": interview_email_form,
|
||||
"interview_result_form":interview_result_form,
|
||||
}
|
||||
return render(request, "interviews/interview_detail.html", context)
|
||||
|
||||
@ -6486,7 +6523,7 @@ def send_interview_email(request, slug):
|
||||
# 2. Match the context expected by your task/service
|
||||
# We pass IDs for the sender/job to avoid serialization issues
|
||||
async_task(
|
||||
"recruitment.tasks.send_bulk_email_task",
|
||||
"recruitment.tasks.send_email_task",
|
||||
recipient_list,
|
||||
subject,
|
||||
"emails/email_template.html",
|
||||
@ -6558,7 +6595,7 @@ def compose_application_email(request, slug):
|
||||
|
||||
|
||||
async_task(
|
||||
"recruitment.tasks.send_bulk_email_task",
|
||||
"recruitment.tasks.send_email_task",
|
||||
email_addresses,
|
||||
subject,
|
||||
# message,
|
||||
|
||||
@ -360,6 +360,7 @@
|
||||
</form>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% if request.user.is_authenticated%}
|
||||
<li class="nav-item dropdown">
|
||||
<button class="language-toggle-btn dropdown-toggle" type="button"
|
||||
data-bs-toggle="dropdown" data-bs-offset="0, 8" aria-expanded="false"
|
||||
@ -410,11 +411,13 @@
|
||||
<i class="fas fa-tachometer-alt me-3 fs-5"></i> <span>{% trans "Dashboard" %}</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none text-primary-theme" href="{% url 'user_detail' request.user.pk %}">
|
||||
<a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none text-primary-theme" href="{% url 'user_detail' request.user.pk %}" >
|
||||
<i class="fas fa-user-circle me-3 fs-5"></i> <span>{% trans "My Profile" %}</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
<li><hr class="dropdown-divider my-1"></li>
|
||||
<li>
|
||||
@ -432,6 +435,8 @@
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -670,22 +670,9 @@
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form method="post" action="#">
|
||||
<form method="post" action="{% url 'update_interview_result' interview.slug %}">
|
||||
{% csrf_token %}
|
||||
<div class="mb-3">
|
||||
<label for="interview_result" class="form-label">{% trans "Interview Result" %}</label>
|
||||
<select class="form-select" id="interview_result" name="result" required>
|
||||
<option value="">{% trans "Select Result" %}</option>
|
||||
<option value="passed">{% trans "Passed" %}</option>
|
||||
<option value="failed">{% trans "Failed" %}</option>
|
||||
<option value="on_hold">{% trans "On Hold" %}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="result_notes" class="form-label">{% trans "Notes" %}</label>
|
||||
<textarea class="form-control" id="result_notes" name="notes" rows="4"
|
||||
placeholder="{% trans 'Add interview feedback and notes' %}"></textarea>
|
||||
</div>
|
||||
{{interview_result_form|crispy}}
|
||||
<button type="submit" class="btn btn-main-action btn-sm mt-2">
|
||||
<i class="fas fa-check me-1"></i> {% trans "Update Result" %}
|
||||
</button>
|
||||
|
||||
@ -502,7 +502,7 @@
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<td id="document-{{ document.id }}">
|
||||
{% if document.file %}
|
||||
<a href="{{ document.file.url }}"
|
||||
class="btn btn-sm btn-outline-primary me-1"
|
||||
@ -510,9 +510,15 @@
|
||||
<i class="fas fa-download"></i>
|
||||
{% trans "Download" %}
|
||||
</a>
|
||||
<button class="btn btn-sm btn-outline-danger" onclick="deleteDocument({{ document.id }})">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
|
||||
|
||||
<button hx-post="{% url 'document_delete' document.pk %}"
|
||||
hx-target="#document-{{ document.id }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-confirm="{% trans 'Are you sure you want to delete this file?' %}"
|
||||
class="btn btn-sm btn-danger">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
@ -657,7 +663,7 @@ function addToCalendar(year, month, day, time, title) {
|
||||
|
||||
window.open(googleCalendarUrl, '_blank');
|
||||
}
|
||||
|
||||
{% comment %}
|
||||
function deleteDocument(documentId) {
|
||||
if (confirm('{% trans "Are you sure you want to delete this document?" %}')) {
|
||||
fetch(`/documents/${documentId}/delete/`, {
|
||||
@ -696,7 +702,7 @@ function getCookie(name) {
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
}
|
||||
} {% endcomment %}
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
@ -572,7 +572,7 @@
|
||||
<a href="{{ document.file.url }}" target="_blank" class="btn btn-sm btn-outline-secondary me-2"><i class="fas fa-eye"></i></a>
|
||||
|
||||
{# HTMX DELETE BUTTON #}
|
||||
<button hx-post="{% url 'application_document_delete' document.id %}"
|
||||
<button hx-post="{% url 'document_delete' document.pk %}"
|
||||
hx-target="#document-{{ document.id }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-confirm="{% trans 'Are you sure you want to delete this file?' %}"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user