messages creation for email
This commit is contained in:
parent
ee88e8bad8
commit
acee95995c
2
.gitignore
vendored
2
.gitignore
vendored
@ -53,7 +53,7 @@ htmlcov/
|
||||
|
||||
# Media and Static files (if served locally and not meant for version control)
|
||||
media/
|
||||
|
||||
static/
|
||||
|
||||
# Deployment files
|
||||
*.tar.gz
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@ -1,15 +1,24 @@
|
||||
"""
|
||||
Email service for sending notifications related to agency messaging.
|
||||
"""
|
||||
from .models import Application
|
||||
from django.shortcuts import get_object_or_404
|
||||
import logging
|
||||
from django.conf import settings
|
||||
from django.core.mail import EmailMultiAlternatives
|
||||
from django.utils.html import strip_tags
|
||||
from django_q.tasks import async_task # Import needed at the top for clarity
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
from django.core.mail import send_mail, EmailMultiAlternatives
|
||||
from django.conf import settings
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.html import strip_tags
|
||||
from django.contrib.auth import get_user_model
|
||||
import logging
|
||||
|
||||
from .models import Message
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
User=get_user_model()
|
||||
|
||||
class EmailService:
|
||||
"""
|
||||
@ -225,17 +234,10 @@ def send_interview_invitation_email(candidate, job, meeting_details=None, recipi
|
||||
return {'success': False, 'error': error_msg}
|
||||
|
||||
|
||||
from .models import Application
|
||||
from django.shortcuts import get_object_or_404
|
||||
import logging
|
||||
from django.conf import settings
|
||||
from django.core.mail import EmailMultiAlternatives
|
||||
from django.utils.html import strip_tags
|
||||
from django_q.tasks import async_task # Import needed at the top for clarity
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def send_bulk_email(subject, message, recipient_list, request=None, attachments=None, async_task_=False, from_interview=False):
|
||||
|
||||
def send_bulk_email(subject, message, recipient_list, request=None, attachments=None, async_task_=False, from_interview=False,job=None):
|
||||
"""
|
||||
Send bulk email to multiple recipients with HTML support and attachments,
|
||||
supporting synchronous or asynchronous dispatch.
|
||||
@ -301,7 +303,8 @@ def send_bulk_email(subject, message, recipient_list, request=None, attachments=
|
||||
|
||||
processed_attachments = attachments if attachments else []
|
||||
task_ids = []
|
||||
|
||||
job_id=job.id
|
||||
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
|
||||
for recipient_email, custom_message in customized_sends:
|
||||
@ -311,7 +314,10 @@ def send_bulk_email(subject, message, recipient_list, request=None, attachments=
|
||||
custom_message, # Pass the custom message
|
||||
[recipient_email], # Pass the specific recipient as a list of one
|
||||
processed_attachments,
|
||||
hook='recruitment.tasks.email_success_hook'
|
||||
sender_user_id,
|
||||
job_id,
|
||||
hook='recruitment.tasks.email_success_hook',
|
||||
|
||||
)
|
||||
task_ids.append(task_id)
|
||||
|
||||
@ -350,80 +356,101 @@ def send_bulk_email(subject, message, recipient_list, request=None, attachments=
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to queue async tasks: {str(e)}", exc_info=True)
|
||||
return {'success': False, 'error': f"Failed to queue async tasks: {str(e)}"}
|
||||
|
||||
|
||||
else:
|
||||
# --- 3. Handle SYNCHRONOUS Send (No changes needed here, as it was fixed previously) ---
|
||||
try:
|
||||
# NOTE: The synchronous block below should also use the 'customized_sends'
|
||||
# list for consistency instead of rebuilding messages from 'pure_candidate_emails'
|
||||
# and 'agency_emails', but keeping your current logic structure to minimize changes.
|
||||
|
||||
from_email = getattr(settings, 'DEFAULT_FROM_EMAIL', 'noreply@kaauh.edu.sa')
|
||||
is_html = '<' in message and '>' in message
|
||||
successful_sends = 0
|
||||
|
||||
# Helper Function for Sync Send (as provided)
|
||||
def send_individual_email(recipient, body_message):
|
||||
# ... (Existing helper function logic) ...
|
||||
nonlocal successful_sends
|
||||
|
||||
if is_html:
|
||||
plain_message = strip_tags(body_message)
|
||||
email_obj = EmailMultiAlternatives(subject=subject, body=plain_message, from_email=from_email, to=[recipient])
|
||||
email_obj.attach_alternative(body_message, "text/html")
|
||||
else:
|
||||
email_obj = EmailMultiAlternatives(subject=subject, body=body_message, from_email=from_email, to=[recipient])
|
||||
|
||||
if attachments:
|
||||
for attachment in attachments:
|
||||
if hasattr(attachment, 'read'):
|
||||
filename = getattr(attachment, 'name', 'attachment')
|
||||
content = attachment.read()
|
||||
content_type = getattr(attachment, 'content_type', 'application/octet-stream')
|
||||
email_obj.attach(filename, content, content_type)
|
||||
elif isinstance(attachment, tuple) and len(attachment) == 3:
|
||||
filename, content, content_type = attachment
|
||||
email_obj.attach(filename, content, content_type)
|
||||
|
||||
try:
|
||||
email_obj.send(fail_silently=False)
|
||||
successful_sends += 1
|
||||
# NOTE: The synchronous block below should also use the 'customized_sends'
|
||||
# list for consistency instead of rebuilding messages from 'pure_candidate_emails'
|
||||
# and 'agency_emails', but keeping your current logic structure to minimize changes.
|
||||
|
||||
from_email = getattr(settings, 'DEFAULT_FROM_EMAIL', 'noreply@kaauh.edu.sa')
|
||||
is_html = '<' in message and '>' in message
|
||||
successful_sends = 0
|
||||
|
||||
# Helper Function for Sync Send (as provided)
|
||||
def send_individual_email(recipient, body_message):
|
||||
# ... (Existing helper function logic) ...
|
||||
nonlocal successful_sends
|
||||
|
||||
if is_html:
|
||||
plain_message = strip_tags(body_message)
|
||||
email_obj = EmailMultiAlternatives(subject=subject, body=plain_message, from_email=from_email, to=[recipient])
|
||||
email_obj.attach_alternative(body_message, "text/html")
|
||||
else:
|
||||
email_obj = EmailMultiAlternatives(subject=subject, body=body_message, from_email=from_email, to=[recipient])
|
||||
|
||||
if attachments:
|
||||
for attachment in attachments:
|
||||
if hasattr(attachment, 'read'):
|
||||
filename = getattr(attachment, 'name', 'attachment')
|
||||
content = attachment.read()
|
||||
content_type = getattr(attachment, 'content_type', 'application/octet-stream')
|
||||
email_obj.attach(filename, content, content_type)
|
||||
elif isinstance(attachment, tuple) and len(attachment) == 3:
|
||||
filename, content, content_type = attachment
|
||||
email_obj.attach(filename, content, content_type)
|
||||
|
||||
try:
|
||||
result=email_obj.send(fail_silently=False)
|
||||
if result==1:
|
||||
try:
|
||||
user=get_object_or_404(User,email=recipient)
|
||||
new_message = Message.objects.create(
|
||||
sender=request.user,
|
||||
recipient=user,
|
||||
job=job,
|
||||
subject=subject,
|
||||
content=message, # Store the full HTML or plain content
|
||||
message_type='DIRECT',
|
||||
is_read=False, # It's just sent, not read yet
|
||||
)
|
||||
logger.info(f"Stored sent message ID {new_message.id} in DB.")
|
||||
except Exception as e:
|
||||
logger.error(f"Email sent to {recipient}, but failed to store in DB: {str(e)}")
|
||||
|
||||
|
||||
else:
|
||||
logger.error("fialed to send email")
|
||||
|
||||
successful_sends += 1
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to send email to {recipient}: {str(e)}", exc_info=True)
|
||||
|
||||
if not from_interview:
|
||||
# Send Emails - Pure Candidates
|
||||
for email in pure_candidate_emails:
|
||||
candidate_name = Application.objects.filter(person__email=email).first().person.full_name
|
||||
candidate_message = f"Hi, {candidate_name}" + "\n" + message
|
||||
send_individual_email(email, candidate_message)
|
||||
|
||||
# Send Emails - Agencies
|
||||
i = 0
|
||||
for email in agency_emails:
|
||||
candidate_email = candidate_through_agency_emails[i]
|
||||
candidate_name = Application.objects.filter(person__email=candidate_email).first().person.full_name
|
||||
agency_message = f"Hi, {candidate_name}" + "\n" + message
|
||||
send_individual_email(email, agency_message)
|
||||
i += 1
|
||||
|
||||
logger.info(f"Bulk email processing complete. Sent successfully to {successful_sends} out of {total_recipients} unique recipients.")
|
||||
return {
|
||||
'success': True,
|
||||
'recipients_count': successful_sends,
|
||||
'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:
|
||||
logger.error(f"Failed to send email to {recipient}: {str(e)}", exc_info=True)
|
||||
|
||||
if not from_interview:
|
||||
# Send Emails - Pure Candidates
|
||||
for email in pure_candidate_emails:
|
||||
candidate_name = Application.objects.filter(email=email).first().first_name
|
||||
candidate_message = f"Hi, {candidate_name}" + "\n" + message
|
||||
send_individual_email(email, candidate_message)
|
||||
|
||||
# Send Emails - Agencies
|
||||
i = 0
|
||||
for email in agency_emails:
|
||||
candidate_email = candidate_through_agency_emails[i]
|
||||
candidate_name = Application.objects.filter(email=candidate_email).first().first_name
|
||||
agency_message = f"Hi, {candidate_name}" + "\n" + message
|
||||
send_individual_email(email, agency_message)
|
||||
i += 1
|
||||
|
||||
logger.info(f"Bulk email processing complete. Sent successfully to {successful_sends} out of {total_recipients} unique recipients.")
|
||||
return {
|
||||
'success': True,
|
||||
'recipients_count': successful_sends,
|
||||
'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:
|
||||
error_msg = f"Failed to process bulk email send request: {str(e)}"
|
||||
logger.error(error_msg, exc_info=True)
|
||||
return {'success': False, 'error': error_msg}
|
||||
error_msg = f"Failed to process bulk email send request: {str(e)}"
|
||||
logger.error(error_msg, exc_info=True)
|
||||
return {'success': False, 'error': error_msg}
|
||||
@ -12,8 +12,9 @@ from . linkedin_service import LinkedInService
|
||||
from django.shortcuts import get_object_or_404
|
||||
from . models import JobPosting
|
||||
from django.utils import timezone
|
||||
from . models import InterviewSchedule,ScheduledInterview,ZoomMeetingDetails
|
||||
|
||||
from . models import InterviewSchedule,ScheduledInterview,ZoomMeetingDetails,Message
|
||||
from django.contrib.auth import get_user_model
|
||||
User = get_user_model()
|
||||
# Add python-docx import for Word document processing
|
||||
try:
|
||||
from docx import Document
|
||||
@ -28,7 +29,7 @@ logger = logging.getLogger(__name__)
|
||||
OPENROUTER_API_KEY ='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'
|
||||
# OPENROUTER_MODEL = 'qwen/qwen-2.5-72b-instruct:free'
|
||||
|
||||
OPENROUTER_MODEL = 'qwen/qwen-2.5-72b-instruct'
|
||||
OPENROUTER_MODEL = 'openai/gpt-oss-20b'
|
||||
# OPENROUTER_MODEL = 'openai/gpt-oss-20b'
|
||||
# OPENROUTER_MODEL = 'mistralai/mistral-small-3.2-24b-instruct:free'
|
||||
|
||||
@ -506,7 +507,6 @@ def handle_zoom_webhook_event(payload):
|
||||
Background task to process a Zoom webhook event and update the local ZoomMeeting status.
|
||||
It handles: created, updated, started, ended, and deleted events.
|
||||
"""
|
||||
print(payload)
|
||||
event_type = payload.get('event')
|
||||
object_data = payload['payload']['object']
|
||||
|
||||
@ -535,9 +535,7 @@ def handle_zoom_webhook_event(payload):
|
||||
# elif event_type == 'meeting.updated':
|
||||
# Only update time fields if they are in the payload
|
||||
print(object_data)
|
||||
meeting_start_time = object_data.get('start_time', meeting_instance.start_time)
|
||||
if meeting_start_time:
|
||||
meeting_instance.start_time = datetime.fromisoformat(meeting_start_time)
|
||||
meeting_instance.start_time = object_data.get('start_time', meeting_instance.start_time)
|
||||
meeting_instance.duration = object_data.get('duration', meeting_instance.duration)
|
||||
meeting_instance.timezone = object_data.get('timezone', meeting_instance.timezone)
|
||||
|
||||
@ -758,7 +756,7 @@ from django.conf import settings
|
||||
from django.core.mail import EmailMultiAlternatives
|
||||
from django.utils.html import strip_tags
|
||||
|
||||
def _task_send_individual_email(subject, body_message, recipient, attachments):
|
||||
def _task_send_individual_email(subject, body_message, recipient, attachments,sender,job):
|
||||
"""Internal helper to create and send a single email."""
|
||||
|
||||
from_email = getattr(settings, 'DEFAULT_FROM_EMAIL', 'noreply@kaauh.edu.sa')
|
||||
@ -778,16 +776,36 @@ def _task_send_individual_email(subject, body_message, recipient, attachments):
|
||||
email_obj.attach(filename, content, content_type)
|
||||
|
||||
try:
|
||||
email_obj.send(fail_silently=False)
|
||||
return True
|
||||
result=email_obj.send(fail_silently=False)
|
||||
|
||||
if result==1:
|
||||
try:
|
||||
user=get_object_or_404(User,email=recipient)
|
||||
new_message = Message.objects.create(
|
||||
sender=sender,
|
||||
recipient=user,
|
||||
job=job,
|
||||
subject=subject,
|
||||
content=body_message, # Store the full HTML or plain content
|
||||
message_type='DIRECT',
|
||||
is_read=False, # It's just sent, not read yet
|
||||
)
|
||||
logger.info(f"Stored sent message ID {new_message.id} in DB.")
|
||||
except Exception as e:
|
||||
logger.error(f"Email sent to {recipient}, but failed to store in DB: {str(e)}")
|
||||
|
||||
|
||||
else:
|
||||
logger.error("fialed to send email")
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Task failed to send email to {recipient}: {str(e)}", exc_info=True)
|
||||
return False
|
||||
logger.error(f"Failed to send email to {recipient}: {str(e)}", exc_info=True)
|
||||
|
||||
|
||||
def send_bulk_email_task(subject, message, recipient_list, attachments=None, hook='recruitment.tasks.email_success_hook'):
|
||||
def send_bulk_email_task(subject, message, recipient_list,attachments=None,sender_user_id=None,job_id=None, hook='recruitment.tasks.email_success_hook'):
|
||||
"""
|
||||
Django-Q background task to send pre-formatted email to a list of recipients.
|
||||
Django-Q background task to send pre-formatted email to a list of recipients.,
|
||||
Receives arguments directly from the async_task call.
|
||||
"""
|
||||
logger.info(f"Starting bulk email task for {len(recipient_list)} recipients")
|
||||
@ -796,11 +814,13 @@ def send_bulk_email_task(subject, message, recipient_list, attachments=None, hoo
|
||||
|
||||
if not recipient_list:
|
||||
return {'success': False, 'error': 'No recipients provided to task.'}
|
||||
|
||||
|
||||
sender=get_object_or_404(User,pk=sender_user_id)
|
||||
job=get_object_or_404(JobPosting,pk=job_id)
|
||||
# Since the async caller sends one task per recipient, total_recipients should be 1.
|
||||
for recipient in recipient_list:
|
||||
# The 'message' is the custom message specific to this recipient.
|
||||
if _task_send_individual_email(subject, message, recipient, attachments):
|
||||
if _task_send_individual_email(subject, message, recipient, attachments,sender,job):
|
||||
successful_sends += 1
|
||||
|
||||
if successful_sends > 0:
|
||||
|
||||
@ -5206,7 +5206,7 @@ def compose_candidate_email(request, job_slug):
|
||||
|
||||
|
||||
if request.method == 'POST':
|
||||
print("........................................................inside candidate conpose.............")
|
||||
|
||||
candidate_ids = request.POST.getlist('candidate_ids')
|
||||
candidates=Application.objects.filter(id__in=candidate_ids)
|
||||
form = CandidateEmailForm(job, candidates, request.POST)
|
||||
@ -5233,14 +5233,16 @@ def compose_candidate_email(request, job_slug):
|
||||
|
||||
# Send emails using email service (no attachments, synchronous to avoid pickle issues)
|
||||
|
||||
email_result = send_bulk_email(
|
||||
email_result = send_bulk_email( #
|
||||
subject=subject,
|
||||
message=message,
|
||||
recipient_list=email_addresses,
|
||||
request=request,
|
||||
attachments=None,
|
||||
async_task_=True, # Changed to False to avoid pickle issues
|
||||
from_interview=False
|
||||
from_interview=False,
|
||||
job=job
|
||||
|
||||
)
|
||||
|
||||
if email_result["success"]:
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 8.4 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 98 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 282 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 74 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 82 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 10 KiB |
@ -155,7 +155,7 @@
|
||||
<i class="fas fa-user-friends me-2"></i> {% trans "Applicants List" %}
|
||||
</h1>
|
||||
<a href="{% url 'person_create' %}" class="btn btn-main-action">
|
||||
<i class="fas fa-plus me-1"></i> {% trans "Add New" %}
|
||||
<i class="fas fa-plus me-1"></i> {% trans "Add New Applicant" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@ -163,18 +163,14 @@
|
||||
<div class="card mb-4 shadow-sm no-hover">
|
||||
<div class="card-body">
|
||||
<div class="row g-4">
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="search" class="form-label small text-muted">{% trans "Search by Name or Email" %}</label>
|
||||
<form method="get" action="" class="w-100">
|
||||
<div class="input-group input-group-lg">
|
||||
<input type="text" name="q" class="form-control" id="search"
|
||||
placeholder="{% trans 'Search applicant...' %}"
|
||||
value="{{ request.GET.q }}">
|
||||
<button class="btn btn-main-action" type="submit">
|
||||
<i class="fas fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="input-group input-group-lg">
|
||||
<form method="get" action="" class="w-100">
|
||||
{% include 'includes/search_form.html' %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
@ -200,10 +196,10 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 d-flex justify-content-end align-self-end">
|
||||
<div class="col-md-4 d-flex">
|
||||
<div class="filter-buttons">
|
||||
<button type="submit" class="btn btn-main-action btn-sm">
|
||||
<i class="fas fa-filter me-1"></i> {% trans "Apply" %}
|
||||
<i class="fas fa-filter me-1"></i> {% trans "Apply Filter" %}
|
||||
</button>
|
||||
{% if request.GET.q or request.GET.nationality or request.GET.gender %}
|
||||
<a href="{% url 'person_list' %}" class="btn btn-outline-secondary btn-sm">
|
||||
@ -217,7 +213,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% if people_list %}
|
||||
<div id="person-list">
|
||||
<!-- View Switcher -->
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user