diff --git a/NorahUniversity/__pycache__/settings.cpython-312.pyc b/NorahUniversity/__pycache__/settings.cpython-312.pyc index a9fd26e..e8aace0 100644 Binary files a/NorahUniversity/__pycache__/settings.cpython-312.pyc and b/NorahUniversity/__pycache__/settings.cpython-312.pyc differ diff --git a/db.sqlite3 b/db.sqlite3 index 5a07bdf..0890a9d 100644 Binary files a/db.sqlite3 and b/db.sqlite3 differ diff --git a/recruitment/__pycache__/admin.cpython-312.pyc b/recruitment/__pycache__/admin.cpython-312.pyc index 5053f08..07aeb8c 100644 Binary files a/recruitment/__pycache__/admin.cpython-312.pyc and b/recruitment/__pycache__/admin.cpython-312.pyc differ diff --git a/recruitment/__pycache__/forms.cpython-312.pyc b/recruitment/__pycache__/forms.cpython-312.pyc index cc1d2a7..24fad7b 100644 Binary files a/recruitment/__pycache__/forms.cpython-312.pyc and b/recruitment/__pycache__/forms.cpython-312.pyc differ diff --git a/recruitment/__pycache__/models.cpython-312.pyc b/recruitment/__pycache__/models.cpython-312.pyc index dbb414c..bf44977 100644 Binary files a/recruitment/__pycache__/models.cpython-312.pyc and b/recruitment/__pycache__/models.cpython-312.pyc differ diff --git a/recruitment/__pycache__/signals.cpython-312.pyc b/recruitment/__pycache__/signals.cpython-312.pyc index 784a796..14eddf8 100644 Binary files a/recruitment/__pycache__/signals.cpython-312.pyc and b/recruitment/__pycache__/signals.cpython-312.pyc differ diff --git a/recruitment/__pycache__/urls.cpython-312.pyc b/recruitment/__pycache__/urls.cpython-312.pyc index aa24719..bca9467 100644 Binary files a/recruitment/__pycache__/urls.cpython-312.pyc and b/recruitment/__pycache__/urls.cpython-312.pyc differ diff --git a/recruitment/__pycache__/views.cpython-312.pyc b/recruitment/__pycache__/views.cpython-312.pyc index d052836..584200d 100644 Binary files a/recruitment/__pycache__/views.cpython-312.pyc and b/recruitment/__pycache__/views.cpython-312.pyc differ diff --git a/recruitment/__pycache__/views_frontend.cpython-312.pyc b/recruitment/__pycache__/views_frontend.cpython-312.pyc index 6ad7682..61b6f94 100644 Binary files a/recruitment/__pycache__/views_frontend.cpython-312.pyc and b/recruitment/__pycache__/views_frontend.cpython-312.pyc differ diff --git a/recruitment/forms.py b/recruitment/forms.py index 78e4cb3..6d8a716 100644 --- a/recruitment/forms.py +++ b/recruitment/forms.py @@ -195,9 +195,9 @@ class JobPostingForm(forms.ModelForm): fields = [ 'title', 'department', 'job_type', 'workplace_type', 'location_city', 'location_state', 'location_country', - 'description', 'qualifications', 'salary_range', 'benefits' + 'description', 'qualifications', 'salary_range', 'benefits','application_start_date' ,'application_deadline', 'application_instructions', - 'position_number', 'reporting_to', 'start_date', 'status', + 'position_number', 'reporting_to', 'joining_date', 'status', 'created_by','open_positions','hash_tags' ] widgets = { @@ -247,6 +247,10 @@ class JobPostingForm(forms.ModelForm): # 'placeholder': 'https://university.edu/careers/job123', # 'required': True # }), + 'application_start_date': forms.DateInput(attrs={ + 'class': 'form-control', + 'type': 'date' + }), 'application_deadline': forms.DateInput(attrs={ 'class': 'form-control', 'type': 'date' @@ -272,7 +276,7 @@ class JobPostingForm(forms.ModelForm): 'class': 'form-control', 'placeholder': 'Department Chair, Director, etc.' }), - 'start_date': forms.DateInput(attrs={ + 'joining_date': forms.DateInput(attrs={ 'class': 'form-control', 'type': 'date' }), @@ -333,31 +337,31 @@ class JobPostingForm(forms.ModelForm): raise forms.ValidationError('Please enter a valid URL (e.g., https://example.com)') return url - def clean(self): - """Cross-field validation""" - cleaned_data = super().clean() + # def clean(self): + # """Cross-field validation""" + # cleaned_data = super().clean() - # Validate dates - start_date = cleaned_data.get('start_date') - application_deadline = cleaned_data.get('application_deadline') + # # Validate dates + # start_date = cleaned_data.get('start_date') + # application_deadline = cleaned_data.get('application_deadline') - # Perform cross-field validation only if both fields have values - if start_date and application_deadline: - if application_deadline > start_date: - self.add_error('application_deadline', - 'The application deadline must be set BEFORE the job start date.') + # # Perform cross-field validation only if both fields have values + # if start_date and application_deadline: + # if application_deadline > start_date: + # self.add_error('application_deadline', + # 'The application deadline must be set BEFORE the job start date.') - # # Validate that if status is ACTIVE, we have required fields - # status = cleaned_data.get('status') - # if status == 'ACTIVE': - # if not cleaned_data.get('application_url'): - # self.add_error('application_url', - # 'Application URL is required for active jobs.') - # if not cleaned_data.get('description'): - # self.add_error('description', - # 'Job description is required for active jobs.') + # # # Validate that if status is ACTIVE, we have required fields + # # status = cleaned_data.get('status') + # # if status == 'ACTIVE': + # # if not cleaned_data.get('application_url'): + # # self.add_error('application_url', + # # 'Application URL is required for active jobs.') + # # if not cleaned_data.get('description'): + # # self.add_error('description', + # # 'Job description is required for active jobs.') - return cleaned_data + # return cleaned_data class JobPostingImageForm(forms.ModelForm): class Meta: diff --git a/recruitment/migrations/0002_alter_jobposting_status.py b/recruitment/migrations/0002_alter_jobposting_status.py new file mode 100644 index 0000000..d2fa6de --- /dev/null +++ b/recruitment/migrations/0002_alter_jobposting_status.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.7 on 2025-10-12 10:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('recruitment', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='jobposting', + name='status', + field=models.CharField(choices=[('DRAFT', 'Draft'), ('ACTIVE', 'Active'), ('CLOSED', 'Closed'), ('CANCELLED', 'Cancelled'), ('ARCHIVED', 'Archived')], default='DRAFT', max_length=20), + ), + ] diff --git a/recruitment/migrations/0003_rename_start_date_jobposting_joining_date_and_more.py b/recruitment/migrations/0003_rename_start_date_jobposting_joining_date_and_more.py new file mode 100644 index 0000000..36a4a08 --- /dev/null +++ b/recruitment/migrations/0003_rename_start_date_jobposting_joining_date_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 5.2.7 on 2025-10-12 13:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('recruitment', '0002_alter_jobposting_status'), + ] + + operations = [ + migrations.RenameField( + model_name='jobposting', + old_name='start_date', + new_name='joining_date', + ), + migrations.AddField( + model_name='jobposting', + name='application_start_date', + field=models.DateField(blank=True, null=True), + ), + ] diff --git a/recruitment/models.py b/recruitment/models.py index 404c8e2..b670906 100644 --- a/recruitment/models.py +++ b/recruitment/models.py @@ -98,6 +98,7 @@ class JobPosting(Base): null=True, blank=True, ) + application_start_date=models.DateField(null=True, blank=True) application_deadline = models.DateField(null=True, blank=True) application_instructions =CKEditor5Field( blank=True, null=True,config_name='extends' @@ -150,7 +151,7 @@ class JobPosting(Base): reporting_to = models.CharField( max_length=100, blank=True, help_text="Who this position reports to" ) - start_date = models.DateField(null=True, blank=True, help_text="Desired start date") + joining_date = models.DateField(null=True, blank=True, help_text="Desired start date") open_positions = models.PositiveIntegerField( default=1, help_text="Number of open positions for this job" ) @@ -228,7 +229,7 @@ class JobPosting(Base): parts.append(self.location_city) if self.location_state: parts.append(self.location_state) - if self.location_country and self.location_country != "United States": + if self.location_country: parts.append(self.location_country) return ", ".join(parts) if parts else "Not specified" diff --git a/recruitment/templatetags/__pycache__/form_filters.cpython-312.pyc b/recruitment/templatetags/__pycache__/form_filters.cpython-312.pyc index 6c22f61..d53ed79 100644 Binary files a/recruitment/templatetags/__pycache__/form_filters.cpython-312.pyc and b/recruitment/templatetags/__pycache__/form_filters.cpython-312.pyc differ diff --git a/recruitment/urls.py b/recruitment/urls.py index 024da8f..4e6a430 100644 --- a/recruitment/urls.py +++ b/recruitment/urls.py @@ -15,6 +15,7 @@ urlpatterns = [ path('jobs//', views.job_detail, name='job_detail'), path('jobs//candidate/', views.job_detail_candidate, name='job_detail_candidate'), path('jobs//candidate/application/success', views.application_success, name='application_success'), + path('careers/',views.kaauh_career,name='kaauh_career'), # LinkedIn Integration URLs diff --git a/recruitment/views.py b/recruitment/views.py index c740262..58603b2 100644 --- a/recruitment/views.py +++ b/recruitment/views.py @@ -277,17 +277,19 @@ def job_detail(request, slug): # Get all candidates for this job, ordered by most recent - candidates = job.candidates.all().order_by("-created_at") + applicants = job.candidates.all().order_by("-created_at") # Count candidates by stage for summary statistics - total_candidates = candidates.count() - applied_count = candidates.filter(stage="Applied").count() - interview_count = candidates.filter(stage="Interview").count() - offer_count = candidates.filter(stage="Offer").count() + total_applicant = applicants.count() + applied_count = applicants.filter(stage="Applied").count() + interview_count = applicants.filter(stage="Interview").count() + offer_count = applicants.filter(stage="Offer").count() status_form = JobPostingStatusForm(instance=job) image_upload_form=JobPostingImageForm(instance=job) + + # 2. Check for POST request (Status Update Submission) if request.method == 'POST': @@ -309,8 +311,8 @@ def job_detail(request, slug): context = { "job": job, - "candidates": candidates, - "total_candidates": total_candidates, + "applicants": applicants, + "total_applicants": total_applicant, "applied_count": applied_count, "interview_count": interview_count, "offer_count": offer_count, @@ -339,6 +341,16 @@ def job_image_upload(request, slug): return redirect('job_detail', slug=job.slug) +def kaauh_career(request): + active_jobs = JobPosting.objects.select_related( + 'form_template' + ).filter( + status='ACTIVE', + form_template__is_active=True + ) + + return render(request,'jobs/career.html',{'active_jobs':active_jobs}) + # job detail facing the candidate: @@ -952,6 +964,8 @@ def submit_form(request, template_id): resume=resume.get_file if resume.is_file else None, job=submission.template.job, ) + return redirect('application_success') + except Exception as e: logger.error(f"Candidate creation failed,{e}") pass diff --git a/recruitment/views_frontend.py b/recruitment/views_frontend.py index 5427f7b..f56d003 100644 --- a/recruitment/views_frontend.py +++ b/recruitment/views_frontend.py @@ -92,6 +92,8 @@ class JobCandidatesListView(LoginRequiredMixin, ListView): context_object_name = 'candidates' paginate_by = 10 + + def get_queryset(self): # Get the job by slug self.job = get_object_or_404(models.JobPosting, slug=self.kwargs['slug']) @@ -99,6 +101,11 @@ class JobCandidatesListView(LoginRequiredMixin, ListView): # Filter candidates for this specific job queryset = models.Candidate.objects.filter(job=self.job) + if self.request.GET.get('stage'): + stage=self.request.GET.get('stage') + queryset=queryset.filter(stage=stage) + + # Handle search search_query = self.request.GET.get('search', '') if search_query: diff --git a/templates/base.html b/templates/base.html index 7053ff1..8e39bac 100644 --- a/templates/base.html +++ b/templates/base.html @@ -22,7 +22,15 @@ --kaauh-border: #eaeff3; } - + /* NEW CLASS FOR WIDER CONTENT */ + .max-width-1600 { + max-width: 1600px; + margin-right: auto; + margin-left: auto; + padding-right: var(--bs-gutter-x, 0.75rem); /* Add Bootstrap padding for responsiveness */ + padding-left: var(--bs-gutter-x, 0.75rem); + } + /* === Top Bar === */ .top-bar { background-color: white; @@ -45,7 +53,7 @@ padding: 0.25rem 0.5rem; } .top-bar .logo-container img { - height: 40px; + height: 60px; object-fit: contain; } @@ -68,6 +76,10 @@ background-color: var(--kaauh-teal) !important; box-shadow: 0 2px 6px rgba(0,0,0,0.12); } + /* Change the outer navbar container to fluid, rely on inner max-width */ + .navbar-dark > .container { + max-width: 100%; /* Override default container width */ + } .nav-link { font-weight: 500; transition: all 0.2s ease; @@ -205,7 +217,8 @@ border-radius: 8px; box-shadow: 0 2px 6px rgba(0,0,0,0.05); } - main.container { + /* The main content width is already handled by the inline style, but making it explicit here */ + main.container-fluid { min-height: calc(100vh - 200px); padding: 1.5rem 0; } @@ -235,244 +248,265 @@
-
- -
- {% comment %}
- - info@kaauh.edu.sa + {# Changed container to container-fluid and added max-width-1600 to inner div #} +
+
+ +
+ {% comment %}
+ + info@kaauh.edu.sa +
+
+ + +966 11 820 0000 +
{% endcomment %} +
+
+ {% trans 'Saudi Vision 2030' %} +
+
+
جامعة الأميرة نورة بنت عبدالرحمن الأكاديمية
+
ومستشفى الملك عبدالله بن عبدالعزيز التخصصي
+
Princess Nourah bint Abdulrahman University
+
King Abdullah bin Abdulaziz University Hospital
+
+ +
+ KAAUH Logo
-
- - +966 11 820 0000 -
{% endcomment %} -
-
- {% trans 'Saudi Vision 2030' %} - {% trans 'King Abdullah Academic University Hospital' %}
-
+
{% if messages %} {% for message in messages %}
-
-
-

- © {% now "Y" %} {% trans "King Abdullah Academic University Hospital (KAAUH)." %}
- {% trans "All rights reserved." %} -

+ + +
+
{% include 'includes/delete_modal.html' %} diff --git a/templates/forms/form_builder.html b/templates/forms/form_builder.html index 328e686..5caea5d 100644 --- a/templates/forms/form_builder.html +++ b/templates/forms/form_builder.html @@ -5,7 +5,9 @@ ATS Form Builder - Vanilla JS + + +{% endblock %} + {% block content %} -
+ +
+
-
-

Submission Details

+
- -
-
-
+
+
+
{% trans "Submission Metadata" %}
+
+
+
- Submission ID: {{ submission.id }} + + {% trans "Submission ID:" %} {{ submission.id }}
- Submitted: {{ submission.submitted_at|date:"M d, Y H:i" }} + + {% trans "Submitted:" %} {{ submission.submitted_at|date:"M d, Y H:i" }}
- Form: {{ form.name }} + + {% trans "Form:" %} {{ submission.template.name }}
{% if submission.applicant_name or submission.applicant_email %} -
+
{% if submission.applicant_name %} -
- Applicant Name: {{ submission.applicant_name }} +
+ + {% trans "Applicant Name:" %} {{ submission.applicant_name }}
{% endif %} {% if submission.applicant_email %} -
- Email: {{ submission.applicant_email }} +
+ + {% trans "Email:" %} {{ submission.applicant_email }}
{% endif %}
@@ -47,19 +200,20 @@
- -
-
-
Responses
+
+
+
{% trans "Form Responses" %}
+
+
{% with submission=submission %} {% get_all_responses_flat submission as flat_responses %} {% if flat_responses %}
- - +
+ - + {% for response in flat_responses %} {% endfor %} @@ -67,50 +221,50 @@ - + {% for response in flat_responses %} {% endfor %} - + {% for response in flat_responses %} {% endfor %} - + {% for response in flat_responses %} {% endfor %} @@ -119,43 +273,14 @@
Field Label{% trans "Field Property" %}{{ response.field_label }}
Response Value{% trans "Response Value" %} {% if response.uploaded_file %} {% elif response.value %} {% if response.field_type == 'checkbox' and response.value|length > 0 %} -
+
{% for val in response.value %} - {{ val }} + {{ val }} {% endfor %}
{% elif response.field_type == 'radio' or response.field_type == 'select' %} {{ response.value }} {% else %} -

{{ response.value|linebreaksbr }}

+

{{ response.value|linebreaksbr }}

{% endif %} {% else %} - Not provided + {% trans "Not provided" %} {% endif %}
Stage{% trans "Associated Stage" %} - {{ response.stage_name|default:"N/A" }} + {{ response.stage_name|default:"N/A" }}
Required{% trans "Field Required" %} {% if response.required %} - Yes + {% trans "Yes" %} {% else %} - No + {% trans "No" %} {% endif %}
{% else %} -
-

No responses found for this submission.

+
+ +

{% trans "No response fields were found for this submission." %}

+

{% trans "This may occur if the form template was modified or responses were cleared." %}

{% endif %} {% endwith %}
-{% endblock %} - -{% block extra_css %} - -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/templates/forms/form_template_submissions_list.html b/templates/forms/form_template_submissions_list.html index 11df1ba..f552085 100644 --- a/templates/forms/form_template_submissions_list.html +++ b/templates/forms/form_template_submissions_list.html @@ -25,7 +25,7 @@ background-color: var(--kaauh-teal); border-color: var(--kaauh-teal); color: white; - font-weight: 600; + font-weight: 900 ; padding: 0.375rem 0.75rem; border-radius: 0.5rem; transition: all 0.2s ease; @@ -251,7 +251,7 @@
{% for submission in page_obj %} -
+

{% trans "Submission" %} #{{ submission.id }}

@@ -328,7 +328,7 @@

{% trans "There are no submissions for this form template yet." %}

- + {% trans "Back to Templates" %}
diff --git a/templates/forms/form_templates_list.html b/templates/forms/form_templates_list.html index f051543..6c14e28 100644 --- a/templates/forms/form_templates_list.html +++ b/templates/forms/form_templates_list.html @@ -148,7 +148,7 @@ {% block content %}
-

+

{% trans "Form Templates" %}

-