issue regarding the agency create
This commit is contained in:
parent
96c259cbc5
commit
40eeef76fd
@ -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',
|
|
||||||
subject,
|
|
||||||
custom_message, # Pass the custom message
|
|
||||||
[recipient_email], # Pass the specific recipient as a list of one
|
|
||||||
processed_attachments,
|
|
||||||
sender_user_id,
|
|
||||||
job_id,
|
|
||||||
hook='recruitment.tasks.email_success_hook',
|
|
||||||
|
|
||||||
)
|
|
||||||
task_ids.append(task_id)
|
task_id = async_task(
|
||||||
|
'recruitment.tasks.send_bulk_email_task',
|
||||||
|
subject,
|
||||||
|
customized_sends,
|
||||||
|
processed_attachments,
|
||||||
|
sender_user_id,
|
||||||
|
job_id,
|
||||||
|
hook='recruitment.tasks.email_success_hook',
|
||||||
|
|
||||||
|
)
|
||||||
|
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.")
|
||||||
|
|
||||||
|
|||||||
@ -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 (
|
||||||
|
|||||||
@ -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}")
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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>
|
||||||
|
|
||||||
|
|||||||
@ -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">
|
||||||
|
|||||||
@ -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 %}
|
||||||
@ -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 %}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user