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 68bc01e..d093f67 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..44e9fbd 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..70cde35 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 d4d3532..7cb37cc 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 a038f86..1852081 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" ) 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 023a178..82a89d1 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 a4da922..3fb5839 100644 --- a/recruitment/views.py +++ b/recruitment/views.py @@ -276,17 +276,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': @@ -308,8 +310,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, @@ -338,6 +340,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: 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 e33694a..f5abfc4 100644 --- a/templates/base.html +++ b/templates/base.html @@ -21,8 +21,16 @@ --kaauh-light-bg: #f9fbfd; --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 %}
- {% comment %}
-
-

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

-
-
-

- ©{% trans "Powered by Tenhal" %}
-

-
-
{% endcomment %} - {% load i18n static %} - -