diff --git a/.env b/.env
index b9e2bf0..8d7fbd5 100644
--- a/.env
+++ b/.env
@@ -1,3 +1,3 @@
-DB_NAME=norahuniversity
-DB_USER=norahuniversity
-DB_PASSWORD=norahuniversity
\ No newline at end of file
+DB_NAME=haikal_db
+DB_USER=faheed
+DB_PASSWORD=Faheed@215
\ No newline at end of file
diff --git a/recruitment/forms.py b/recruitment/forms.py
index 82a5b80..d08c798 100644
--- a/recruitment/forms.py
+++ b/recruitment/forms.py
@@ -897,6 +897,15 @@ class JobPostingStatusForm(forms.ModelForm):
widgets = {
"status": forms.Select(attrs={"class": "form-select"}),
}
+
+ def clean_status(self):
+ status = self.cleaned_data.get("status")
+ if status == "ACTIVE":
+ if self.instance and self.instance.pk:
+ print(self.instance.assigned_to)
+ if not self.instance.assigned_to:
+ raise ValidationError("Please assign the job posting before setting it to Active.")
+ return status
class LinkedPostContentForm(forms.ModelForm):
@@ -2096,23 +2105,19 @@ class CandidateEmailForm(forms.Form):
-
+from django.forms import HiddenInput
class MessageForm(forms.ModelForm):
"""Form for creating and editing messages between users"""
class Meta:
model = Message
- fields = ["recipient", "job", "subject", "content", "message_type"]
+ fields = ["job","recipient", "subject", "content", "message_type"]
widgets = {
"recipient": forms.Select(
attrs={"class": "form-select", "placeholder": "Select recipient","required": True,}
),
"job": forms.Select(
- attrs={"class": "form-select", "placeholder": "Select job",
- "hx-get": "/en/messages/create/",
- "hx-target": "#id_recipient",
- "hx-select": "#id_recipient",
- "hx-swap": "outerHTML",}
+ attrs={"class": "form-select", "placeholder": "Select job"}
),
"subject": forms.TextInput(
attrs={
@@ -2216,6 +2221,8 @@ class MessageForm(forms.ModelForm):
self.fields["recipient"].queryset = User.objects.filter(
user_type="staff"
).order_by("username")
+
+
def clean(self):
"""Validate message form data"""
diff --git a/recruitment/migrations/0006_emailcontent_alter_interview_details_url_note_and_more.py b/recruitment/migrations/0006_emailcontent_alter_interview_details_url_note_and_more.py
new file mode 100644
index 0000000..b76acc2
--- /dev/null
+++ b/recruitment/migrations/0006_emailcontent_alter_interview_details_url_note_and_more.py
@@ -0,0 +1,56 @@
+# Generated by Django 5.2.7 on 2025-12-02 10:28
+
+import django.db.models.deletion
+import django_ckeditor_5.fields
+import django_extensions.db.fields
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('recruitment', '0005_merge_20251202_1308'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='EmailContent',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('subject', models.CharField(max_length=255, verbose_name='Subject')),
+ ('message', django_ckeditor_5.fields.CKEditor5Field(verbose_name='Message Body')),
+ ],
+ options={
+ 'verbose_name': 'Email Content',
+ 'verbose_name_plural': 'Email Contents',
+ },
+ ),
+ migrations.AlterField(
+ model_name='interview',
+ name='details_url',
+ field=models.JSONField(blank=True, null=True),
+ ),
+ migrations.CreateModel(
+ name='Note',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')),
+ ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')),
+ ('slug', django_extensions.db.fields.RandomCharField(blank=True, editable=False, length=8, unique=True, verbose_name='Slug')),
+ ('note_type', models.CharField(choices=[('Feedback', 'Candidate Feedback'), ('Logistics', 'Logistical Note'), ('General', 'General Comment')], default='Feedback', max_length=50, verbose_name='Note Type')),
+ ('content', django_ckeditor_5.fields.CKEditor5Field(verbose_name='Content/Feedback')),
+ ('application', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notes', to='recruitment.application', verbose_name='Application')),
+ ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='interview_notes', to=settings.AUTH_USER_MODEL, verbose_name='Author')),
+ ('interview', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notes', to='recruitment.interview', verbose_name='Scheduled Interview')),
+ ],
+ options={
+ 'verbose_name': 'Interview Note',
+ 'verbose_name_plural': 'Interview Notes',
+ 'ordering': ['created_at'],
+ },
+ ),
+ migrations.DeleteModel(
+ name='InterviewNote',
+ ),
+ ]
diff --git a/recruitment/views.py b/recruitment/views.py
index ada62cb..11f9114 100644
--- a/recruitment/views.py
+++ b/recruitment/views.py
@@ -519,6 +519,7 @@ def job_detail(request, slug):
job_status = status_form.cleaned_data["status"]
form_template = job.form_template
if job_status == "ACTIVE":
+
form_template.is_active = True
form_template.save(update_fields=["is_active"])
else:
@@ -535,7 +536,9 @@ def job_detail(request, slug):
return redirect("job_detail", slug=slug)
else:
- messages.error(request, "Failed to update status due to validation errors.")
+ error_messages = status_form.errors.get('status', [])
+ formatted_errors = "
".join(error_messages)
+ messages.error(request, f"{formatted_errors}")
# --- 2. Quality Metrics (JSON Aggregation) ---
@@ -607,10 +610,13 @@ def job_detail(request, slug):
if avg_t_in_exam_duration
else 0
)
-
+
category_data = (
- applications.filter(ai_analysis_data__analysis_data_en__category__isnull=False)
- .values("ai_analysis_data__analysis_data_en__category")
+ applications.filter(
+ ai_analysis_data__analysis_data_en__category__isnull=False
+ ).exclude(
+ ai_analysis_data__analysis_data_en__category__exact=None
+ ).values("ai_analysis_data__analysis_data_en__category")
.annotate(
application_count=Count("id"),
category=Cast(
@@ -619,6 +625,7 @@ def job_detail(request, slug):
)
.order_by("ai_analysis_data__analysis_data_en__category")
)
+
# Prepare data for Chart.js
categories = [item["category"] for item in category_data]
applications_count = [item["application_count"] for item in category_data]
@@ -4688,7 +4695,7 @@ def message_detail(request, message_id):
@login_required
def message_create(request):
"""Create a new message"""
- from .email_service import EmailService
+ from .email_service import EmailService
if request.method == "POST":
form = MessageForm(request.user, request.POST)
@@ -4699,10 +4706,7 @@ def message_create(request):
# Send email if message_type is 'email' and recipient has email
if message.recipient and message.recipient.email:
-
try:
-
-
email_result = async_task('recruitment.tasks._task_send_individual_email',
subject=message.subject,
body_message=message.content,
@@ -4730,9 +4734,35 @@ def message_create(request):
messages.error(request, "Please correct the errors below.")
else:
-
form = MessageForm(request.user)
+
+ form.fields["job"].widget.attrs.update({"hx-get": "/en/messages/create/",
+ "hx-target": "#id_recipient",
+ "hx-select": "#id_recipient",
+ "hx-swap": "outerHTML",})
+ if request.user.user_type == "staff":
+ job_id = request.GET.get("job")
+ if job_id:
+ job = get_object_or_404(JobPosting, id=job_id)
+ applications=job.applications.all()
+ applicant_users = User.objects.filter(person_profile__in=applications.values_list('person', flat=True))
+ agency_users = User.objects.filter(id__in=AgencyJobAssignment.objects.filter(job=job).values_list('agency__user', flat=True))
+ form.fields["recipient"].queryset = applicant_users | agency_users
+
+ # form.fields["recipient"].queryset = User.objects.filter(person_profile__)
+ else:
+
+ form.fields['recipient'].widget = HiddenInput()
+ if request.method == "GET" and "HX-Request" in request.headers and request.user.user_type in ["candidate","agency"]:
+ print()
+ job_id = request.GET.get("job")
+ if job_id:
+ job = get_object_or_404(JobPosting, id=job_id)
+ form.fields["recipient"].queryset = User.objects.filter(id=job.assigned_to.id)
+ form.fields["recipient"].initial = job.assigned_to
+
+
context = {
"form": form,
}
diff --git a/templates/messages/application_message_form.html b/templates/messages/application_message_form.html
index cf9f760..0dc122d 100644
--- a/templates/messages/application_message_form.html
+++ b/templates/messages/application_message_form.html
@@ -1,5 +1,5 @@
{% extends "portal_base.html" %}
-{% load static %}
+{% load static crispy_forms_tags %}
{% load i18n %}
{% block title %}{% if form.instance.pk %}{% trans "Reply to Message" %}{% else %}{% trans "Compose Message" %}{% endif %}{% endblock %}
@@ -11,23 +11,23 @@