email send from interview detail page

This commit is contained in:
Faheed 2025-12-11 14:29:00 +03:00
parent d90a8d5048
commit d0975094f6
4 changed files with 143 additions and 173 deletions

View File

@ -237,14 +237,13 @@ def send_interview_invitation_email(candidate, job, meeting_details=None, recipi
def send_bulk_email(subject, message, recipient_list, request=None, attachments=None, async_task_=False, from_interview=False,job=None): def send_bulk_email(subject, message, recipient_list, request=None, attachments=None, async_task_=False,job=None):
""" """
Send bulk email to multiple recipients with HTML support and attachments, Send bulk email to multiple recipients with HTML support and attachments,
supporting synchronous or asynchronous dispatch. supporting synchronous or asynchronous dispatch.
""" """
# --- 1. Categorization and Custom Message Preparation (CORRECTED) --- # --- 1. Categorization and Custom Message Preparation (CORRECTED) ---
if not from_interview:
agency_emails = [] agency_emails = []
pure_candidate_emails = [] pure_candidate_emails = []
@ -292,9 +291,7 @@ def send_bulk_email(subject, message, recipient_list, request=None, attachments=
if total_recipients == 0: if total_recipients == 0:
return {'success': False, 'error': 'No valid recipients found for sending.'} return {'success': False, 'error': 'No valid recipients found for sending.'}
else:
# For interview flow
total_recipients = len(recipient_list)
# --- 2. Handle ASYNC Dispatch (FIXED: Single loop used) --- # --- 2. Handle ASYNC Dispatch (FIXED: Single loop used) ---
@ -306,7 +303,7 @@ def send_bulk_email(subject, message, recipient_list, request=None, attachments=
job_id=job.id job_id=job.id
sender_user_id=request.user.id if request and hasattr(request, 'user') and request.user.is_authenticated else None sender_user_id=request.user.id if request and hasattr(request, 'user') and request.user.is_authenticated else None
if not from_interview:
# Loop through ALL final customized sends # Loop through ALL final customized sends
@ -331,25 +328,6 @@ def send_bulk_email(subject, message, recipient_list, request=None, attachments=
'message': f'Emails queued for background sending to {len(task_ids)} recipient(s).' 'message': f'Emails queued for background sending to {len(task_ids)} recipient(s).'
} }
else: # from_interview is True (generic send to all participants)
task_id = async_task(
'recruitment.tasks.send_bulk_email_task',
subject,
message,
recipient_list, # Send the original message to the entire list
processed_attachments,
hook='recruitment.tasks.email_success_hook'
)
task_ids.append(task_id)
logger.info(f"Interview emails queued. ID: {task_id}")
return {
'success': True,
'async': True,
'task_ids': task_ids,
'message': f'Interview emails queued for background sending to {total_recipients} recipient(s)'
}
except ImportError: except ImportError:
logger.error("Async execution requested, but django_q or required modules not found. Defaulting to sync.") logger.error("Async execution requested, but django_q or required modules not found. Defaulting to sync.")
@ -398,7 +376,7 @@ def send_bulk_email(subject, message, recipient_list, request=None, attachments=
except Exception as e: except Exception as e:
logger.error(f"Failed to send email to {recipient}: {str(e)}", exc_info=True) logger.error(f"Failed to send email to {recipient}: {str(e)}", exc_info=True)
if not from_interview:
# Send Emails - Pure Candidates # Send Emails - Pure Candidates
for email in pure_candidate_emails: for email in pure_candidate_emails:
candidate_name = Application.objects.filter(email=email).first().first_name candidate_name = Application.objects.filter(email=email).first().first_name
@ -420,16 +398,7 @@ def send_bulk_email(subject, message, recipient_list, request=None, attachments=
'recipients_count': successful_sends, 'recipients_count': successful_sends,
'message': f'Email processing complete. {successful_sends} email(s) were sent successfully to {total_recipients} unique intended recipients.' 'message': f'Email processing complete. {successful_sends} email(s) were sent successfully to {total_recipients} unique intended recipients.'
} }
else:
for email in recipient_list:
send_individual_email(email, message)
logger.info(f"Interview email processing complete. Sent successfully to {successful_sends} out of {total_recipients} recipients.")
return {
'success': True,
'recipients_count': successful_sends,
'message': f'Interview emails sent successfully to {successful_sends} recipient(s).'
}
except Exception as e: except Exception as e:
error_msg = f"Failed to process bulk email send request: {str(e)}" error_msg = f"Failed to process bulk email send request: {str(e)}"

View File

@ -2036,12 +2036,11 @@ class SettingsForm(forms.ModelForm):
class InterviewEmailForm(forms.Form): class InterviewEmailForm(forms.Form):
"""Form for composing emails to participants about a candidate""" """Form for composing emails to participants about a candidate"""
to = forms.MultipleChoiceField( to = forms.CharField(
widget=forms.CheckboxSelectMultiple(attrs={
'class': 'form-check' label=_('To'), # Use a descriptive label
}), required=True,
label=_('Select Candidates'), # Use a descriptive label
required=False
) )
subject = forms.CharField( subject = forms.CharField(
@ -2069,15 +2068,17 @@ class InterviewEmailForm(forms.Form):
def __init__(self, job, application,schedule, *args, **kwargs): def __init__(self, job, application,schedule, *args, **kwargs):
applicant=application.person.user applicant=application.person.user
interview=schedule.interview interview=schedule.interview
super().__init__(*args, **kwargs)
if application.hiring_agency: if application.hiring_agency:
self.fields['to'].initial=application.hiring_agency.email self.fields['to'].initial=application.hiring_agency.email
self.fields['to'].disabled= True
else: else:
self.fields['to'].initial=application.person.email self.fields['to'].initial=application.person.email
self.fields['to'].disabled= True
super().__init__(*args, **kwargs)
# Set initial message with candidate and meeting info # Set initial message with candidate and meeting info
initial_message = f""" initial_message = f"""
@ -2085,20 +2086,21 @@ Dear {applicant.first_name} {applicant.last_name},
Your interview details are as follows: Your interview details are as follows:
Date: {interview.interview_date} Date: {interview.start_time.strftime("%d-%m-%Y")}
Time: {interview.interview_time} Time: {interview.start_time.strftime("%I:%M %p")}
Interview Duration: {interview.duration} minutes
Job: {job.title} Job: {job.title}
""" """
if schedule.location_type == 'Remote': if interview.location_type == 'Remote':
initial_message += "This is a remote schedule. You will receive the meeting link separately.\n\n" initial_message += f"Pease join using meeting link {interview.details_url} .\n\n"
else: else:
email_body += "This is an onsite schedule. Please arrive 10 minutes early.\n\n" initial_message += "This is an onsite schedule. Please arrive 10 minutes early.\n\n"
initial_message += """ initial_message += """
Best regards, Best regards,
HR Team KAAUH Hiring Team
""" """
self.fields['message'].initial = initial_message self.fields['message'].initial = initial_message

View File

@ -90,7 +90,8 @@ from .forms import (
BulkInterviewTemplateForm, BulkInterviewTemplateForm,
SettingsForm, SettingsForm,
InterviewCancelForm, InterviewCancelForm,
InterviewEmailForm InterviewEmailForm,
ApplicationStageForm
) )
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
@ -1018,13 +1019,15 @@ def delete_form_template(request, template_id):
) )
@login_required # @login_required
@staff_or_candidate_required # @staff_or_candidate_required
def application_submit_form(request, template_slug): def application_submit_form(request, slug):
"""Display the form as a step-by-step wizard""" """Display the form as a step-by-step wizard"""
form_template=get_object_or_404(FormTemplate,slug=slug,is_active=True)
if not request.user.is_authenticated: if not request.user.is_authenticated:
return redirect("application_signup",slug=slug) return redirect("application_signup",slug=slug)
job = get_object_or_404(JobPosting, form_template__slug=slug) print(form_template.job.slug)
job = get_object_or_404(JobPosting, slug=form_template.job.slug)
if request.user.user_type == "candidate": if request.user.user_type == "candidate":
person=request.user.person_profile person=request.user.person_profile
if job.has_already_applied_to_this_job(person): if job.has_already_applied_to_this_job(person):
@ -3949,11 +3952,11 @@ def api_application_detail(request, candidate_id):
@login_required @login_required
@staff_user_required @staff_user_required
def compose_application_email(request, job_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 .email_service import send_bulk_email
job = get_object_or_404(JobPosting, slug=job_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)
@ -3997,7 +4000,6 @@ def compose_application_email(request, job_slug):
request=request, request=request,
attachments=None, attachments=None,
async_task_=True, # Changed to False to avoid pickle issues async_task_=True, # Changed to False to avoid pickle issues
from_interview=False,
job=job job=job
) )
@ -4386,16 +4388,19 @@ def interview_detail(request, slug):
schedule = get_object_or_404(ScheduledInterview, slug=slug) schedule = get_object_or_404(ScheduledInterview, slug=slug)
interview = schedule.interview interview = schedule.interview
application=schedule.application
job=schedule.job
reschedule_form = ScheduledInterviewForm() reschedule_form = ScheduledInterviewForm()
reschedule_form.initial['topic'] = interview.topic reschedule_form.initial['topic'] = interview.topic
meeting=interview meeting=interview
interview_email_form=InterviewEmailForm(job,application,schedule)
context = { context = {
'schedule': schedule, 'schedule': schedule,
'interview': interview, 'interview': interview,
'reschedule_form':reschedule_form, 'reschedule_form':reschedule_form,
'interview_status_form':ScheduledInterviewUpdateStatusForm(), 'interview_status_form':ScheduledInterviewUpdateStatusForm(),
'cancel_form':InterviewCancelForm(instance=meeting), 'cancel_form':InterviewCancelForm(instance=meeting),
'interview_email_form':interview_email_form
} }
return render(request, 'interviews/interview_detail.html', context) return render(request, 'interviews/interview_detail.html', context)
@ -5741,17 +5746,40 @@ def sync_history(request, job_slug=None):
def send_interview_email(request,slug): def send_interview_email(request,slug):
schedule=get_object_or_404(ScheduledInterview,slug=slug) schedule=get_object_or_404(ScheduledInterview,slug=slug)
application=schedule.application.first() application=schedule.application
job=application.job job=application.job
form=InterviewEmailForm(job,application,schedule) form=InterviewEmailForm(job,application,schedule)
if request.method=='POST': if request.method=='POST':
form=InterviewEmailForm(job, application, schedule, request.POST)
if form.is_valid():
recipient=form.cleaned_data.get('to').strip() recipient=form.cleaned_data.get('to').strip()
body_message=form.cleaned_data.get('message') body_message=form.cleaned_data.get('message')
subject=form.cleaned_data.get('subject')
sender=request.user sender=request.user
job=job job=job
pass try:
email_result = async_task('recruitment.tasks._task_send_individual_email',
subject=subject,
body_message=body_message,
recipient=recipient,
attachments=None,
sender=sender,
job=job
)
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)
# async_task('recruitment.tasks._task_send_individual_email', 'value1', 'value2')
# def _task_send_individual_email(subject, body_message, recipient, attachments,sender,job):

