small changes and career page fix

This commit is contained in:
ismail 2025-12-18 22:56:48 +03:00
parent f1310189aa
commit 551a28d986
15 changed files with 163 additions and 88 deletions

View File

@ -209,21 +209,21 @@ ACCOUNT_LOGIN_ON_EMAIL_CONFIRMATION = True
ACCOUNT_FORMS = {"signup": "recruitment.forms.StaffSignupForm"} ACCOUNT_FORMS = {"signup": "recruitment.forms.StaffSignupForm"}
MAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" # MAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
EMAIL_HOST = "10.10.1.110" # EMAIL_HOST = "10.10.1.110"
EMAIL_PORT = 2225 # EMAIL_PORT = 2225
# EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" # EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
# EMAIL_HOST_PASSWORD = os.getenv("EMAIL_PASSWORD", "mssp.0Q0rSwb.zr6ke4n2k3e4on12.aHwJqnI") EMAIL_HOST_PASSWORD = os.getenv("EMAIL_PASSWORD", "mssp.0Q0rSwb.zr6ke4n2k3e4on12.aHwJqnI")
# EMAIL_HOST = "smtp.mailersend.net" EMAIL_HOST = "smtp.mailersend.net"
# EMAIL_PORT = 2525 EMAIL_PORT = 2525
# EMAIL_HOST_USER = "MS_lhygCJ@test-65qngkd8nx3lwr12.mlsender.net" EMAIL_HOST_USER = "MS_lhygCJ@test-65qngkd8nx3lwr12.mlsender.net"
# EMAIL_HOST_PASSWORD = "mssp.0Q0rSwb.zr6ke4n2k3e4on12.aHwJqnI" EMAIL_HOST_PASSWORD = "mssp.0Q0rSwb.zr6ke4n2k3e4on12.aHwJqnI"
# EMAIL_USE_TLS = True EMAIL_USE_TLS = True
# EMAIL_HOST = 'sandbox.smtp.mailtrap.io' EMAIL_HOST = 'sandbox.smtp.mailtrap.io'
# EMAIL_HOST_USER = '38e5179debe69a' EMAIL_HOST_USER = '38e5179debe69a'
# EMAIL_HOST_PASSWORD = 'ffa75647d01ecb' EMAIL_HOST_PASSWORD = 'ffa75647d01ecb'
# EMAIL_PORT = '2525' EMAIL_PORT = '2525'
# Crispy Forms Configuration # Crispy Forms Configuration

View File

@ -377,6 +377,13 @@ class ApplicationForm(forms.ModelForm):
Submit("submit", _("Submit"), css_class="btn btn-primary"), Submit("submit", _("Submit"), css_class="btn btn-primary"),
) )
def clean_job(self):
job = self.cleaned_data.get("job")
if job.max_applications <= Application.objects.filter(job=job).count():
raise forms.ValidationError(
"The maximum number of applicants for this job has been reached."
)
return job
# def clean(self): # def clean(self):
# cleaned_data = super().clean() # cleaned_data = super().clean()
# job = cleaned_data.get("job") # job = cleaned_data.get("job")
@ -1805,6 +1812,27 @@ class PasswordResetForm(forms.Form):
raise forms.ValidationError(_('New passwords do not match.')) raise forms.ValidationError(_('New passwords do not match.'))
return cleaned_data return cleaned_data
class PersonPasswordResetForm(forms.Form):
new_password1 = forms.CharField(
widget=forms.PasswordInput(attrs={'class': 'form-control'}),
label=_('New Password')
)
new_password2 = forms.CharField(
widget=forms.PasswordInput(attrs={'class': 'form-control'}),
label=_('Confirm New Password')
)
def clean(self):
"""Custom validation for password reset"""
cleaned_data = super().clean()
new_password1 = cleaned_data.get('new_password1')
new_password2 = cleaned_data.get('new_password2')
if new_password1 and new_password2:
if new_password1 != new_password2:
raise forms.ValidationError(_('New passwords do not match.'))
return cleaned_data
class StaffAssignmentForm(forms.ModelForm): class StaffAssignmentForm(forms.ModelForm):

