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 and Static files (if served locally and not meant for version control)
|
||||||
media/
|
media/
|
||||||
|
static/
|
||||||
|
|
||||||
# Deployment files
|
# Deployment files
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@ -1,15 +1,24 @@
|
|||||||
"""
|
"""
|
||||||
Email service for sending notifications related to agency messaging.
|
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.core.mail import send_mail, EmailMultiAlternatives
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.utils.html import strip_tags
|
from django.utils.html import strip_tags
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
import logging
|
import logging
|
||||||
|
from .models import Message
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
User=get_user_model()
|
||||||
|
|
||||||
class EmailService:
|
class EmailService:
|
||||||
"""
|
"""
|
||||||
@ -225,17 +234,10 @@ def send_interview_invitation_email(candidate, job, meeting_details=None, recipi
|
|||||||
return {'success': False, 'error': error_msg}
|
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,
|
Send bulk email to multiple recipients with HTML support and attachments,
|
||||||
supporting synchronous or asynchronous dispatch.
|
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 []
|
processed_attachments = attachments if attachments else []
|
||||||
task_ids = []
|
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:
|
if not from_interview:
|
||||||
# Loop through ALL final customized sends
|
# Loop through ALL final customized sends
|
||||||
for recipient_email, custom_message in 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
|
custom_message, # Pass the custom message
|
||||||
[recipient_email], # Pass the specific recipient as a list of one
|
[recipient_email], # Pass the specific recipient as a list of one
|
||||||
processed_attachments,
|
processed_attachments,
|
||||||
hook='recruitment.tasks.email_success_hook'
|
sender_user_id,
|
||||||
|
job_id,
|
||||||
|
hook='recruitment.tasks.email_success_hook',
|
||||||
|
|
||||||
)
|
)
|
||||||
task_ids.append(task_id)
|
task_ids.append(task_id)
|
||||||
|
|
||||||
@ -350,80 +356,101 @@ def send_bulk_email(subject, message, recipient_list, request=None, attachments=
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to queue async tasks: {str(e)}", exc_info=True)
|
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)}"}
|
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) ---
|
# --- 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:
|
try:
|
||||||
email_obj.send(fail_silently=False)
|
# NOTE: The synchronous block below should also use the 'customized_sends'
|
||||||
successful_sends += 1
|
# 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:
|
except Exception as e:
|
||||||
logger.error(f"Failed to send email to {recipient}: {str(e)}", exc_info=True)
|
error_msg = f"Failed to process bulk email send request: {str(e)}"
|
||||||
|
logger.error(error_msg, exc_info=True)
|
||||||
if not from_interview:
|
return {'success': False, 'error': error_msg}
|
||||||
# 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}
|
|
||||||
@ -12,8 +12,9 @@ from . linkedin_service import LinkedInService
|
|||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from . models import JobPosting
|
from . models import JobPosting
|
||||||
from django.utils import timezone
|
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
|
# Add python-docx import for Word document processing
|
||||||
try:
|
try:
|
||||||
from docx import Document
|
from docx import Document
|
||||||
@ -28,7 +29,7 @@ logger = logging.getLogger(__name__)
|
|||||||
OPENROUTER_API_KEY ='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'
|
OPENROUTER_API_KEY ='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'
|
||||||
# OPENROUTER_MODEL = 'qwen/qwen-2.5-72b-instruct:free'
|
# 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 = 'openai/gpt-oss-20b'
|
||||||
# OPENROUTER_MODEL = 'mistralai/mistral-small-3.2-24b-instruct:free'
|
# 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.
|
Background task to process a Zoom webhook event and update the local ZoomMeeting status.
|
||||||
It handles: created, updated, started, ended, and deleted events.
|
It handles: created, updated, started, ended, and deleted events.
|
||||||
"""
|
"""
|
||||||
print(payload)
|
|
||||||
event_type = payload.get('event')
|
event_type = payload.get('event')
|
||||||
object_data = payload['payload']['object']
|
object_data = payload['payload']['object']
|
||||||
|
|
||||||
@ -535,9 +535,7 @@ def handle_zoom_webhook_event(payload):
|
|||||||
# elif event_type == 'meeting.updated':
|
# elif event_type == 'meeting.updated':
|
||||||
# Only update time fields if they are in the payload
|
# Only update time fields if they are in the payload
|
||||||
print(object_data)
|
print(object_data)
|
||||||
meeting_start_time = object_data.get('start_time', meeting_instance.start_time)
|
meeting_instance.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.duration = object_data.get('duration', meeting_instance.duration)
|
meeting_instance.duration = object_data.get('duration', meeting_instance.duration)
|
||||||
meeting_instance.timezone = object_data.get('timezone', meeting_instance.timezone)
|
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.core.mail import EmailMultiAlternatives
|
||||||
from django.utils.html import strip_tags
|
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."""
|
"""Internal helper to create and send a single email."""
|
||||||
|
|
||||||
from_email = getattr(settings, 'DEFAULT_FROM_EMAIL', 'noreply@kaauh.edu.sa')
|
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)
|
email_obj.attach(filename, content, content_type)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
email_obj.send(fail_silently=False)
|
result=email_obj.send(fail_silently=False)
|
||||||
return True
|
|
||||||
|
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:
|
except Exception as e:
|
||||||
logger.error(f"Task 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)
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
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.
|
Receives arguments directly from the async_task call.
|
||||||
"""
|
"""
|
||||||
logger.info(f"Starting bulk email task for {len(recipient_list)} recipients")
|
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:
|
if not recipient_list:
|
||||||
return {'success': False, 'error': 'No recipients provided to task.'}
|
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.
|
# Since the async caller sends one task per recipient, total_recipients should be 1.
|
||||||
for recipient in recipient_list:
|
for recipient in recipient_list:
|
||||||
# The 'message' is the custom message specific to this recipient.
|
# 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
|
successful_sends += 1
|
||||||
|
|
||||||
if successful_sends > 0:
|
if successful_sends > 0:
|
||||||
|
|||||||
@ -5206,7 +5206,7 @@ def compose_candidate_email(request, job_slug):
|
|||||||
|
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
print("........................................................inside candidate conpose.............")
|
|
||||||
candidate_ids = request.POST.getlist('candidate_ids')
|
candidate_ids = request.POST.getlist('candidate_ids')
|
||||||
candidates=Application.objects.filter(id__in=candidate_ids)
|
candidates=Application.objects.filter(id__in=candidate_ids)
|
||||||
form = CandidateEmailForm(job, candidates, request.POST)
|
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)
|
# Send emails using email service (no attachments, synchronous to avoid pickle issues)
|
||||||
|
|
||||||
email_result = send_bulk_email(
|
email_result = send_bulk_email( #
|
||||||
subject=subject,
|
subject=subject,
|
||||||
message=message,
|
message=message,
|
||||||
recipient_list=email_addresses,
|
recipient_list=email_addresses,
|
||||||
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
|
from_interview=False,
|
||||||
|
job=job
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if email_result["success"]:
|
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" %}
|
<i class="fas fa-user-friends me-2"></i> {% trans "Applicants List" %}
|
||||||
</h1>
|
</h1>
|
||||||
<a href="{% url 'person_create' %}" class="btn btn-main-action">
|
<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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -163,18 +163,14 @@
|
|||||||
<div class="card mb-4 shadow-sm no-hover">
|
<div class="card mb-4 shadow-sm no-hover">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row g-4">
|
<div class="row g-4">
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label for="search" class="form-label small text-muted">{% trans "Search by Name or Email" %}</label>
|
<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">
|
||||||
<div class="input-group input-group-lg">
|
<form method="get" action="" class="w-100">
|
||||||
<input type="text" name="q" class="form-control" id="search"
|
{% include 'includes/search_form.html' %}
|
||||||
placeholder="{% trans 'Search applicant...' %}"
|
</form>
|
||||||
value="{{ request.GET.q }}">
|
</div>
|
||||||
<button class="btn btn-main-action" type="submit">
|
|
||||||
<i class="fas fa-search"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
@ -200,10 +196,10 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</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">
|
<div class="filter-buttons">
|
||||||
<button type="submit" class="btn btn-main-action btn-sm">
|
<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>
|
</button>
|
||||||
{% if request.GET.q or request.GET.nationality or request.GET.gender %}
|
{% if request.GET.q or request.GET.nationality or request.GET.gender %}
|
||||||
<a href="{% url 'person_list' %}" class="btn btn-outline-secondary btn-sm">
|
<a href="{% url 'person_list' %}" class="btn btn-outline-secondary btn-sm">
|
||||||
@ -217,7 +213,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{% if people_list %}
|
{% if people_list %}
|
||||||
<div id="person-list">
|
<div id="person-list">
|
||||||
<!-- View Switcher -->
|
<!-- View Switcher -->
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user