View File

@ -595,38 +595,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 'send_interview_email' schedule.slug %}">
{% csrf_token %} {% csrf_token %}
<div class="mb-3"> {{interview_email_form|crispy}}
<label for="email_to" class="form-label">{% trans "To" %}</label>
<input type="email" class="form-control" id="email_to" value="{{ schedule.application.email }}" readonly>
</div>
<div class="mb-3">
<label for="email_subject" class="form-label">{% trans "Subject" %}</label>
<input type="text" class="form-control" id="email_subject" name="subject"
value="{% trans 'Interview Details' %} - {{ schedule.job.title }}">
</div>
<div class="mb-3">
<label for="email_message" class="form-label">{% trans "Message" %}</label>
<textarea class="form-control" id="email_message" name="message" rows="6">
{% trans "Dear" %} {{ schedule.application.name }},
{% trans "Your interview details are as follows:" %}
{% trans "Date:" %} {{ schedule.interview_date|date:"d-m-Y" }}
{% trans "Time:" %} {{ schedule.interview_time|date:"h:i A" }}
{% trans "Job:" %} {{ schedule.job.title }}
{% if interview.location_type == 'Remote' %}
{% trans "This is a remote schedule. You will receive the meeting link separately." %}
{% else %}
{% trans "This is an onsite schedule. Please arrive 10 minutes early." %}
{% endif %}
{% trans "Best regards," %}
{% trans "HR Team" %}
</textarea>
</div>
<button type="submit" class="btn btn-main-action btn-sm"> <button type="submit" class="btn btn-main-action btn-sm">
<i class="fas fa-paper-plane me-1"></i> {% trans "Send Email" %} <i class="fas fa-paper-plane me-1"></i> {% trans "Send Email" %}
</button> </button>