View File

@ -28,8 +28,9 @@ class EmailService:
try: try:
# Using EmailMessage for more control (e.g., HTML content) # Using EmailMessage for more control (e.g., HTML content)
from time import sleep
for recipient in recipient_list: for recipient in recipient_list:
sleep(2)
email = EmailMessage( email = EmailMessage(
subject=subject, subject=subject,
body=body, body=body,
@ -46,8 +47,6 @@ class EmailService:
recipient_user=User.objects.filter(email=recipient).first() recipient_user=User.objects.filter(email=recipient).first()
if result and recipient_user and not context["message_created"]: if result and recipient_user and not context["message_created"]:
Message.objects.create(sender=context['sender_user'],recipient=recipient_user,job=context['job'],subject=subject,content=context['email_message'],message_type='DIRECT',is_read=False) Message.objects.create(sender=context['sender_user'],recipient=recipient_user,job=context['job'],subject=subject,content=context['email_message'],message_type='DIRECT',is_read=False)
return len(recipient_list) return len(recipient_list)
except Exception as e: except Exception as e:

View File

@ -103,6 +103,7 @@ urlpatterns = [
path("persons/<slug:slug>/", views.PersonDetailView.as_view(), name="person_detail"), path("persons/<slug:slug>/", views.PersonDetailView.as_view(), name="person_detail"),
path("persons/<slug:slug>/update/", views.PersonUpdateView.as_view(), name="person_update"), path("persons/<slug:slug>/update/", views.PersonUpdateView.as_view(), name="person_update"),
path("persons/<slug:slug>/delete/", views.PersonDeleteView.as_view(), name="person_delete"), path("persons/<slug:slug>/delete/", views.PersonDeleteView.as_view(), name="person_delete"),
path("persons/<slug:slug>/password_reset/", views.password_reset, name="password_reset"),
# ======================================================================== # ========================================================================
# FORM & TEMPLATE MANAGEMENT # FORM & TEMPLATE MANAGEMENT

View File

@ -280,6 +280,11 @@ class PersonDetailView(DetailView, LoginRequiredMixin, StaffRequiredMixin):
template_name = "people/person_detail.html" template_name = "people/person_detail.html"
context_object_name = "person" context_object_name = "person"
def get_context_data(self, **kwargs):
from .forms import PersonPasswordResetForm
context = super().get_context_data(**kwargs)
context['password_form'] = PersonPasswordResetForm()
return context
class PersonUpdateView(UpdateView, LoginRequiredMixin, StaffOrAgencyRequiredMixin): class PersonUpdateView(UpdateView, LoginRequiredMixin, StaffOrAgencyRequiredMixin):
model = Person model = Person
@ -1302,11 +1307,10 @@ def delete_form_template(request, template_id):
# @staff_or_candidate_required # @staff_or_candidate_required
def application_submit_form(request, slug): def application_submit_form(request, slug):
"""Display the form as a step-by-step wizard""" """Display the form as a step-by-step wizard"""
form_template = get_object_or_404(FormTemplate, slug=slug, is_active=True) job = get_object_or_404(JobPosting, slug=slug)
if not request.user.is_authenticated: if not request.user.is_authenticated:
return redirect("application_signup", slug=slug) return redirect("application_signup", slug=slug)
print(form_template.job.slug)
job = get_object_or_404(JobPosting, slug=form_template.job.slug)
if request.user.user_type == "candidate": if request.user.user_type == "candidate":
person = request.user.person_profile person = request.user.person_profile
if job.has_already_applied_to_this_job(person): if job.has_already_applied_to_this_job(person):
@ -1316,23 +1320,16 @@ def application_submit_form(request, slug):
"You have already applied to this job: Multiple applications are not allowed." "You have already applied to this job: Multiple applications are not allowed."
), ),
) )
return redirect("job_application_detail", slug=job.slug) return redirect("job_application_detail", slug=slug)
# template = get_object_or_404(FormTemplate, slug=slug, is_active=True) if job.is_application_limit_reached:
template = job.form_template
stage = template.stages.filter(name="Contact Information")
job_id = template.job.internal_job_id
job = template.job
is_limit_exceeded = job.is_application_limit_reached
if is_limit_exceeded:
messages.error( messages.error(
request, request,
_( _(
"Application limit reached: This job is no longer accepting new applications." "Application limit reached: This job is no longer accepting new applications."
), ),
) )
return redirect("application_detail", slug=job.slug) return redirect("job_application_detail", slug=slug)
if job.is_expired: if job.is_expired:
messages.error( messages.error(
request, request,
@ -1340,12 +1337,12 @@ def application_submit_form(request, slug):
"Application deadline passed: This job is no longer accepting new applications." "Application deadline passed: This job is no longer accepting new applications."
), ),
) )
return redirect("application_detail", slug=job.slug) return redirect("job_application_detail", slug=slug)
return render( return render(
request, request,
"applicant/application_submit_form.html", "applicant/application_submit_form.html",
{"template_slug": template.slug, "job_id": job_id}, {"template_slug": job.form_template.slug, "job_id": job.internal_job_id},
) )
@ -1357,8 +1354,9 @@ def application_submit(request, template_slug):
import re import re
"""Handle form submission""" """Handle form submission"""
if not request.user.is_authenticated: # or request.user.user_type != "candidate": if not request.user.is_authenticated or request.user.user_type != "candidate":
return JsonResponse({"success": False, "message": "Unauthorized access."}) return JsonResponse({"success": False, "message": "Unauthorized access."})
template = get_object_or_404(FormTemplate, slug=template_slug) template = get_object_or_404(FormTemplate, slug=template_slug)
job = template.job job = template.job
if request.method == "POST": if request.method == "POST":
@ -1414,38 +1412,38 @@ def application_submit(request, template_slug):
except FormField.DoesNotExist: except FormField.DoesNotExist:
continue continue
try: try:
gpa = submission.responses.get(field__label="GPA") # gpa = submission.responses.get(field__label="GPA")
if gpa and gpa.value: # if gpa and gpa.value:
gpa_str = gpa.value.replace("/", "").strip() # gpa_str = gpa.value.replace("/", "").strip()
if not re.match(r"^\d+(\.\d+)?$", gpa_str): # if not re.match(r"^\d+(\.\d+)?$", gpa_str):
# --- FIX APPLIED HERE --- # # --- FIX APPLIED HERE ---
return JsonResponse( # return JsonResponse(
{ # {
"success": False, # "success": False,
"message": _("GPA must be a numeric value."), # "message": _("GPA must be a numeric value."),
} # }
) # )
try: # try:
gpa_float = float(gpa_str) # gpa_float = float(gpa_str)
except ValueError: # except ValueError:
# --- FIX APPLIED HERE --- # # --- FIX APPLIED HERE ---
return JsonResponse( # return JsonResponse(
{ # {
"success": False, # "success": False,
"message": _("GPA must be a numeric value."), # "message": _("GPA must be a numeric value."),
} # }
) # )
if not (0.0 <= gpa_float <= 4.0): # if not (0.0 <= gpa_float <= 4.0):
# --- FIX APPLIED HERE --- # # --- FIX APPLIED HERE ---
return JsonResponse( # return JsonResponse(
{ # {
"success": False, # "success": False,
"message": _("GPA must be between 0.0 and 4.0."), # "message": _("GPA must be between 0.0 and 4.0."),
} # }
) # )
resume = submission.responses.get(field__label="Resume Upload") resume = submission.responses.get(field__label="Resume Upload")
@ -1456,7 +1454,7 @@ def application_submit(request, template_slug):
submission.save() submission.save()
# time=timezone.now() # time=timezone.now()
person = request.user.person_profile person = request.user.person_profile
person.gpa = gpa.value if gpa else None # person.gpa = gpa.value if gpa else None
person.save() person.save()
Application.objects.create( Application.objects.create(
person=person, person=person,
@ -2990,6 +2988,22 @@ def portal_password_reset(request, pk):
for error in errors: for error in errors:
messages.error(request, f"{field}: {error}") messages.error(request, f"{field}: {error}")
@require_POST
def password_reset(request, slug):
from .forms import PersonPasswordResetForm
person = get_object_or_404(Person, slug=slug)
if request.method == "POST":
form = PersonPasswordResetForm(request.POST)
if form.is_valid():
person.user.set_password(form.cleaned_data["new_password1"])
person.user.save()
messages.success(request, "Password reset successfully.")
return redirect("person_detail", slug=person.slug)
else:
for field, errors in form.errors.items():
for error in errors:
messages.error(request, f"{field}: {error}")
def portal_login(request): def portal_login(request):
"""Unified portal login for agency and applicant""" """Unified portal login for agency and applicant"""

View File

@ -41,7 +41,7 @@
<i class="fas fa-paper-plane me-2"></i> {% trans "You already applied for this position" %} <i class="fas fa-paper-plane me-2"></i> {% trans "You already applied for this position" %}
</button> </button>
{% else %} {% else %}
<a href="{% url 'application_submit_form' job.form_template.slug %}" class="btn btn-main-action btn-lg w-100"> <a href="{% url 'application_submit_form' job.slug %}" class="btn btn-main-action btn-lg w-100">
<i class="fas fa-paper-plane me-2"></i> {% trans "Apply for this Position" %} <i class="fas fa-paper-plane me-2"></i> {% trans "Apply for this Position" %}
</a> </a>
{% endif %} {% endif %}
@ -220,12 +220,10 @@
<i class="fas fa-paper-plane me-2"></i> {% trans "You already applied for this position" %} <i class="fas fa-paper-plane me-2"></i> {% trans "You already applied for this position" %}
</button> </button>
{% else %} {% else %}
<a href="{% url 'application_submit_form' job.form_template.slug %}" class="btn btn-main-action btn-lg w-100"> <a href="{% url 'application_submit_form' job.slug %}" class="btn btn-main-action btn-lg w-100">
<i class="fas fa-paper-plane me-2"></i> {% trans "Apply for this Position" %} <i class="fas fa-paper-plane me-2"></i> {% trans "Apply for this Position" %}
</a> </a>
{% endif %} {% endif %}
</footer> </footer>
{% endif %} {% endif %}
{% endblock content%} {% endblock content%}

View File

@ -231,7 +231,7 @@
<div class="mt-auto pt-2 border-top"> <div class="mt-auto pt-2 border-top">
<div class="d-flex gap-2 justify-content-end"> <div class="d-flex gap-2 justify-content-end">
<a href="{% url 'application_submit_form' template.slug %}" class="btn btn-outline-primary btn-sm" title="{% trans 'Preview' %}"> <a href="{% url 'application_submit_form' template.job.slug %}" class="btn btn-outline-primary btn-sm" title="{% trans 'Preview' %}">
<i class="fas fa-eye"></i> <i class="fas fa-eye"></i>
</a> </a>
<a href="{% url 'form_builder' template.slug %}" class="btn btn-outline-secondary btn-sm" title="{% trans 'Edit' %}"> <a href="{% url 'form_builder' template.slug %}" class="btn btn-outline-secondary btn-sm" title="{% trans 'Edit' %}">
@ -286,7 +286,7 @@
<td>{{ template.updated_at|date:"M d, Y" }}</td> <td>{{ template.updated_at|date:"M d, Y" }}</td>
<td class="text-end"> <td class="text-end">
<div class="btn-group btn-group-sm" role="group"> <div class="btn-group btn-group-sm" role="group">
<a href="{% url 'application_submit_form' template.slug %}" class="btn btn-outline-primary" title="{% trans 'Preview' %}"> <a href="{% url 'application_submit_form' template.job.slug %}" class="btn btn-outline-primary" title="{% trans 'Preview' %}">
<i class="fas fa-eye"></i> <i class="fas fa-eye"></i>
</a> </a>
<a href="{% url 'form_builder' template.slug %}" class="btn btn-outline-secondary" title="{% trans 'Edit' %}"> <a href="{% url 'form_builder' template.slug %}" class="btn btn-outline-secondary" title="{% trans 'Edit' %}">

View File

@ -216,10 +216,11 @@
{% else %}bg-secondary{% endif %}'> {% else %}bg-secondary{% endif %}'>
{{ job.get_status_display }} {{ job.get_status_display }}
</span> </span>
{% if user.is_staff and user == application.job.assigned_to or user.is_superuser %}
<button type="button" class="btn btn-outline-light btn-sm ms-2" data-bs-toggle="modal" data-bs-target="#editStatusModal"> <button type="button" class="btn btn-outline-light btn-sm ms-2" data-bs-toggle="modal" data-bs-target="#editStatusModal">
<i class="fas fa-edit"></i> <i class="fas fa-edit"></i>
</button> </button>
{% endif %}
</div> </div>
{# Share Public Link Button #} {# Share Public Link Button #}
@ -403,10 +404,11 @@
<p class="text-muted small mb-3"> <p class="text-muted small mb-3">
{% trans "Manage the custom application forms associated with this job posting." %} {% trans "Manage the custom application forms associated with this job posting." %}
</p> </p>
{% if user.is_staff and user == application.job.assigned_to or user.is_superuser %}
<a href="{% url 'form_builder' job.form_template.slug %}" class="btn btn-outline-secondary w-100"> <a href="{% url 'form_builder' job.form_template.slug %}" class="btn btn-outline-secondary w-100">
<i class="fas fa-list-alt me-1"></i> {% trans "Manage Job Form" %} <i class="fas fa-list-alt me-1"></i> {% trans "Manage Job Form" %}
</a> </a>
{% endif %}
{% comment %} {% if not job.form_template %} {% comment %} {% if not job.form_template %}
<a href="{% url 'create_form_template' %}" class="btn btn-main-action"> <a href="{% url 'create_form_template' %}" class="btn btn-main-action">
<i class="fas fa-plus-circle me-1"></i> {% trans "Create New Form Template" %} <i class="fas fa-plus-circle me-1"></i> {% trans "Create New Form Template" %}

View File

@ -1,5 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load static i18n %} {% load static i18n crispy_forms_tags %}
{% block title %}{{ person.get_full_name }} - {{ block.super }}{% endblock %} {% block title %}{{ person.get_full_name }} - {{ block.super }}{% endblock %}
@ -536,6 +536,33 @@
</a> </a>
{% if user.is_staff %} {% if user.is_staff %}
<div class="d-flex gap-2"> <div class="d-flex gap-2">
<button type="button" class="btn btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#setStaffPasswordModal">
<i class="fas fa-lock me-1"></i> {% trans "Set Staff Password" %}
</button>
<!-- Set Staff Password Modal -->
<div class="modal fade" id="setStaffPasswordModal" tabindex="-1" aria-labelledby="setStaffPasswordModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="setStaffPasswordModalLabel">
<i class="fas fa-lock me-2"></i> {% trans "Set Staff Password" %}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form action="{% url 'password_reset' person.slug %}" method="post">
{% csrf_token %}
{{password_form|crispy}}
<div class="d-flex justify-content-end">
<button type="submit" class="btn btn-primary">{% trans "Save" %}</button>
</div>
</form>
</div>
</div>
</div>
</div>
<a href="{% url 'person_update' person.slug %}" class="btn btn-main-action"> <a href="{% url 'person_update' person.slug %}" class="btn btn-main-action">
<i class="fas fa-edit me-1"></i> {% trans "Edit Applicant" %} <i class="fas fa-edit me-1"></i> {% trans "Edit Applicant" %}
</a> </a>
@ -543,8 +570,6 @@
<i class="fas fa-trash-alt me-1"></i> {% trans "Delete" %} <i class="fas fa-trash-alt me-1"></i> {% trans "Delete" %}
</a> </a>
</div>
{% endif %} {% endif %}
</div> </div>
</div> </div>

View File

@ -231,7 +231,7 @@
<div class="card-footer text-center"> <div class="card-footer text-center">
<small class="text-muted"> <small class="text-muted">
{% trans "Already have an account?" %} {% trans "Already have an account?" %}
<a href="{% url 'account_login' %}?next={% url 'application_submit_form' job.form_template.slug %}" class="text-decoration-none text-kaauh-teal"> <a href="{% url 'account_login' %}?next={% url 'application_submit_form' job.slug %}" class="text-decoration-none text-kaauh-teal">
{% trans "Login here" %} {% trans "Login here" %}
</a> </a>
</small> </small>

View File

@ -298,7 +298,7 @@
</small> </small>
</div> </div>
{# Change Stage button #} {# Change Stage button #}
{% if user.is_staff %} {% if user.is_staff and user == application.job.assigned_to or user.is_superuser %}
<button type="button" class="btn btn-outline-light btn-sm mt-1" data-bs-toggle="modal" data-bs-target="#stageUpdateModal"> <button type="button" class="btn btn-outline-light btn-sm mt-1" data-bs-toggle="modal" data-bs-target="#stageUpdateModal">
<i class="fas fa-exchange-alt"></i> {% trans "Change Stage" %} <i class="fas fa-exchange-alt"></i> {% trans "Change Stage" %}
</button> </button>

View File

@ -402,7 +402,7 @@
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#noteModal" data-bs-target="#noteModal"
hx-get="{% url 'application_add_note' application.slug %}" hx-get="{% url 'application_add_note' application.slug %}"
hx-swap="outerHTML" hx-swap="innerHTML"
hx-target=".notemodal"> hx-target=".notemodal">
<i class="fas fa-calendar-plus me-1"></i> <i class="fas fa-calendar-plus me-1"></i>
Add note Add note

View File

@ -329,7 +329,7 @@
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#noteModal" data-bs-target="#noteModal"
hx-get="{% url 'application_add_note' application.slug %}" hx-get="{% url 'application_add_note' application.slug %}"
hx-swap="outerHTML" hx-swap="innerHTML"
hx-target=".notemodal"> hx-target=".notemodal">
<i class="fas fa-calendar-plus me-1"></i> <i class="fas fa-calendar-plus me-1"></i>
Add note Add note

View File

@ -358,7 +358,7 @@
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#noteModal" data-bs-target="#noteModal"
hx-get="{% url 'application_add_note' application.slug %}" hx-get="{% url 'application_add_note' application.slug %}"
hx-swap="outerHTML" hx-swap="innerHTML"
hx-target=".notemodal"> hx-target=".notemodal">
<i class="fas fa-calendar-plus me-1"></i> <i class="fas fa-calendar-plus me-1"></i>
Add note Add note

View File

@ -383,6 +383,9 @@
<th scope="col" style="width: 5%;"> <th scope="col" style="width: 5%;">
<i class="fas fa-graduation-cap me-1"></i> {% trans "GPA" %} <i class="fas fa-graduation-cap me-1"></i> {% trans "GPA" %}
</th> </th>
<th scope="col" style="width: 5%;">
<i class="fas fa-graduation-cap me-1"></i> {% trans "Years of Experience" %}
</th>
<th scope="col" style="width: 5%;" class="text-center"> <th scope="col" style="width: 5%;" class="text-center">
<i class="fas fa-robot me-1"></i> {% trans "AI Score" %} <i class="fas fa-robot me-1"></i> {% trans "AI Score" %}
</th> </th>
@ -426,6 +429,11 @@
</div> </div>
</td> </td>
<td class="text-center">{{application.person.gpa|default:"0"}}</td> <td class="text-center">{{application.person.gpa|default:"0"}}</td>
<td class="text-center">
<span class="badge ai-score-badge">
{{ application.years_of_experience }}
</span>
</td>
<td class="text-center"> <td class="text-center">
{% if application.is_resume_parsed %} {% if application.is_resume_parsed %}
{% if application.match_score %} {% if application.match_score %}
@ -473,7 +481,7 @@
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#noteModal" data-bs-target="#noteModal"
hx-get="{% url 'application_add_note' application.slug %}" hx-get="{% url 'application_add_note' application.slug %}"
hx-swap="outerHTML" hx-swap="innerHTML"
hx-target=".notemodal"> hx-target=".notemodal">
<i class="fas fa-calendar-plus me-1"></i> <i class="fas fa-calendar-plus me-1"></i>
Add note Add note