issue regarding the agency create

This commit is contained in:
Faheed 2025-12-10 14:23:27 +03:00
parent 96c259cbc5
commit 40eeef76fd
8 changed files with 140 additions and 115 deletions

View File

@ -242,7 +242,7 @@ def send_bulk_email(subject, message, recipient_list, request=None, attachments=
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: if not from_interview:
@ -308,19 +308,19 @@ def send_bulk_email(subject, message, recipient_list, request=None, attachments=
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: if not from_interview:
# Loop through ALL final customized sends # Loop through ALL final customized sends
for recipient_email, custom_message in customized_sends:
task_id = async_task(
'recruitment.tasks.send_bulk_email_task', task_id = async_task(
subject, 'recruitment.tasks.send_bulk_email_task',
custom_message, # Pass the custom message subject,
[recipient_email], # Pass the specific recipient as a list of one customized_sends,
processed_attachments, processed_attachments,
sender_user_id, sender_user_id,
job_id, job_id,
hook='recruitment.tasks.email_success_hook', hook='recruitment.tasks.email_success_hook',
) )
task_ids.append(task_id) task_ids.append(task_id)
logger.info(f"{len(task_ids)} tasks ({total_recipients} emails) queued.") logger.info(f"{len(task_ids)} tasks ({total_recipients} emails) queued.")

View File

@ -81,7 +81,9 @@ class SourceForm(forms.ModelForm):
} }
), ),
"ip_address": forms.TextInput( "ip_address": forms.TextInput(
attrs={"class": "form-control", "placeholder": "192.168.1.100"} attrs={"class": "form-control", "placeholder": "192.168.1.100",
"required":True},
), ),
"trusted_ips":forms.TextInput( "trusted_ips":forms.TextInput(
attrs={"class": "form-control", "placeholder": "192.168.1.100","required": False} attrs={"class": "form-control", "placeholder": "192.168.1.100","required": False}
@ -118,6 +120,13 @@ class SourceForm(forms.ModelForm):
if Source.objects.filter(name=name).exclude(pk=instance.pk).exists(): if Source.objects.filter(name=name).exclude(pk=instance.pk).exists():
raise ValidationError("A source with this name already exists.") raise ValidationError("A source with this name already exists.")
return name return name
def clean_ip_address(self):
ip_address=self.cleaned_data.get('ip_address')
if not ip_address:
raise ValidationError(_("Ip address should not be empty"))
return ip_address
class SourceAdvancedForm(forms.ModelForm): class SourceAdvancedForm(forms.ModelForm):
@ -1063,6 +1072,7 @@ class HiringAgencyForm(forms.ModelForm):
self.helper.form_class = "form-horizontal" self.helper.form_class = "form-horizontal"
self.helper.label_class = "col-md-3" self.helper.label_class = "col-md-3"
self.helper.field_class = "col-md-9" self.helper.field_class = "col-md-9"
self.fields['email'].required=True
self.helper.layout = Layout( self.helper.layout = Layout(
Field("name", css_class="form-control"), Field("name", css_class="form-control"),
@ -1113,7 +1123,7 @@ class HiringAgencyForm(forms.ModelForm):
email = email.lower().strip() email = email.lower().strip()
if not instance.pk: # Creating new instance if not instance.pk: # Creating new instance
print("created ....") print("created ....")
if HiringAgency.objects.filter(email=email).exists(): if HiringAgency.objects.filter(email=email).exists() or User.objects.filter(email=email):
raise ValidationError("An agency with this email already exists.") raise ValidationError("An agency with this email already exists.")
else: # Editing existing instance else: # Editing existing instance
if ( if (

View File

@ -1022,31 +1022,33 @@ def _task_send_individual_email(subject, body_message, recipient, attachments,se
logger.info(f"Stored sent message ID {new_message.id} in DB.") logger.info(f"Stored sent message ID {new_message.id} in DB.")
except Exception as e: except Exception as e:
logger.error(f"Email sent to {recipient}, but failed to store in DB: {str(e)}") logger.error(f"Email sent to {recipient}, but failed to store in DB: {str(e)}")
return result == 1
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)
def send_bulk_email_task(subject, message, recipient_list,attachments=None,sender_user_id=None,job_id=None, hook='recruitment.tasks.email_success_hook'): def send_bulk_email_task(subject, customized_sends,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") print("jhjmfhsdjhfksjhdkfjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjh")
logger.info(f"Starting bulk email task for {len(customized_sends)} recipients")
successful_sends = 0 successful_sends = 0
total_recipients = len(recipient_list) total_recipients = len(customized_sends)
if not recipient_list: if not customized_sends:
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) sender=get_object_or_404(User,pk=sender_user_id)
job=get_object_or_404(JobPosting,pk=job_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_email, custom_message in customized_sends:
# The 'message' is the custom message specific to this recipient. # The 'message' is the custom message specific to this recipient.
r=_task_send_individual_email(subject, message, recipient, attachments,sender,job) r=_task_send_individual_email(subject, custom_message, recipient_email, attachments,sender,job)
print(f"Email send result for {recipient}: {r}") print(f"Email send result for {recipient_email}: {r}")
if r: if r:
successful_sends += 1 successful_sends += 1
print(f"successful_sends: {successful_sends} out of {total_recipients}") print(f"successful_sends: {successful_sends} out of {total_recipients}")

View File

@ -6051,18 +6051,24 @@ def interview_list(request):
# Get filter parameters # Get filter parameters
status_filter = request.GET.get('status', '') status_filter = request.GET.get('status', '')
interview_type=request.GET.get('type')
job_filter = request.GET.get('job', '') job_filter = request.GET.get('job', '')
search_query = request.GET.get('q', '') print(job_filter)
search_query = request.GET.get('search', '')
jobs=JobPosting.objects.filter(status='ACTIVE')
# Apply filters # Apply filters
if interview_type:
interviews=interviews.filter(interview__location_type=interview_type)
if status_filter: if status_filter:
interviews = interviews.filter(status=status_filter) interviews = interviews.filter(status=status_filter)
if job_filter: if job_filter:
interviews = interviews.filter(job__title__icontains=job_filter) interviews = interviews.filter(job__slug=job_filter)
if search_query: if search_query:
interviews = interviews.filter( interviews = interviews.filter(
Q(application__person__first_name__icontains=search_query) | Q(application__person__first_name__icontains=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(job__title__icontains=search_query) Q(job__title__icontains=search_query)
) )
@ -6077,6 +6083,7 @@ def interview_list(request):
'job_filter': job_filter, 'job_filter': job_filter,
'search_query': search_query, 'search_query': search_query,
'interviews': interviews, 'interviews': interviews,
'jobs':jobs
} }
return render(request, 'interviews/interview_list.html', context) return render(request, 'interviews/interview_list.html', context)
@ -6090,7 +6097,10 @@ def interview_detail(request, slug):
reschedule_form = ScheduledInterviewForm() reschedule_form = ScheduledInterviewForm()
reschedule_form.initial['topic'] = interview.interview.topic try:
reschedule_form.initial['topic'] = interview.interview.topic
except Exception as e:
print(e)
meeting=interview.interview meeting=interview.interview
context = { context = {
'interview': interview, 'interview': interview,

View File

@ -246,13 +246,13 @@
<i class="fas fa-download me-1"></i> {% trans "Download Resume" %} <i class="fas fa-download me-1"></i> {% trans "Download Resume" %}
</a> </a>
{% endif %} {% endif %}
<button type="button" class="btn btn-outline-primary btn-sm" {% comment %} <button type="button" class="btn btn-outline-primary btn-sm"
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#candidateModal" data-bs-target="#candidateModal"
hx-get="#" hx-get="#"
hx-target="#candidateModalBody"> hx-target="#candidateModalBody">
<i class="fas fa-eye me-1"></i> {% trans "AI Scoring" %} <i class="fas fa-eye me-1"></i> {% trans "AI Scoring" %}
</button> </button> {% endcomment %}
</div> </div>
</div> </div>

View File

@ -187,7 +187,7 @@
<div class="filter-controls"> <div class="filter-controls">
<form method="get" class="row g-3"> <form method="get" class="row g-3">
<div class="col-md-3"> <div class="col-md-3">
<label for="job_filter" class="form-label form-label-sm">{% trans "Job" %}</label> {% comment %} <label for="job_filter" class="form-label form-label-sm">{% trans "Job" %}</label>
<select name="job" id="job_filter" class="form-select form-select-sm"> <select name="job" id="job_filter" class="form-select form-select-sm">
<option value="">{% trans "All Jobs" %}</option> <option value="">{% trans "All Jobs" %}</option>
{% for job in jobs %} {% for job in jobs %}
@ -195,8 +195,16 @@
{{ job.title }} {{ job.title }}
</option> </option>
{% endfor %} {% endfor %}
</select> {% endcomment %}
<label for="job_filter" class="form-label small text-muted">{% trans "Filter by Job" %}</label>
<select name="job" id="job_filter" class="form-select form-select-sm">
<option value="">{% trans "All Jobs" %}</option>
{% for job in jobs %}
<option value="{{ job.slug }}" {% if job_filter == job.slug %}selected{% endif %}>{{ job.title }}</option>
{% endfor %}
</select> </select>
</div> </div>
<div class="col-md-2"> <div class="col-md-2">
<label for="status_filter" class="form-label form-label-sm">{% trans "Status" %}</label> <label for="status_filter" class="form-label form-label-sm">{% trans "Status" %}</label>
<select name="status" id="status_filter" class="form-select form-select-sm"> <select name="status" id="status_filter" class="form-select form-select-sm">
@ -211,8 +219,8 @@
<label for="type_filter" class="form-label form-label-sm">{% trans "Type" %}</label> <label for="type_filter" class="form-label form-label-sm">{% trans "Type" %}</label>
<select name="type" id="type_filter" class="form-select form-select-sm"> <select name="type" id="type_filter" class="form-select form-select-sm">
<option value="">{% trans "All Types" %}</option> <option value="">{% trans "All Types" %}</option>
<option value="remote" {% if request.GET.type == "remote" %}selected{% endif %}>{% trans "Remote" %}</option> <option value="Remote" {% if request.GET.type == "Remote" %}selected{% endif %}>{% trans "Remote" %}</option>
<option value="onsite" {% if request.GET.type == "onsite" %}selected{% endif %}>{% trans "Onsite" %}</option> <option value="Onsite" {% if request.GET.type == "Onsite" %}selected{% endif %}>{% trans "Onsite" %}</option>
</select> </select>
</div> </div>
<div class="col-md-3"> <div class="col-md-3">

View File

@ -1,107 +1,101 @@
{% extends "portal_base.html" %} {% extends "portal_base.html" %}
{% load static %} {% load static i18n %}
{% block title %}{{ message.subject }}{% endblock %} {% block title %}{{ message.subject }}{% endblock %}
{% block content %} {% block content %}
<div class="container-fluid"> <div class="container-fluid py-4">
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<!-- Message Header --> <div class="card shadow-sm mb-4 border-0">
<div class="card mb-4"> <div class="card-header bg-gradient border-0 pb-3">
<div class="card-header d-flex justify-content-between align-items-center"> <div class="d-flex justify-content-between align-items-start gap-3">
<h5 class="mb-0"> <div>
{{ message.subject }} <h4 class="mb-2 fw-bold">{{ message.subject }}</h4>
{% if message.parent_message %} {% if message.parent_message %}
<span class="badge bg-secondary ms-2">Reply</span> <span class="badge bg-info">{% trans "Reply" %}</span>
{% endif %} {% endif %}
</h5> </div>
<div class="btn-group" role="group"> <div class="btn-group flex-wrap" role="group">
<a href="{% url 'message_reply' message.id %}" class="btn btn-outline-info"> <a href="{% url 'message_reply' message.id %}" class="btn btn-sm btn-main-action">
<i class="fas fa-reply"></i> Reply <i class="fas fa-reply me-1"></i> {% trans "Reply" %}
</a>
{% if message.recipient == request.user %}
<a href="{% url 'message_mark_unread' message.id %}"
class="btn btn-outline-warning"
hx-post="{% url 'message_mark_unread' message.id %}">
<i class="fas fa-envelope"></i> Mark Unread
</a> </a>
{% endif %} {% if message.recipient == request.user %}
<a href="{% url 'message_delete' message.id %}" <button type="button" class="btn btn-sm btn-outline-warning"
class="btn btn-outline-danger" hx-post="{% url 'message_mark_unread' message.id %}"
hx-get="{% url 'message_delete' message.id %}" hx-swap="outerHTML">
hx-confirm="Are you sure you want to delete this message?"> <i class="fas fa-envelope me-1"></i> {% trans "Mark Unread" %}
<i class="fas fa-trash"></i> Delete </button>
</a> {% endif %}
<a href="{% url 'message_list' %}" class="btn btn-outline-secondary"> <button type="button" class="btn btn-sm btn-outline-danger"
<i class="fas fa-arrow-left"></i> Back to Messages hx-post="{% url 'message_delete' message.id %}"
</a> hx-confirm="{% trans 'Are you sure you want to permanently delete this message? This action cannot be undone.' %}"
hx-redirect="{% url 'message_list' %}"
onclick="this.disabled=true;">
<i class="fas fa-trash me-1"></i> {% trans "Delete" %}
</button>
<a href="{% url 'message_list' %}" class="btn btn-sm btn-outline-secondary">
<i class="fas fa-arrow-left me-1"></i> {% trans "Back" %}
</a>
</div>
</div> </div>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="row mb-3"> <div class="row g-4">
<div class="col-md-6"> <div class="col-md-6">
<strong>From:</strong> <small class="text-muted d-block mb-1">{% trans "From:" %}</small>
<span class="text-primary">{{ message.sender.get_full_name|default:message.sender.username }}</span> <span class="fw-semibold">{{ message.sender.get_full_name|default:message.sender.username }}</span>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<strong>To:</strong> <small class="text-muted d-block mb-1">{% trans "To:" %}</small>
<span class="text-primary">{{ message.recipient.get_full_name|default:message.recipient.username }}</span> <span class="fw-semibold">{{ message.recipient.get_full_name|default:message.recipient.username }}</span>
</div> </div>
</div>
<div class="row mb-3">
<div class="col-md-6"> <div class="col-md-6">
<strong>Type:</strong> <small class="text-muted d-block mb-1">{% trans "Type:" %}</small>
<span class="badge bg-{{ message.message_type|lower }}"> <span class="badge bg-primary-theme">
{{ message.get_message_type_display }} {{message.get_message_type_display}}
</span> </span>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<strong>Status:</strong> <small class="text-muted d-block mb-1">{% trans "Status:" %}</small>
{% if message.is_read %} {% if message.is_read %}
<span class="badge bg-success">Read</span> <span class="badge bg-success">{% trans "Read" %}</span>
{% if message.read_at %} {% if message.read_at %}
<small class="text-muted">({{ message.read_at|date:"M d, Y H:i" }})</small> <small class="text-muted">({{ message.read_at|date:"M d, Y H:i" }})</small>
{% endif %} {% endif %}
{% else %} {% else %}
<span class="badge bg-warning">Unread</span> <span class="badge bg-warning text-dark">{% trans "Unread" %}</span>
{% endif %} {% endif %}
</div> </div>
</div>
<div class="row mb-3">
<div class="col-md-6"> <div class="col-md-6">
<strong>Created:</strong> <small class="text-muted d-block mb-1">{% trans "Created:" %}</small>
<span>{{ message.created_at|date:"M d, Y H:i" }}</span> <span>{{ message.created_at|date:"M d, Y H:i" }}</span>
</div> </div>
{% if message.job %} {% if message.job %}
<div class="col-md-6"> <div class="col-md-6">
<strong>Related Job:</strong> <small class="text-muted d-block mb-1">{% trans "Related Job:" %}</small>
<a href="{% url 'job_detail' message.job.slug %}" class="text-primary"> <a href="{% url 'job_detail' message.job.slug %}" class="fw-semibold text-decoration-none text-primary-theme">
{{ message.job.title }} {{ message.job.title }}
</a> </a>
</div> </div>
{% endif %} {% endif %}
</div> </div>
{% if message.parent_message %} {% if message.parent_message %}
<div class="alert alert-info"> <div class="alert alert-info mt-3 mb-0 border-0">
<strong>In reply to:</strong> <strong>{% trans "In reply to:" %}</strong>
<a href="{% url 'message_detail' message.parent_message.id %}"> <a href="{% url 'message_detail' message.parent_message.id %}" class="text-decoration-none">
{{ message.parent_message.subject }} {{ message.parent_message.subject }}
</a> </a>
<small class="text-muted d-block"> <small class="text-muted d-block mt-2">
From {{ message.parent_message.sender.get_full_name|default:message.parent_message.sender.username }} {% trans "From" %} {{ message.parent_message.sender.get_full_name|default:message.parent_message.sender.username }}
on {{ message.parent_message.created_at|date:"M d, Y H:i" }} {% trans "on" %} {{ message.parent_message.created_at|date:"M d, Y H:i" }}
</small> </small>
</div> </div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
<!-- Message Content --> <div class="card shadow-sm border-0 mb-4">
<div class="card">
<div class="card-header">
<h6 class="mb-0">Message</h6>
</div>
<div class="card-body"> <div class="card-body">
<div class="message-content"> <div class="message-content">
{{ message.content|linebreaks }} {{ message.content|linebreaks }}
@ -109,17 +103,16 @@
</div> </div>
</div> </div>
<!-- Message Thread (if this is a reply and has replies) -->
{% if message.replies.all %} {% if message.replies.all %}
<div class="card mt-4"> <div class="card shadow-sm border-0">
<div class="card-header"> <div class="card-header bg-light border-0">
<h6 class="mb-0"> <h6 class="mb-0">
<i class="fas fa-comments"></i> Replies ({{ message.replies.count }}) <i class="fas fa-comments text-primary"></i> {% trans "Replies" %} <span class="badge bg-primary">{{ message.replies.count }}</span>
</h6> </h6>
</div> </div>
<div class="card-body"> <div class="card-body">
{% for reply in message.replies.all %} {% for reply in message.replies.all %}
<div class="border-start ps-3 mb-3"> <div class="message-reply mb-3 p-3 bg-light rounded">
<div class="d-flex justify-content-between align-items-start mb-2"> <div class="d-flex justify-content-between align-items-start mb-2">
<div> <div>
<strong>{{ reply.sender.get_full_name|default:reply.sender.username }}</strong> <strong>{{ reply.sender.get_full_name|default:reply.sender.username }}</strong>
@ -131,14 +124,12 @@
{{ reply.get_message_type_display }} {{ reply.get_message_type_display }}
</span> </span>
</div> </div>
<div class="reply-content"> <div class="reply-content mb-3">
{{ reply.content|linebreaks }} {{ reply.content|linebreaks }}
</div> </div>
<div class="mt-2"> <a href="{% url 'message_reply' reply.id %}" class="btn btn-sm btn-outline-primary">
<a href="{% url 'message_reply' reply.id %}" class="btn btn-sm btn-outline-info"> <i class="fas fa-reply me-1"></i> {% trans "Reply" %}
<i class="fas fa-reply"></i> Reply to this </a>
</a>
</div>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
@ -149,31 +140,35 @@
</div> </div>
{% endblock %} {% endblock %}
{% block extra_css %} {% block customCSS %}
<style> <style>
.message-content { .message-content {
white-space: pre-wrap; white-space: pre-wrap;
word-wrap: break-word; word-wrap: break-word;
line-height: 1.6; line-height: 1.6;
padding: 1rem; padding: 1.5rem;
background-color: #f8f9fa; background-color: #f8f9fa;
border-radius: 0.375rem; border-radius: 0.5rem;
border: 1px solid #dee2e6; border: 1px solid #e9ecef;
} }
.reply-content { .reply-content {
white-space: pre-wrap; white-space: pre-wrap;
word-wrap: break-word; word-wrap: break-word;
line-height: 1.5; line-height: 1.5;
font-size: 0.9rem; font-size: 0.95rem;
} }
.border-start { .message-reply:hover {
border-left: 3px solid #0d6efd; box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
} }
.ps-3 { .bg-gradient {
padding-left: 1rem; background: linear-gradient(135deg, #f5f7fa 0%, #ffffff 100%);
}
.btn-group .btn {
font-size: 0.875rem;
} }
</style> </style>
{% endblock %} {% endblock %}

View File

@ -285,7 +285,7 @@
<div class="row"> <div class="row">
<div class="col-md-6 mb-3"> <div class="col-md-6 mb-3">
<label for="{{ form.email.id_for_label }}" class="form-label"> <label for="{{ form.email.id_for_label }}" class="form-label">
{{ form.email.label }} {{ form.email.label }}<span class="text-danger">*</span>
</label> </label>
{{ form.email|add_class:"form-control" }} {{ form.email|add_class:"form-control" }}
{% if form.email.errors %} {% if form.email.errors %}