diff --git a/NorahUniversity/settings.py b/NorahUniversity/settings.py index ccb1a6a..7bb0640 100644 --- a/NorahUniversity/settings.py +++ b/NorahUniversity/settings.py @@ -209,21 +209,21 @@ ACCOUNT_LOGIN_ON_EMAIL_CONFIRMATION = True ACCOUNT_FORMS = {"signup": "recruitment.forms.StaffSignupForm"} -MAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" -EMAIL_HOST = "10.10.1.110" -EMAIL_PORT = 2225 +# MAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" +# EMAIL_HOST = "10.10.1.110" +# EMAIL_PORT = 2225 # EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" -# EMAIL_HOST_PASSWORD = os.getenv("EMAIL_PASSWORD", "mssp.0Q0rSwb.zr6ke4n2k3e4on12.aHwJqnI") -# EMAIL_HOST = "smtp.mailersend.net" -# EMAIL_PORT = 2525 -# EMAIL_HOST_USER = "MS_lhygCJ@test-65qngkd8nx3lwr12.mlsender.net" -# EMAIL_HOST_PASSWORD = "mssp.0Q0rSwb.zr6ke4n2k3e4on12.aHwJqnI" -# EMAIL_USE_TLS = True -# EMAIL_HOST = 'sandbox.smtp.mailtrap.io' -# EMAIL_HOST_USER = '38e5179debe69a' -# EMAIL_HOST_PASSWORD = 'ffa75647d01ecb' -# EMAIL_PORT = '2525' +EMAIL_HOST_PASSWORD = os.getenv("EMAIL_PASSWORD", "mssp.0Q0rSwb.zr6ke4n2k3e4on12.aHwJqnI") +EMAIL_HOST = "smtp.mailersend.net" +EMAIL_PORT = 2525 +EMAIL_HOST_USER = "MS_lhygCJ@test-65qngkd8nx3lwr12.mlsender.net" +EMAIL_HOST_PASSWORD = "mssp.0Q0rSwb.zr6ke4n2k3e4on12.aHwJqnI" +EMAIL_USE_TLS = True +EMAIL_HOST = 'sandbox.smtp.mailtrap.io' +EMAIL_HOST_USER = '38e5179debe69a' +EMAIL_HOST_PASSWORD = 'ffa75647d01ecb' +EMAIL_PORT = '2525' # Crispy Forms Configuration diff --git a/recruitment/forms.py b/recruitment/forms.py index db7989b..ffacc67 100644 --- a/recruitment/forms.py +++ b/recruitment/forms.py @@ -377,6 +377,13 @@ class ApplicationForm(forms.ModelForm): 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): # cleaned_data = super().clean() # job = cleaned_data.get("job") @@ -720,7 +727,7 @@ class BulkInterviewTemplateForm(forms.ModelForm): if end_date and start_date and end_date < start_date: raise forms.ValidationError(_("End date must be after start date")) return end_date - + def clean_end_time(self): start_time = self.cleaned_data.get("start_time") end_time = self.cleaned_data.get("end_time") @@ -1465,7 +1472,7 @@ class CandidateEmailForm(forms.Form): f"You will receive a separate email shortly with details regarding your start date, first-day instructions, and onboarding documents.", f"We look forward to seeing you at KAAUH.", f"If you have any questions before your start date, please contact [Onboarding Contact].", - + ] elif candidate: message_parts="" @@ -1639,7 +1646,7 @@ class MessageForm(forms.ModelForm): # Validate messaging permissions if self.user and cleaned_data.get("recipient"): self._validate_messaging_permissions(cleaned_data) - + if self.cleaned_data.get('recipient')==self.user: raise forms.ValidationError(_("You cannot message yourself")) @@ -1805,6 +1812,27 @@ class PasswordResetForm(forms.Form): raise forms.ValidationError(_('New passwords do not match.')) 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): @@ -2225,7 +2253,7 @@ Location: {interview.physical_address} Room No: {interview.room_number} This is an onsite schedule. Please arrive 10 minutes early.\n\n""" - + self.fields['message'].initial = initial_message diff --git a/recruitment/services/email_service.py b/recruitment/services/email_service.py index cdbcf06..1234954 100644 --- a/recruitment/services/email_service.py +++ b/recruitment/services/email_service.py @@ -28,8 +28,9 @@ class EmailService: try: # Using EmailMessage for more control (e.g., HTML content) - + from time import sleep for recipient in recipient_list: + sleep(2) email = EmailMessage( subject=subject, body=body, @@ -46,8 +47,6 @@ class EmailService: recipient_user=User.objects.filter(email=recipient).first() 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) - - return len(recipient_list) except Exception as e: @@ -111,7 +110,7 @@ class EmailService: context=context, from_email=from_email, html_content=html_content, - + ) # Return the count of recipients if successful, or 0 if failure diff --git a/recruitment/urls.py b/recruitment/urls.py index 46e8ad8..e49764c 100644 --- a/recruitment/urls.py +++ b/recruitment/urls.py @@ -103,6 +103,7 @@ urlpatterns = [ path("persons//", views.PersonDetailView.as_view(), name="person_detail"), path("persons//update/", views.PersonUpdateView.as_view(), name="person_update"), path("persons//delete/", views.PersonDeleteView.as_view(), name="person_delete"), + path("persons//password_reset/", views.password_reset, name="password_reset"), # ======================================================================== # FORM & TEMPLATE MANAGEMENT diff --git a/recruitment/views.py b/recruitment/views.py index 4573408..353423f 100644 --- a/recruitment/views.py +++ b/recruitment/views.py @@ -280,6 +280,11 @@ class PersonDetailView(DetailView, LoginRequiredMixin, StaffRequiredMixin): template_name = "people/person_detail.html" 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): model = Person @@ -1302,11 +1307,10 @@ def delete_form_template(request, template_id): # @staff_or_candidate_required def application_submit_form(request, slug): """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: 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": person = request.user.person_profile 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." ), ) - 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) - 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: + if job.is_application_limit_reached: messages.error( request, _( "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: messages.error( request, @@ -1340,12 +1337,12 @@ def application_submit_form(request, slug): "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( request, "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 """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."}) + template = get_object_or_404(FormTemplate, slug=template_slug) job = template.job if request.method == "POST": @@ -1414,38 +1412,38 @@ def application_submit(request, template_slug): except FormField.DoesNotExist: continue try: - gpa = submission.responses.get(field__label="GPA") - if gpa and gpa.value: - gpa_str = gpa.value.replace("/", "").strip() + # gpa = submission.responses.get(field__label="GPA") + # if gpa and gpa.value: + # gpa_str = gpa.value.replace("/", "").strip() - if not re.match(r"^\d+(\.\d+)?$", gpa_str): - # --- FIX APPLIED HERE --- - return JsonResponse( - { - "success": False, - "message": _("GPA must be a numeric value."), - } - ) + # if not re.match(r"^\d+(\.\d+)?$", gpa_str): + # # --- FIX APPLIED HERE --- + # return JsonResponse( + # { + # "success": False, + # "message": _("GPA must be a numeric value."), + # } + # ) - try: - gpa_float = float(gpa_str) - except ValueError: - # --- FIX APPLIED HERE --- - return JsonResponse( - { - "success": False, - "message": _("GPA must be a numeric value."), - } - ) + # try: + # gpa_float = float(gpa_str) + # except ValueError: + # # --- FIX APPLIED HERE --- + # return JsonResponse( + # { + # "success": False, + # "message": _("GPA must be a numeric value."), + # } + # ) - if not (0.0 <= gpa_float <= 4.0): - # --- FIX APPLIED HERE --- - return JsonResponse( - { - "success": False, - "message": _("GPA must be between 0.0 and 4.0."), - } - ) + # if not (0.0 <= gpa_float <= 4.0): + # # --- FIX APPLIED HERE --- + # return JsonResponse( + # { + # "success": False, + # "message": _("GPA must be between 0.0 and 4.0."), + # } + # ) resume = submission.responses.get(field__label="Resume Upload") @@ -1456,7 +1454,7 @@ def application_submit(request, template_slug): submission.save() # time=timezone.now() person = request.user.person_profile - person.gpa = gpa.value if gpa else None + # person.gpa = gpa.value if gpa else None person.save() Application.objects.create( person=person, @@ -2990,6 +2988,22 @@ def portal_password_reset(request, pk): for error in errors: 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): """Unified portal login for agency and applicant""" diff --git a/templates/applicant/job_application_detail.html b/templates/applicant/job_application_detail.html index 5412c05..dac4a07 100644 --- a/templates/applicant/job_application_detail.html +++ b/templates/applicant/job_application_detail.html @@ -41,7 +41,7 @@ {% trans "You already applied for this position" %} {% else %} - + {% trans "Apply for this Position" %} {% endif %} @@ -220,12 +220,10 @@ {% trans "You already applied for this position" %} {% else %} - + {% trans "Apply for this Position" %} {% endif %} {% endif %} - - {% endblock content%} \ No newline at end of file diff --git a/templates/forms/form_templates_list.html b/templates/forms/form_templates_list.html index 8f9440e..035d5eb 100644 --- a/templates/forms/form_templates_list.html +++ b/templates/forms/form_templates_list.html @@ -231,7 +231,7 @@
- + @@ -286,7 +286,7 @@ {{ template.updated_at|date:"M d, Y" }} {# Share Public Link Button #} @@ -403,10 +404,11 @@

{% trans "Manage the custom application forms associated with this job posting." %}

- -
+ {% if user.is_staff and user == application.job.assigned_to or user.is_superuser %} + {% trans "Manage Job Form" %} + {% endif %} {% comment %} {% if not job.form_template %} {% trans "Create New Form Template" %} diff --git a/templates/people/person_detail.html b/templates/people/person_detail.html index 8a22e74..9f1935c 100644 --- a/templates/people/person_detail.html +++ b/templates/people/person_detail.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% load static i18n %} +{% load static i18n crispy_forms_tags %} {% block title %}{{ person.get_full_name }} - {{ block.super }}{% endblock %} @@ -536,6 +536,33 @@ {% if user.is_staff %}
+ + + + {% trans "Edit Applicant" %} @@ -543,8 +570,6 @@ {% trans "Delete" %} - -
{% endif %}
diff --git a/templates/recruitment/applicant_signup.html b/templates/recruitment/applicant_signup.html index 11dadc8..c655b8b 100644 --- a/templates/recruitment/applicant_signup.html +++ b/templates/recruitment/applicant_signup.html @@ -231,7 +231,7 @@ {# Change Stage button #} - {% if user.is_staff %} + {% if user.is_staff and user == application.job.assigned_to or user.is_superuser %} diff --git a/templates/recruitment/applications_document_review_view.html b/templates/recruitment/applications_document_review_view.html index 5993b23..144f94c 100644 --- a/templates/recruitment/applications_document_review_view.html +++ b/templates/recruitment/applications_document_review_view.html @@ -402,7 +402,7 @@ data-bs-toggle="modal" data-bs-target="#noteModal" hx-get="{% url 'application_add_note' application.slug %}" - hx-swap="outerHTML" + hx-swap="innerHTML" hx-target=".notemodal"> Add note diff --git a/templates/recruitment/applications_exam_view.html b/templates/recruitment/applications_exam_view.html index 1fffb2c..2844bab 100644 --- a/templates/recruitment/applications_exam_view.html +++ b/templates/recruitment/applications_exam_view.html @@ -329,7 +329,7 @@ data-bs-toggle="modal" data-bs-target="#noteModal" hx-get="{% url 'application_add_note' application.slug %}" - hx-swap="outerHTML" + hx-swap="innerHTML" hx-target=".notemodal"> Add note diff --git a/templates/recruitment/applications_offer_view.html b/templates/recruitment/applications_offer_view.html index 2b91ec4..612f21a 100644 --- a/templates/recruitment/applications_offer_view.html +++ b/templates/recruitment/applications_offer_view.html @@ -358,7 +358,7 @@ data-bs-toggle="modal" data-bs-target="#noteModal" hx-get="{% url 'application_add_note' application.slug %}" - hx-swap="outerHTML" + hx-swap="innerHTML" hx-target=".notemodal"> Add note diff --git a/templates/recruitment/applications_screening_view.html b/templates/recruitment/applications_screening_view.html index 1c2e8b4..fddff0a 100644 --- a/templates/recruitment/applications_screening_view.html +++ b/templates/recruitment/applications_screening_view.html @@ -383,6 +383,9 @@ {% trans "GPA" %} + + {% trans "Years of Experience" %} + {% trans "AI Score" %} @@ -426,6 +429,11 @@ {{application.person.gpa|default:"0"}} + + + {{ application.years_of_experience }} + + {% if application.is_resume_parsed %} {% if application.match_score %} @@ -473,7 +481,7 @@ data-bs-toggle="modal" data-bs-target="#noteModal" hx-get="{% url 'application_add_note' application.slug %}" - hx-swap="outerHTML" + hx-swap="innerHTML" hx-target=".notemodal"> Add note