interview result added and issue resolved #113
6
.env
6
.env
@ -1,3 +1,3 @@
|
|||||||
DB_NAME=norahuniversity
|
DB_NAME=haikal_db
|
||||||
DB_USER=norahuniversity
|
DB_USER=faheed
|
||||||
DB_PASSWORD=norahuniversity
|
DB_PASSWORD=Faheed@215
|
||||||
@ -208,7 +208,9 @@ ACCOUNT_LOGIN_ON_EMAIL_CONFIRMATION = True
|
|||||||
ACCOUNT_FORMS = {"signup": "recruitment.forms.StaffSignupForm"}
|
ACCOUNT_FORMS = {"signup": "recruitment.forms.StaffSignupForm"}
|
||||||
|
|
||||||
|
|
||||||
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
|
MAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||||
|
EMAIL_HOST = "10.10.1.110"
|
||||||
|
EMAIL_PORT = 2225
|
||||||
# EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
# EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||||
|
|
||||||
# EMAIL_HOST_PASSWORD = os.getenv("EMAIL_PASSWORD", "mssp.0Q0rSwb.zr6ke4n2k3e4on12.aHwJqnI")
|
# EMAIL_HOST_PASSWORD = os.getenv("EMAIL_PASSWORD", "mssp.0Q0rSwb.zr6ke4n2k3e4on12.aHwJqnI")
|
||||||
@ -217,10 +219,10 @@ EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
|
|||||||
# EMAIL_HOST_USER = "MS_lhygCJ@test-65qngkd8nx3lwr12.mlsender.net"
|
# EMAIL_HOST_USER = "MS_lhygCJ@test-65qngkd8nx3lwr12.mlsender.net"
|
||||||
# EMAIL_HOST_PASSWORD = "mssp.0Q0rSwb.zr6ke4n2k3e4on12.aHwJqnI"
|
# EMAIL_HOST_PASSWORD = "mssp.0Q0rSwb.zr6ke4n2k3e4on12.aHwJqnI"
|
||||||
# EMAIL_USE_TLS = True
|
# EMAIL_USE_TLS = True
|
||||||
EMAIL_HOST = 'sandbox.smtp.mailtrap.io'
|
# EMAIL_HOST = 'sandbox.smtp.mailtrap.io'
|
||||||
EMAIL_HOST_USER = '38e5179debe69a'
|
# EMAIL_HOST_USER = '38e5179debe69a'
|
||||||
EMAIL_HOST_PASSWORD = 'ffa75647d01ecb'
|
# EMAIL_HOST_PASSWORD = 'ffa75647d01ecb'
|
||||||
EMAIL_PORT = '2525'
|
# EMAIL_PORT = '2525'
|
||||||
|
|
||||||
|
|
||||||
# Crispy Forms Configuration
|
# Crispy Forms Configuration
|
||||||
|
|||||||
@ -1496,6 +1496,7 @@ class MessageForm(forms.ModelForm):
|
|||||||
self.helper.form_class = "g-3"
|
self.helper.form_class = "g-3"
|
||||||
|
|
||||||
self._filter_recipient_field()
|
self._filter_recipient_field()
|
||||||
|
self._filter_job_field()
|
||||||
|
|
||||||
self.helper.layout = Layout(
|
self.helper.layout = Layout(
|
||||||
Row(
|
Row(
|
||||||
@ -1516,6 +1517,7 @@ class MessageForm(forms.ModelForm):
|
|||||||
"""Filter job options based on user type"""
|
"""Filter job options based on user type"""
|
||||||
|
|
||||||
if self.user.user_type == "agency":
|
if self.user.user_type == "agency":
|
||||||
|
print("jhjkshfjksd")
|
||||||
|
|
||||||
job_assignments =AgencyJobAssignment.objects.filter(
|
job_assignments =AgencyJobAssignment.objects.filter(
|
||||||
agency__user=self.user,
|
agency__user=self.user,
|
||||||
@ -1528,11 +1530,18 @@ class MessageForm(forms.ModelForm):
|
|||||||
|
|
||||||
print("Agency user job queryset:", self.fields["job"].queryset)
|
print("Agency user job queryset:", self.fields["job"].queryset)
|
||||||
elif self.user.user_type == "candidate":
|
elif self.user.user_type == "candidate":
|
||||||
|
print("sjhdakjhsdkjashkdjhskd")
|
||||||
# Candidates can only see jobs they applied for
|
# 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(
|
self.fields["job"].queryset = JobPosting.objects.filter(
|
||||||
applications__person=self.user.person_profile,
|
applications__in=applications,
|
||||||
).distinct().order_by("-created_at")
|
).distinct().order_by("-created_at")
|
||||||
else:
|
else:
|
||||||
|
print("shhadjkhkd")
|
||||||
# Staff can see all jobs
|
# Staff can see all jobs
|
||||||
self.fields["job"].queryset = JobPosting.objects.filter(
|
self.fields["job"].queryset = JobPosting.objects.filter(
|
||||||
status="ACTIVE"
|
status="ACTIVE"
|
||||||
@ -2145,3 +2154,22 @@ KAAUH Hiring Team
|
|||||||
|
|
||||||
self.fields['message'].initial = initial_message
|
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")
|
STARTED = "started", _("Started")
|
||||||
ENDED = "ended", _("Ended")
|
ENDED = "ended", _("Ended")
|
||||||
CANCELLED = "cancelled", _("Cancelled")
|
CANCELLED = "cancelled", _("Cancelled")
|
||||||
|
|
||||||
|
class InterviewResult(models.TextChoices):
|
||||||
|
PASSED="passed",_("Passed")
|
||||||
|
FAILED="failed",_("Failed")
|
||||||
|
ON_HOLD="on_hold",_("ON Hold")
|
||||||
|
|
||||||
location_type = models.CharField(
|
location_type = models.CharField(
|
||||||
max_length=10,
|
max_length=10,
|
||||||
@ -1129,6 +1134,18 @@ class Interview(Base):
|
|||||||
verbose_name=_("Location Type"),
|
verbose_name=_("Location Type"),
|
||||||
db_index=True,
|
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
|
# Common fields
|
||||||
topic = models.CharField(
|
topic = models.CharField(
|
||||||
|
|||||||
@ -3,6 +3,7 @@ from django.core.mail import send_mail, EmailMessage
|
|||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.conf import settings # To access EMAIL_HOST_USER, etc.
|
from django.conf import settings # To access EMAIL_HOST_USER, etc.
|
||||||
|
from recruitment.models import Message
|
||||||
|
|
||||||
UserModel = get_user_model()
|
UserModel = get_user_model()
|
||||||
User = UserModel # Type alias for clarity
|
User = UserModel # Type alias for clarity
|
||||||
@ -17,28 +18,37 @@ class EmailService:
|
|||||||
subject: str,
|
subject: str,
|
||||||
body: str,
|
body: str,
|
||||||
recipient_list: List[str],
|
recipient_list: List[str],
|
||||||
|
context:dict,
|
||||||
from_email: str = settings.DEFAULT_FROM_EMAIL,
|
from_email: str = settings.DEFAULT_FROM_EMAIL,
|
||||||
html_content: Union[str, None] = None
|
html_content: Union[str, None] = None,
|
||||||
) -> int:
|
) -> int:
|
||||||
"""
|
"""
|
||||||
Internal method to handle the actual sending using Django's email backend.
|
Internal method to handle the actual sending using Django's email backend.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Using EmailMessage for more control (e.g., HTML content)
|
# Using EmailMessage for more control (e.g., HTML content)
|
||||||
email = EmailMessage(
|
|
||||||
subject=subject,
|
for recipient in recipient_list:
|
||||||
body=body,
|
email = EmailMessage(
|
||||||
from_email=from_email,
|
subject=subject,
|
||||||
to=recipient_list,
|
body=body,
|
||||||
)
|
from_email=from_email,
|
||||||
|
to=[recipient],
|
||||||
|
)
|
||||||
|
|
||||||
if html_content:
|
if html_content:
|
||||||
email.content_subtype = "html" # Main content is HTML
|
email.content_subtype = "html" # Main content is HTML
|
||||||
email.body = html_content # Overwrite body with HTML
|
email.body = html_content # Overwrite body with HTML
|
||||||
|
|
||||||
# Returns the number of successfully sent emails (usually 1 or the count of recipients)
|
# Returns the number of successfully sent emails (usually 1 or the count of recipients)
|
||||||
sent_count = email.send(fail_silently=False)
|
result=email.send(fail_silently=False)
|
||||||
return sent_count
|
recipient_user=User.objects.filter(email=recipient).first()
|
||||||
|
if result and recipient_user and not context["message_created"]:
|
||||||
|
Message.objects.create(sender=context['sender_user'],recipient=recipient_user,job=context['job'],subject=subject,content=context['email_message'],message_type='DIRECT',is_read=False)
|
||||||
|
|
||||||
|
|
||||||
|
return len(recipient_list)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Log the error (in a real app, use Django's logger)
|
# Log the error (in a real app, use Django's logger)
|
||||||
@ -46,34 +56,34 @@ class EmailService:
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def send_single_email(
|
# def send_single_email(
|
||||||
self,
|
# self,
|
||||||
user: User,
|
# user: User,
|
||||||
subject: str,
|
# subject: str,
|
||||||
template_name: str,
|
# template_name: str,
|
||||||
context: dict,
|
# context: dict,
|
||||||
from_email: str = settings.DEFAULT_FROM_EMAIL
|
# from_email: str = settings.DEFAULT_FROM_EMAIL
|
||||||
) -> int:
|
# ) -> int:
|
||||||
"""
|
# """
|
||||||
Sends a single, template-based email to one user.
|
# Sends a single, template-based email to one user.
|
||||||
"""
|
# """
|
||||||
recipient_list = [user.email]
|
# recipient_list = [user.email]
|
||||||
|
|
||||||
# 1. Render content from template
|
# # 1. Render content from template
|
||||||
html_content = render_to_string(template_name, context)
|
# html_content = render_to_string(template_name, context)
|
||||||
# You can optionally render a plain text version as well:
|
# # You can optionally render a plain text version as well:
|
||||||
# text_content = strip_tags(html_content)
|
# # text_content = strip_tags(html_content)
|
||||||
|
|
||||||
# 2. Call internal sender
|
# # 2. Call internal sender
|
||||||
return self._send_email_internal(
|
# return self._send_email_internal(
|
||||||
subject=subject,
|
# subject=subject,
|
||||||
body="", # Can be empty if html_content is provided
|
# body="", # Can be empty if html_content is provided
|
||||||
recipient_list=recipient_list,
|
# recipient_list=recipient_list,
|
||||||
from_email=from_email,
|
# from_email=from_email,
|
||||||
html_content=html_content
|
# html_content=html_content
|
||||||
)
|
# )
|
||||||
|
|
||||||
def send_bulk_email(
|
def send_email_service(
|
||||||
self,
|
self,
|
||||||
recipient_emails: List[str],
|
recipient_emails: List[str],
|
||||||
subject: str,
|
subject: str,
|
||||||
@ -98,8 +108,10 @@ class EmailService:
|
|||||||
subject=subject,
|
subject=subject,
|
||||||
body="",
|
body="",
|
||||||
recipient_list=recipient_emails,
|
recipient_list=recipient_emails,
|
||||||
|
context=context,
|
||||||
from_email=from_email,
|
from_email=from_email,
|
||||||
html_content=html_content
|
html_content=html_content,
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Return the count of recipients if successful, or 0 if failure
|
# Return the count of recipients if successful, or 0 if failure
|
||||||
|
|||||||
@ -1534,7 +1534,7 @@ def send_job_closed_notification(job_id):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def send_bulk_email_task(
|
def send_email_task(
|
||||||
recipient_emails,
|
recipient_emails,
|
||||||
subject: str,
|
subject: str,
|
||||||
template_name: str,
|
template_name: str,
|
||||||
@ -1551,7 +1551,7 @@ def send_bulk_email_task(
|
|||||||
service = EmailService()
|
service = EmailService()
|
||||||
|
|
||||||
# Execute the bulk sending method
|
# Execute the bulk sending method
|
||||||
processed_count = service.send_bulk_email(
|
processed_count = service.send_email_service(
|
||||||
recipient_emails=recipient_emails,
|
recipient_emails=recipient_emails,
|
||||||
subject=subject,
|
subject=subject,
|
||||||
template_name=template_name,
|
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}."
|
"message": f"Attempted to send email to {len(recipient_emails)} recipients. Service reported processing {processed_count}."
|
||||||
})
|
})
|
||||||
|
|
||||||
def send_single_email_task(
|
# def send_single_email_task(
|
||||||
recipient_emails,
|
# recipient_emails,
|
||||||
subject: str,
|
# subject: str,
|
||||||
template_name: str,
|
# template_name: str,
|
||||||
context: dict,
|
# context: dict,
|
||||||
) -> str:
|
# ) -> str:
|
||||||
"""
|
# """
|
||||||
Django-Q task to send a bulk email asynchronously.
|
# Django-Q task to send a bulk email asynchronously.
|
||||||
"""
|
# """
|
||||||
from .services.email_service import EmailService
|
# from .services.email_service import EmailService
|
||||||
|
|
||||||
if not recipient_emails:
|
# if not recipient_emails:
|
||||||
return json.dumps({"status": "error", "message": "No recipients provided."})
|
# return json.dumps({"status": "error", "message": "No recipients provided."})
|
||||||
|
|
||||||
service = EmailService()
|
# service = EmailService()
|
||||||
|
|
||||||
# Execute the bulk sending method
|
# # Execute the bulk sending method
|
||||||
processed_count = service.send_bulk_email(
|
# processed_count = service.send_bulk_email(
|
||||||
recipient_emails=recipient_emails,
|
# recipient_emails=recipient_emails,
|
||||||
subject=subject,
|
# subject=subject,
|
||||||
template_name=template_name,
|
# template_name=template_name,
|
||||||
context=context,
|
# context=context,
|
||||||
)
|
# )
|
||||||
|
|
||||||
# The return value is stored in the result object for monitoring
|
# # The return value is stored in the result object for monitoring
|
||||||
return json.dumps({
|
# return json.dumps({
|
||||||
"status": "success",
|
# "status": "success",
|
||||||
"count": processed_count,
|
# "count": processed_count,
|
||||||
"message": f"Attempted to send email to {len(recipient_emails)} recipients. Service reported processing {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/", views.interview_list, name="interview_list"),
|
||||||
path("interviews/<slug:slug>/", views.interview_detail, name="interview_detail"),
|
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_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("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"),
|
path("interview/<slug:slug>/interview-email/",views.send_interview_email,name="send_interview_email"),
|
||||||
|
|
||||||
|
|||||||
@ -94,6 +94,7 @@ from .forms import (
|
|||||||
InterviewCancelForm,
|
InterviewCancelForm,
|
||||||
InterviewEmailForm,
|
InterviewEmailForm,
|
||||||
ApplicationStageForm,
|
ApplicationStageForm,
|
||||||
|
InterviewResultForm
|
||||||
)
|
)
|
||||||
from .utils import generate_random_password
|
from .utils import generate_random_password
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
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):
|
for i, application in enumerate(applications):
|
||||||
if i < len(available_slots):
|
if i < len(available_slots):
|
||||||
slot = available_slots[i]
|
slot = available_slots[i]
|
||||||
|
# schedule=ScheduledInterview.objects.create(application=application,job=job)
|
||||||
async_task(
|
async_task(
|
||||||
"recruitment.tasks.create_interview_and_meeting",
|
"recruitment.tasks.create_interview_and_meeting",
|
||||||
application.pk,
|
application.pk,
|
||||||
@ -2141,6 +2143,7 @@ def reschedule_meeting_for_application(request, slug):
|
|||||||
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
if interview.location_type == "Remote":
|
if interview.location_type == "Remote":
|
||||||
|
|
||||||
form = ScheduledInterviewForm(request.POST)
|
form = ScheduledInterviewForm(request.POST)
|
||||||
else:
|
else:
|
||||||
form = OnsiteScheduleInterviewUpdateForm(request.POST)
|
form = OnsiteScheduleInterviewUpdateForm(request.POST)
|
||||||
@ -3055,6 +3058,8 @@ def applicant_portal_dashboard(request):
|
|||||||
|
|
||||||
# Get candidate's documents using the Person documents property
|
# Get candidate's documents using the Person documents property
|
||||||
documents = applicant.documents.order_by("-created_at")
|
documents = applicant.documents.order_by("-created_at")
|
||||||
|
|
||||||
|
print(documents)
|
||||||
|
|
||||||
# Add password change form for modal
|
# Add password change form for modal
|
||||||
password_form = PasswordResetForm()
|
password_form = PasswordResetForm()
|
||||||
@ -3653,10 +3658,8 @@ def message_detail(request, message_id):
|
|||||||
@login_required
|
@login_required
|
||||||
def message_create(request):
|
def message_create(request):
|
||||||
"""Create a new message"""
|
"""Create a new message"""
|
||||||
from .email_service import EmailService
|
from django.conf import settings
|
||||||
from .services.email_service import UnifiedEmailService
|
from django_q.tasks import async_task
|
||||||
from .dto.email_dto import EmailConfig, BulkEmailConfig, EmailPriority
|
|
||||||
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
form = MessageForm(request.user, request.POST)
|
form = MessageForm(request.user, request.POST)
|
||||||
|
|
||||||
@ -3679,22 +3682,25 @@ def message_create(request):
|
|||||||
# from .services.email_service import UnifiedEmailService
|
# from .services.email_service import UnifiedEmailService
|
||||||
# from .dto.email_dto import EmailConfig, EmailPriority
|
# from .dto.email_dto import EmailConfig, EmailPriority
|
||||||
|
|
||||||
service = UnifiedEmailService()
|
|
||||||
|
|
||||||
# Create email configuration
|
email_addresses = [message.recipient.email]
|
||||||
config = EmailConfig(
|
subject=message.subject
|
||||||
to_email=message.recipient.email,
|
|
||||||
subject=message.subject,
|
email_result=async_task(
|
||||||
html_content=body,
|
"recruitment.tasks.send_email_task",
|
||||||
attachments=None,
|
email_addresses,
|
||||||
sender=request.user
|
subject,
|
||||||
if request and hasattr(request, "user")
|
# message,
|
||||||
else None,
|
"emails/email_template.html",
|
||||||
priority=EmailPriority.NORMAL,
|
{
|
||||||
|
"email_message": body,
|
||||||
|
"logo_url": settings.STATIC_URL + "image/kaauh.png",
|
||||||
|
"message_created":True
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
# Send email using unified service
|
# Send email using unified service
|
||||||
email_result = service.send_email(config)
|
|
||||||
if email_result:
|
if email_result:
|
||||||
messages.success(
|
messages.success(
|
||||||
request, "Message sent successfully via email!"
|
request, "Message sent successfully via email!"
|
||||||
@ -3749,7 +3755,7 @@ def message_create(request):
|
|||||||
and "HX-Request" in request.headers
|
and "HX-Request" in request.headers
|
||||||
and request.user.user_type in ["candidate", "agency"]
|
and request.user.user_type in ["candidate", "agency"]
|
||||||
):
|
):
|
||||||
print()
|
|
||||||
job_id = request.GET.get("job")
|
job_id = request.GET.get("job")
|
||||||
if job_id:
|
if job_id:
|
||||||
job = get_object_or_404(JobPosting, id=job_id)
|
job = get_object_or_404(JobPosting, id=job_id)
|
||||||
@ -4251,7 +4257,7 @@ def cancel_interview_for_application(request, slug):
|
|||||||
Handles POST request to cancel an interview, setting the status
|
Handles POST request to cancel an interview, setting the status
|
||||||
and saving the form data (likely a reason for cancellation).
|
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)
|
form = InterviewCancelForm(request.POST, instance=scheduled_interview)
|
||||||
|
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
@ -4275,6 +4281,33 @@ def cancel_interview_for_application(request, slug):
|
|||||||
return redirect("interview_detail", slug=scheduled_interview.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
|
@login_required
|
||||||
@staff_user_required
|
@staff_user_required
|
||||||
def agency_access_link_deactivate(request, slug):
|
def agency_access_link_deactivate(request, slug):
|
||||||
@ -4389,161 +4422,85 @@ def api_application_detail(request, candidate_id):
|
|||||||
return JsonResponse({"success": False, "error": str(e)})
|
return JsonResponse({"success": False, "error": str(e)})
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
# @login_required
|
||||||
@staff_user_required
|
# @staff_user_required
|
||||||
def compose_application_email(request, slug):
|
# def compose_application_email(request, slug):
|
||||||
"""Compose email to participants about a candidate"""
|
# """Compose email to participants about a candidate"""
|
||||||
from .email_service import send_bulk_email
|
# from django.conf import settings
|
||||||
from .services.email_service import EmailService
|
|
||||||
from .dto.email_dto import BulkEmailConfig, EmailPriority
|
|
||||||
|
|
||||||
job = get_object_or_404(JobPosting, slug=slug)
|
# job = get_object_or_404(JobPosting, slug=slug)
|
||||||
candidate_ids = request.GET.getlist("candidate_ids")
|
# candidate_ids = request.GET.getlist("candidate_ids")
|
||||||
candidates = Application.objects.filter(id__in=candidate_ids)
|
# candidates = Application.objects.filter(id__in=candidate_ids)
|
||||||
|
|
||||||
if request.method == "POST":
|
# if request.method == "POST":
|
||||||
candidate_ids = request.POST.getlist("candidate_ids")
|
# candidate_ids = request.POST.getlist("candidate_ids")
|
||||||
|
|
||||||
applications = Application.objects.filter(id__in=candidate_ids)
|
# applications = Application.objects.filter(id__in=candidate_ids)
|
||||||
form = CandidateEmailForm(job, applications, request.POST)
|
# form = CandidateEmailForm(job, applications, request.POST)
|
||||||
|
|
||||||
if form.is_valid():
|
# if form.is_valid():
|
||||||
# Get email addresses
|
# # Get email addresses
|
||||||
email_addresses = form.get_email_addresses()
|
# email_addresses = form.get_email_addresses()
|
||||||
|
|
||||||
if not email_addresses:
|
# if not email_addresses:
|
||||||
messages.error(request, "No email selected")
|
# messages.error(request, "No email selected")
|
||||||
referer = request.META.get("HTTP_REFERER")
|
# referer = request.META.get("HTTP_REFERER")
|
||||||
|
|
||||||
if referer:
|
# if referer:
|
||||||
# Redirect back to the referring page
|
# # Redirect back to the referring page
|
||||||
return redirect(referer)
|
# return redirect(referer)
|
||||||
else:
|
# else:
|
||||||
return redirect("dashboard")
|
# return redirect("dashboard")
|
||||||
|
|
||||||
subject = form.cleaned_data.get("subject")
|
# subject = form.cleaned_data.get("subject")
|
||||||
message = form.get_formatted_message()
|
# message = form.get_formatted_message()
|
||||||
|
|
||||||
service = EmailService()
|
|
||||||
|
# async_task(
|
||||||
|
# "recruitment.tasks.send_bulk_email_task",
|
||||||
|
# email_addresses,
|
||||||
|
# subject,
|
||||||
|
# # message,
|
||||||
|
# "emails/email_template.html",
|
||||||
|
# {
|
||||||
|
# "job": job,
|
||||||
|
# "applications": applications,
|
||||||
|
# "email_message": message,
|
||||||
|
# "logo_url": settings.STATIC_URL + "image/kaauh.png",
|
||||||
|
# },
|
||||||
|
# )
|
||||||
|
# return redirect(request.path)
|
||||||
|
|
||||||
|
|
||||||
# Prepare recipients data for bulk email
|
# else:
|
||||||
# recipients_data = []
|
# # Form validation errors
|
||||||
# for email_addr in email_addresses:
|
# messages.error(request, "Please correct the errors below.")
|
||||||
# recipients_data.append(
|
|
||||||
# {
|
|
||||||
# "email": email_addr,
|
|
||||||
# "name": email_addr.split("@")[0]
|
|
||||||
# if "@" in email_addr
|
|
||||||
# else email_addr,
|
|
||||||
# }
|
|
||||||
# )
|
|
||||||
from django.conf import settings
|
|
||||||
async_task(
|
|
||||||
"recruitment.tasks.send_bulk_email_task",
|
|
||||||
email_addresses,
|
|
||||||
subject,
|
|
||||||
# message,
|
|
||||||
"emails/email_template.html",
|
|
||||||
{
|
|
||||||
"job": job,
|
|
||||||
"applications": applications,
|
|
||||||
"email_message": message,
|
|
||||||
"logo_url": settings.STATIC_URL + "image/kaauh.png",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
# Create bulk email configuration
|
|
||||||
# bulk_config = BulkEmailConfig(
|
|
||||||
# subject=subject,
|
|
||||||
# recipients_data=recipients_data,
|
|
||||||
# attachments=None,
|
|
||||||
# sender=request.user if request and hasattr(request, "user") else None,
|
|
||||||
# job=job,
|
|
||||||
# priority=EmailPriority.NORMAL,
|
|
||||||
# async_send=True,
|
|
||||||
# )
|
|
||||||
|
|
||||||
# Send bulk emails
|
# # For HTMX requests, return error response
|
||||||
# if email_result["success"]:
|
# if "HX-Request" in request.headers:
|
||||||
# for application in applications:
|
# return JsonResponse(
|
||||||
# if hasattr(application, "person") and application.person:
|
# {
|
||||||
# try:
|
# "success": False,
|
||||||
# Message.objects.create(
|
# "error": "Please correct the form errors and try again.",
|
||||||
# sender=request.user,
|
# }
|
||||||
# recipient=application.person.user,
|
# )
|
||||||
# subject=subject,
|
|
||||||
# content=message,
|
|
||||||
# job=job,
|
|
||||||
# message_type="job_related",
|
|
||||||
# is_email_sent=True,
|
|
||||||
# email_address=application.person.email
|
|
||||||
# if application.person.email
|
|
||||||
# else application.email,
|
|
||||||
# )
|
|
||||||
|
|
||||||
# except Exception as e:
|
# return render(
|
||||||
# # Log error but don't fail the entire process
|
# request,
|
||||||
# print(f"Error creating message")
|
# "includes/email_compose_form.html",
|
||||||
|
# {"form": form, "job": job, "candidates": candidates},
|
||||||
|
# )
|
||||||
|
|
||||||
# messages.success(
|
# else:
|
||||||
# request,
|
# # GET request - show the form
|
||||||
# f"Email will be sent shortly to recipient(s)",
|
# form = CandidateEmailForm(job, candidates)
|
||||||
# )
|
|
||||||
# response = HttpResponse(status=200)
|
|
||||||
# response.headers["HX-Refresh"] = "true"
|
|
||||||
# return response
|
|
||||||
# # return redirect("applications_interview_view", slug=job.slug)
|
|
||||||
# else:
|
|
||||||
# messages.error(
|
|
||||||
# request,
|
|
||||||
# f"Failed to send email: {email_result.get('message', 'Unknown error')}",
|
|
||||||
# )
|
|
||||||
|
|
||||||
# # For HTMX requests, return error response
|
# return render(
|
||||||
# if "HX-Request" in request.headers:
|
# request,
|
||||||
# return JsonResponse(
|
# "includes/email_compose_form.html",
|
||||||
# {
|
# # {"form": form, "job": job, "candidates": candidates},
|
||||||
# "success": False,
|
# {"form": form, "job": job},
|
||||||
# "error": email_result.get(
|
# )
|
||||||
# "message", "Failed to send email"
|
|
||||||
# ),
|
|
||||||
# }
|
|
||||||
# )
|
|
||||||
|
|
||||||
# return render(
|
|
||||||
# request,
|
|
||||||
# "includes/email_compose_form.html",
|
|
||||||
# {"form": form, "job": job, "candidate": candidates},
|
|
||||||
# )
|
|
||||||
|
|
||||||
else:
|
|
||||||
# Form validation errors
|
|
||||||
messages.error(request, "Please correct the errors below.")
|
|
||||||
|
|
||||||
# For HTMX requests, return error response
|
|
||||||
if "HX-Request" in request.headers:
|
|
||||||
return JsonResponse(
|
|
||||||
{
|
|
||||||
"success": False,
|
|
||||||
"error": "Please correct the form errors and try again.",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return render(
|
|
||||||
request,
|
|
||||||
"includes/email_compose_form.html",
|
|
||||||
{"form": form, "job": job, "candidates": candidates},
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
# GET request - show the form
|
|
||||||
form = CandidateEmailForm(job, candidates)
|
|
||||||
|
|
||||||
return render(
|
|
||||||
request,
|
|
||||||
"includes/email_compose_form.html",
|
|
||||||
# {"form": form, "job": job, "candidates": candidates},
|
|
||||||
{"form": form, "job": job},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# Source CRUD Views
|
# Source CRUD Views
|
||||||
@ -4799,6 +4756,7 @@ def application_signup(request, slug):
|
|||||||
@login_required
|
@login_required
|
||||||
@staff_user_required
|
@staff_user_required
|
||||||
def interview_list(request):
|
def interview_list(request):
|
||||||
|
|
||||||
"""List all interviews with filtering and pagination"""
|
"""List all interviews with filtering and pagination"""
|
||||||
interviews = ScheduledInterview.objects.select_related(
|
interviews = ScheduledInterview.objects.select_related(
|
||||||
"application",
|
"application",
|
||||||
@ -4825,7 +4783,7 @@ def interview_list(request):
|
|||||||
interviews = interviews.filter(
|
interviews = interviews.filter(
|
||||||
Q(application__person__first_name=search_query)
|
Q(application__person__first_name=search_query)
|
||||||
| Q(application__person__last_name__icontains=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)
|
| Q(job__title__icontains=search_query)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -4854,8 +4812,11 @@ def interview_detail(request, slug):
|
|||||||
OnsiteScheduleInterviewUpdateForm,
|
OnsiteScheduleInterviewUpdateForm,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
schedule = get_object_or_404(ScheduledInterview, slug=slug)
|
schedule = get_object_or_404(ScheduledInterview, slug=slug)
|
||||||
interview = schedule.interview
|
interview = schedule.interview
|
||||||
|
interview_result_form=InterviewResultForm(instance=interview)
|
||||||
application = schedule.application
|
application = schedule.application
|
||||||
job = schedule.job
|
job = schedule.job
|
||||||
print(interview.location_type)
|
print(interview.location_type)
|
||||||
@ -4878,6 +4839,7 @@ def interview_detail(request, slug):
|
|||||||
"interview_status_form": ScheduledInterviewUpdateStatusForm(),
|
"interview_status_form": ScheduledInterviewUpdateStatusForm(),
|
||||||
"cancel_form": InterviewCancelForm(instance=meeting),
|
"cancel_form": InterviewCancelForm(instance=meeting),
|
||||||
"interview_email_form": interview_email_form,
|
"interview_email_form": interview_email_form,
|
||||||
|
"interview_result_form":interview_result_form,
|
||||||
}
|
}
|
||||||
return render(request, "interviews/interview_detail.html", context)
|
return render(request, "interviews/interview_detail.html", context)
|
||||||
|
|
||||||
@ -6487,55 +6449,196 @@ def sync_history(request, job_slug=None):
|
|||||||
return render(request, "recruitment/sync_history.html", context)
|
return render(request, "recruitment/sync_history.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
# def send_interview_email(request, slug):
|
||||||
|
# from django.conf import settings
|
||||||
|
# schedule = get_object_or_404(ScheduledInterview, slug=slug)
|
||||||
|
# application = schedule.application
|
||||||
|
# job = application.job
|
||||||
|
# form = InterviewEmailForm(job, application, schedule)
|
||||||
|
# if request.method == "POST":
|
||||||
|
# form = InterviewEmailForm(job, application, schedule, request.POST)
|
||||||
|
# if form.is_valid():
|
||||||
|
# recipient = form.cleaned_data.get("to").strip()
|
||||||
|
# body_message = form.cleaned_data.get("message")
|
||||||
|
# subject = form.cleaned_data.get("subject")
|
||||||
|
# sender_user = request.user
|
||||||
|
# job = job
|
||||||
|
# try:
|
||||||
|
|
||||||
|
# # Send email using background task
|
||||||
|
# email_result= async_task(
|
||||||
|
# "recruitment.tasks.send_bulk_email_task",
|
||||||
|
# recipient,
|
||||||
|
# subject,
|
||||||
|
# # message,
|
||||||
|
# "emails/email_template.html",
|
||||||
|
# {
|
||||||
|
# "job": job,
|
||||||
|
# "applications": application,
|
||||||
|
# "email_message":body_message,
|
||||||
|
# "logo_url": settings.STATIC_URL + "image/kaauh.png",
|
||||||
|
# },
|
||||||
|
# )
|
||||||
|
|
||||||
|
# if email_result:
|
||||||
|
# messages.success(request, "Message sent successfully via email!")
|
||||||
|
# else:
|
||||||
|
# messages.warning(
|
||||||
|
# request,
|
||||||
|
# f"email failed: {email_result.get('message', 'Unknown error')}",
|
||||||
|
# )
|
||||||
|
|
||||||
|
# except Exception as e:
|
||||||
|
# messages.warning(
|
||||||
|
# request, f"Message saved but email sending failed: {str(e)}"
|
||||||
|
# )
|
||||||
|
# else:
|
||||||
|
# form = InterviewEmailForm(job, application, schedule)
|
||||||
|
# else: # GET request
|
||||||
|
# form = InterviewEmailForm(job, application, schedule)
|
||||||
|
|
||||||
|
# # This is the final return, which handles GET requests and invalid POST requests.
|
||||||
|
# return redirect("interview_detail", slug=schedule.slug)
|
||||||
|
|
||||||
|
|
||||||
def send_interview_email(request, slug):
|
def send_interview_email(request, slug):
|
||||||
|
from django.conf import settings
|
||||||
|
from django_q.tasks import async_task
|
||||||
|
|
||||||
schedule = get_object_or_404(ScheduledInterview, slug=slug)
|
schedule = get_object_or_404(ScheduledInterview, slug=slug)
|
||||||
application = schedule.application
|
application = schedule.application
|
||||||
job = application.job
|
job = application.job
|
||||||
form = InterviewEmailForm(job, application, schedule)
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
form = InterviewEmailForm(job, application, schedule, request.POST)
|
form = InterviewEmailForm(job, application, schedule, request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
recipient = form.cleaned_data.get("to").strip()
|
# 1. Ensure recipient is a list (fixes the "@" error)
|
||||||
|
recipient_str = form.cleaned_data.get("to").strip()
|
||||||
|
recipient_list = [recipient_str]
|
||||||
|
|
||||||
body_message = form.cleaned_data.get("message")
|
body_message = form.cleaned_data.get("message")
|
||||||
subject = form.cleaned_data.get("subject")
|
subject = form.cleaned_data.get("subject")
|
||||||
sender = request.user
|
|
||||||
job = job
|
|
||||||
try:
|
try:
|
||||||
# Use new unified email service for background processing
|
# 2. Match the context expected by your task/service
|
||||||
from .services.email_service import UnifiedEmailService
|
# We pass IDs for the sender/job to avoid serialization issues
|
||||||
from .dto.email_dto import EmailConfig, EmailPriority
|
async_task(
|
||||||
|
"recruitment.tasks.send_email_task",
|
||||||
service = UnifiedEmailService()
|
recipient_list,
|
||||||
|
subject,
|
||||||
# Create email configuration
|
"emails/email_template.html",
|
||||||
config = EmailConfig(
|
{
|
||||||
to_email=recipient,
|
"job": job, # Useful for Message creation
|
||||||
subject=subject,
|
"sender_user": request.user,
|
||||||
html_content=body_message,
|
"applications": application,
|
||||||
attachments=None,
|
"email_message": body_message,
|
||||||
sender=sender,
|
"message_created":False,
|
||||||
job=job,
|
"logo_url": settings.STATIC_URL + "image/kaauh.png",
|
||||||
priority=EmailPriority.NORMAL,
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
# Send email using background task
|
messages.success(request, "Interview email enqueued successfully!")
|
||||||
email_result = service.send_email(config)
|
return redirect("interview_detail", slug=schedule.slug)
|
||||||
if email_result:
|
|
||||||
messages.success(request, "Message sent successfully via email!")
|
|
||||||
else:
|
|
||||||
messages.warning(
|
|
||||||
request,
|
|
||||||
f"email failed: {email_result.get('message', 'Unknown error')}",
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
messages.warning(
|
messages.error(request, f"Task scheduling failed: {str(e)}")
|
||||||
request, f"Message saved but email sending failed: {str(e)}"
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
form = InterviewEmailForm(job, application, schedule)
|
messages.error(request, "Please correct the errors in the form.")
|
||||||
else: # GET request
|
else:
|
||||||
|
# GET request
|
||||||
form = InterviewEmailForm(job, application, schedule)
|
form = InterviewEmailForm(job, application, schedule)
|
||||||
|
|
||||||
# This is the final return, which handles GET requests and invalid POST requests.
|
# 3. FIX: Instead of always redirecting, render the template
|
||||||
return redirect("interview_detail", slug=schedule.slug)
|
# This allows users to see validation errors.
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
"recruitment/interview_email_form.html", # Replace with your actual template path
|
||||||
|
{
|
||||||
|
"form": form,
|
||||||
|
"schedule": schedule,
|
||||||
|
"job": job
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@staff_user_required
|
||||||
|
def compose_application_email(request, slug):
|
||||||
|
"""Compose email to participants about a candidate"""
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
job = get_object_or_404(JobPosting, slug=slug)
|
||||||
|
candidate_ids = request.GET.getlist("candidate_ids")
|
||||||
|
candidates = Application.objects.filter(id__in=candidate_ids)
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
candidate_ids = request.POST.getlist("candidate_ids")
|
||||||
|
|
||||||
|
applications = Application.objects.filter(id__in=candidate_ids)
|
||||||
|
form = CandidateEmailForm(job, applications, request.POST)
|
||||||
|
|
||||||
|
if form.is_valid():
|
||||||
|
# Get email addresses
|
||||||
|
email_addresses = form.get_email_addresses()
|
||||||
|
|
||||||
|
if not email_addresses:
|
||||||
|
messages.error(request, "No email selected")
|
||||||
|
referer = request.META.get("HTTP_REFERER")
|
||||||
|
|
||||||
|
if referer:
|
||||||
|
# Redirect back to the referring page
|
||||||
|
return redirect(referer)
|
||||||
|
else:
|
||||||
|
return redirect("dashboard")
|
||||||
|
|
||||||
|
subject = form.cleaned_data.get("subject")
|
||||||
|
message = form.get_formatted_message()
|
||||||
|
|
||||||
|
|
||||||
|
async_task(
|
||||||
|
"recruitment.tasks.send_email_task",
|
||||||
|
email_addresses,
|
||||||
|
subject,
|
||||||
|
# message,
|
||||||
|
"emails/email_template.html",
|
||||||
|
{
|
||||||
|
"job": job,
|
||||||
|
"sender_user": request.user,
|
||||||
|
"applications": applications,
|
||||||
|
"email_message": message,
|
||||||
|
"message_created":False,
|
||||||
|
"logo_url": settings.STATIC_URL + "image/kaauh.png",
|
||||||
|
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return redirect(request.path)
|
||||||
|
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Form validation errors
|
||||||
|
messages.error(request, "Please correct the errors below.")
|
||||||
|
|
||||||
|
# For HTMX requests, return error response
|
||||||
|
if "HX-Request" in request.headers:
|
||||||
|
return JsonResponse(
|
||||||
|
{
|
||||||
|
"success": False,
|
||||||
|
"error": "Please correct the form errors and try again.",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
"includes/email_compose_form.html",
|
||||||
|
{"form": form, "job": job, "candidates": candidates},
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# GET request - show the form
|
||||||
|
form = CandidateEmailForm(job, candidates)
|
||||||
|
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
"includes/email_compose_form.html",
|
||||||
|
# {"form": form, "job": job, "candidates": candidates},
|
||||||
|
{"form": form, "job": job},
|
||||||
|
)
|
||||||
|
|||||||
@ -360,6 +360,7 @@
|
|||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
|
{% if request.user.is_authenticated%}
|
||||||
<li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
<button class="language-toggle-btn dropdown-toggle" type="button"
|
<button class="language-toggle-btn dropdown-toggle" type="button"
|
||||||
data-bs-toggle="dropdown" data-bs-offset="0, 8" aria-expanded="false"
|
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>
|
<i class="fas fa-tachometer-alt me-3 fs-5"></i> <span>{% trans "Dashboard" %}</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<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>
|
<i class="fas fa-user-circle me-3 fs-5"></i> <span>{% trans "My Profile" %}</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
|
||||||
<li><hr class="dropdown-divider my-1"></li>
|
<li><hr class="dropdown-divider my-1"></li>
|
||||||
<li>
|
<li>
|
||||||
@ -432,6 +435,8 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -670,22 +670,9 @@
|
|||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form method="post" action="#">
|
<form method="post" action="{% url 'update_interview_result' interview.slug %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="mb-3">
|
{{interview_result_form|crispy}}
|
||||||
<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>
|
|
||||||
<button type="submit" class="btn btn-main-action btn-sm mt-2">
|
<button type="submit" class="btn btn-main-action btn-sm mt-2">
|
||||||
<i class="fas fa-check me-1"></i> {% trans "Update Result" %}
|
<i class="fas fa-check me-1"></i> {% trans "Update Result" %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -502,7 +502,7 @@
|
|||||||
-
|
-
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td id="document-{{ document.id }}">
|
||||||
{% if document.file %}
|
{% if document.file %}
|
||||||
<a href="{{ document.file.url }}"
|
<a href="{{ document.file.url }}"
|
||||||
class="btn btn-sm btn-outline-primary me-1"
|
class="btn btn-sm btn-outline-primary me-1"
|
||||||
@ -510,9 +510,15 @@
|
|||||||
<i class="fas fa-download"></i>
|
<i class="fas fa-download"></i>
|
||||||
{% trans "Download" %}
|
{% trans "Download" %}
|
||||||
</a>
|
</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 %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -657,7 +663,7 @@ function addToCalendar(year, month, day, time, title) {
|
|||||||
|
|
||||||
window.open(googleCalendarUrl, '_blank');
|
window.open(googleCalendarUrl, '_blank');
|
||||||
}
|
}
|
||||||
|
{% comment %}
|
||||||
function deleteDocument(documentId) {
|
function deleteDocument(documentId) {
|
||||||
if (confirm('{% trans "Are you sure you want to delete this document?" %}')) {
|
if (confirm('{% trans "Are you sure you want to delete this document?" %}')) {
|
||||||
fetch(`/documents/${documentId}/delete/`, {
|
fetch(`/documents/${documentId}/delete/`, {
|
||||||
@ -696,7 +702,7 @@ function getCookie(name) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return cookieValue;
|
return cookieValue;
|
||||||
}
|
} {% endcomment %}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% endblock %}
|
{% 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>
|
<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 #}
|
{# 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-target="#document-{{ document.id }}"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
hx-confirm="{% trans 'Are you sure you want to delete this file?' %}"
|
hx-confirm="{% trans 'Are you sure you want to delete this file?' %}"
|
||||||
|
|||||||
@ -92,14 +92,14 @@
|
|||||||
class="btn btn-sm btn-outline-secondary" title="Edit Setting">
|
class="btn btn-sm btn-outline-secondary" title="Edit Setting">
|
||||||
<i class="fas fa-edit"></i>
|
<i class="fas fa-edit"></i>
|
||||||
</a>
|
</a>
|
||||||
<form method="post" action="{% url 'settings_delete' pk=setting.pk %}"
|
{% comment %} <form method="post" action="{% url 'settings_delete' pk=setting.pk %}"
|
||||||
onsubmit="return confirm('Are you sure you want to delete this setting?');"
|
onsubmit="return confirm('Are you sure you want to delete this setting?');"
|
||||||
style="display: inline;">
|
style="display: inline;">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button type="submit" class="btn btn-sm btn-outline-danger" title="Delete Setting">
|
<button type="submit" class="btn btn-sm btn-outline-danger" title="Delete Setting">
|
||||||
<i class="fas fa-trash"></i>
|
<i class="fas fa-trash"></i>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form> {% endcomment %}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user