diff --git a/NorahUniversity/__pycache__/settings.cpython-312.pyc b/NorahUniversity/__pycache__/settings.cpython-312.pyc index 8d89095..ddf3afb 100644 Binary files a/NorahUniversity/__pycache__/settings.cpython-312.pyc and b/NorahUniversity/__pycache__/settings.cpython-312.pyc differ diff --git a/NorahUniversity/__pycache__/urls.cpython-312.pyc b/NorahUniversity/__pycache__/urls.cpython-312.pyc index e44eacc..df79ce7 100644 Binary files a/NorahUniversity/__pycache__/urls.cpython-312.pyc and b/NorahUniversity/__pycache__/urls.cpython-312.pyc differ diff --git a/NorahUniversity/settings.py b/NorahUniversity/settings.py index 605ed40..c87beef 100644 --- a/NorahUniversity/settings.py +++ b/NorahUniversity/settings.py @@ -132,16 +132,24 @@ WSGI_APPLICATION = 'NorahUniversity.wsgi.application' # Database # https://docs.djangoproject.com/en/5.2/ref/settings/#databases +# DATABASES = { +# 'default': { +# 'ENGINE': 'django.db.backends.postgresql_psycopg2', +# 'NAME': 'norahuniversity', +# 'USER': 'norahuniversity', +# 'PASSWORD': 'norahuniversity', +# 'HOST': '127.0.0.1', +# 'PORT': '5432', +# } +# } + DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'NAME': 'norahuniversity', - 'USER': 'norahuniversity', - 'PASSWORD': 'norahuniversity', - 'HOST': '127.0.0.1', - 'PORT': '5432', + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', } -} +} + # Password validation # https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators diff --git a/recruitment/__pycache__/admin.cpython-312.pyc b/recruitment/__pycache__/admin.cpython-312.pyc index 07aeb8c..0b0a860 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 bba0bac..3f269ca 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 fa57734..3cb77d1 100644 Binary files a/recruitment/__pycache__/models.cpython-312.pyc and b/recruitment/__pycache__/models.cpython-312.pyc differ diff --git a/recruitment/__pycache__/urls.cpython-312.pyc b/recruitment/__pycache__/urls.cpython-312.pyc index b4efb3c..66d8262 100644 Binary files a/recruitment/__pycache__/urls.cpython-312.pyc and b/recruitment/__pycache__/urls.cpython-312.pyc differ diff --git a/recruitment/__pycache__/utils.cpython-312.pyc b/recruitment/__pycache__/utils.cpython-312.pyc index b9c315a..83c8083 100644 Binary files a/recruitment/__pycache__/utils.cpython-312.pyc and b/recruitment/__pycache__/utils.cpython-312.pyc differ diff --git a/recruitment/__pycache__/views.cpython-312.pyc b/recruitment/__pycache__/views.cpython-312.pyc index cf28cc0..1c3f983 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 a6f5a1f..188826c 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/migrations/0001_initial.py b/recruitment/migrations/0001_initial.py index 1895908..3cfbbcb 100644 --- a/recruitment/migrations/0001_initial.py +++ b/recruitment/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.2.7 on 2025-10-17 19:41 +# Generated by Django 5.2.7 on 2025-10-19 15:50 import django.core.validators import django.db.models.deletion @@ -99,8 +99,8 @@ class Migration(migrations.Migration): ('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')), ('topic', models.CharField(max_length=255, verbose_name='Topic')), - ('meeting_id', models.CharField(max_length=20, unique=True, verbose_name='Meeting ID')), - ('start_time', models.DateTimeField(verbose_name='Start Time')), + ('meeting_id', models.CharField(db_index=True, max_length=20, unique=True, verbose_name='Meeting ID')), + ('start_time', models.DateTimeField(db_index=True, verbose_name='Start Time')), ('duration', models.PositiveIntegerField(verbose_name='Duration')), ('timezone', models.CharField(max_length=50, verbose_name='Timezone')), ('join_url', models.URLField(verbose_name='Join URL')), @@ -110,7 +110,7 @@ class Migration(migrations.Migration): ('mute_upon_entry', models.BooleanField(default=False, verbose_name='Mute Upon Entry')), ('waiting_room', models.BooleanField(default=False, verbose_name='Waiting Room')), ('zoom_gateway_response', models.JSONField(blank=True, null=True, verbose_name='Zoom Gateway Response')), - ('status', models.CharField(blank=True, max_length=20, null=True, verbose_name='Status')), + ('status', models.CharField(blank=True, db_index=True, default='waiting', max_length=20, null=True, verbose_name='Status')), ], options={ 'abstract': False, @@ -142,41 +142,6 @@ class Migration(migrations.Migration): 'ordering': ['order'], }, ), - migrations.CreateModel( - name='FormSubmission', - 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')), - ('submitted_at', models.DateTimeField(auto_now_add=True)), - ('applicant_name', models.CharField(blank=True, help_text='Name of the applicant', max_length=200)), - ('applicant_email', models.EmailField(blank=True, help_text='Email of the applicant', max_length=254)), - ('submitted_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='form_submissions', to=settings.AUTH_USER_MODEL)), - ], - options={ - 'verbose_name': 'Form Submission', - 'verbose_name_plural': 'Form Submissions', - 'ordering': ['-submitted_at'], - }, - ), - migrations.CreateModel( - name='FieldResponse', - 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')), - ('value', models.JSONField(blank=True, help_text='Response value (stored as JSON)', null=True)), - ('uploaded_file', models.FileField(blank=True, null=True, upload_to='form_uploads/')), - ('field', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='responses', to='recruitment.formfield')), - ('submission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='responses', to='recruitment.formsubmission')), - ], - options={ - 'verbose_name': 'Field Response', - 'verbose_name_plural': 'Field Responses', - }, - ), migrations.CreateModel( name='FormTemplate', fields=[ @@ -195,10 +160,24 @@ class Migration(migrations.Migration): 'ordering': ['-created_at'], }, ), - migrations.AddField( - model_name='formsubmission', - name='template', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='submissions', to='recruitment.formtemplate'), + migrations.CreateModel( + name='FormSubmission', + 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')), + ('submitted_at', models.DateTimeField(auto_now_add=True, db_index=True)), + ('applicant_name', models.CharField(blank=True, help_text='Name of the applicant', max_length=200)), + ('applicant_email', models.EmailField(blank=True, db_index=True, help_text='Email of the applicant', max_length=254)), + ('submitted_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='form_submissions', to=settings.AUTH_USER_MODEL)), + ('template', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='submissions', to='recruitment.formtemplate')), + ], + options={ + 'verbose_name': 'Form Submission', + 'verbose_name_plural': 'Form Submissions', + 'ordering': ['-submitted_at'], + }, ), migrations.AddField( model_name='formstage', @@ -214,7 +193,7 @@ class Migration(migrations.Migration): ('slug', django_extensions.db.fields.RandomCharField(blank=True, editable=False, length=8, unique=True, verbose_name='Slug')), ('first_name', models.CharField(max_length=255, verbose_name='First Name')), ('last_name', models.CharField(max_length=255, verbose_name='Last Name')), - ('email', models.EmailField(max_length=254, verbose_name='Email')), + ('email', models.EmailField(db_index=True, max_length=254, verbose_name='Email')), ('phone', models.CharField(max_length=20, verbose_name='Phone')), ('address', models.TextField(max_length=200, verbose_name='Address')), ('resume', models.FileField(upload_to='resumes/', verbose_name='Resume')), @@ -222,19 +201,16 @@ class Migration(migrations.Migration): ('is_potential_candidate', models.BooleanField(default=False, verbose_name='Potential Candidate')), ('parsed_summary', models.TextField(blank=True, verbose_name='Parsed Summary')), ('applied', models.BooleanField(default=False, verbose_name='Applied')), - ('stage', models.CharField(choices=[('Applied', 'Applied'), ('Exam', 'Exam'), ('Interview', 'Interview'), ('Offer', 'Offer')], default='Applied', max_length=100, verbose_name='Stage')), + ('stage', models.CharField(choices=[('Applied', 'Applied'), ('Exam', 'Exam'), ('Interview', 'Interview'), ('Offer', 'Offer')], db_index=True, default='Applied', max_length=100, verbose_name='Stage')), ('applicant_status', models.CharField(blank=True, choices=[('Applicant', 'Applicant'), ('Candidate', 'Candidate')], default='Applicant', max_length=100, null=True, verbose_name='Applicant Status')), ('exam_date', models.DateTimeField(blank=True, null=True, verbose_name='Exam Date')), ('exam_status', models.CharField(blank=True, choices=[('Passed', 'Passed'), ('Failed', 'Failed')], max_length=100, null=True, verbose_name='Exam Status')), ('interview_date', models.DateTimeField(blank=True, null=True, verbose_name='Interview Date')), - ('interview_status', models.CharField(blank=True, choices=[('Accepted', 'Accepted'), ('Rejected', 'Rejected')], max_length=100, null=True, verbose_name='Interview Status')), + ('interview_status', models.CharField(blank=True, choices=[('Passed', 'Passed'), ('Failed', 'Failed')], max_length=100, null=True, verbose_name='Interview Status')), ('offer_date', models.DateField(blank=True, null=True, verbose_name='Offer Date')), ('offer_status', models.CharField(blank=True, choices=[('Accepted', 'Accepted'), ('Rejected', 'Rejected')], max_length=100, null=True, verbose_name='Offer Status')), ('join_date', models.DateField(blank=True, null=True, verbose_name='Join Date')), - ('match_score', models.IntegerField(blank=True, null=True)), - ('strengths', models.TextField(blank=True)), - ('weaknesses', models.TextField(blank=True)), - ('criteria_checklist', models.JSONField(blank=True, default=dict)), + ('ai_analysis_data', models.JSONField(default=dict, help_text='Full JSON output from the resume scoring model.', verbose_name='AI Analysis Data')), ('submitted_by_agency', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='submitted_candidates', to='recruitment.hiringagency', verbose_name='Submitted by Agency')), ], options={ @@ -261,22 +237,23 @@ class Migration(migrations.Migration): ('benefits', django_ckeditor_5.fields.CKEditor5Field(blank=True, null=True)), ('application_url', models.URLField(blank=True, help_text='URL where candidates apply', null=True, validators=[django.core.validators.URLValidator()])), ('application_start_date', models.DateField(blank=True, null=True)), - ('application_deadline', models.DateField(blank=True, null=True)), + ('application_deadline', models.DateField(blank=True, db_index=True, null=True)), ('application_instructions', django_ckeditor_5.fields.CKEditor5Field(blank=True, null=True)), ('internal_job_id', models.CharField(editable=False, max_length=50, primary_key=True, serialize=False)), ('created_by', models.CharField(blank=True, help_text='Name of person who created this job', max_length=100)), - ('status', models.CharField(choices=[('DRAFT', 'Draft'), ('ACTIVE', 'Active'), ('CLOSED', 'Closed'), ('CANCELLED', 'Cancelled'), ('ARCHIVED', 'Archived')], default='DRAFT', max_length=20)), + ('status', models.CharField(choices=[('DRAFT', 'Draft'), ('ACTIVE', 'Active'), ('CLOSED', 'Closed'), ('CANCELLED', 'Cancelled'), ('ARCHIVED', 'Archived')], db_index=True, default='DRAFT', max_length=20)), ('hash_tags', models.CharField(blank=True, help_text='Comma-separated hashtags for linkedin post like #hiring,#jobopening', max_length=200, validators=[recruitment.validators.validate_hash_tags])), ('linkedin_post_id', models.CharField(blank=True, help_text='LinkedIn post ID after posting', max_length=200)), ('linkedin_post_url', models.URLField(blank=True, help_text='Direct URL to LinkedIn post')), ('posted_to_linkedin', models.BooleanField(default=False)), ('linkedin_post_status', models.CharField(blank=True, help_text='Status of LinkedIn posting', max_length=50)), ('linkedin_posted_at', models.DateTimeField(blank=True, null=True)), - ('published_at', models.DateTimeField(blank=True, null=True)), + ('published_at', models.DateTimeField(blank=True, db_index=True, null=True)), ('position_number', models.CharField(blank=True, help_text='University position number', max_length=50)), ('reporting_to', models.CharField(blank=True, help_text='Who this position reports to', max_length=100)), ('joining_date', models.DateField(blank=True, help_text='Desired start date', null=True)), ('open_positions', models.PositiveIntegerField(default=1, help_text='Number of open positions for this job')), + ('max_applications', models.PositiveIntegerField(blank=True, default=1000, help_text='Maximum number of applications allowed', null=True)), ('cancel_reason', models.TextField(blank=True, help_text='Reason for canceling the job posting', verbose_name='Cancel Reason')), ('cancelled_by', models.CharField(blank=True, help_text='Name of person who cancelled this job', max_length=100, verbose_name='Cancelled By')), ('cancelled_at', models.DateTimeField(blank=True, null=True)), @@ -293,24 +270,22 @@ class Migration(migrations.Migration): name='InterviewSchedule', 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')), - ('start_date', models.DateField(verbose_name='Start Date')), - ('end_date', models.DateField(verbose_name='End Date')), + ('start_date', models.DateField(db_index=True, verbose_name='Start Date')), + ('end_date', models.DateField(db_index=True, verbose_name='End Date')), ('working_days', models.JSONField(verbose_name='Working Days')), ('start_time', models.TimeField(verbose_name='Start Time')), ('end_time', models.TimeField(verbose_name='End Time')), - ('breaks', models.JSONField(blank=True, default=list, verbose_name='Break Times')), + ('break_start_time', models.TimeField(blank=True, null=True, verbose_name='Break Start Time')), + ('break_end_time', models.TimeField(blank=True, null=True, verbose_name='Break End Time')), ('interview_duration', models.PositiveIntegerField(verbose_name='Interview Duration (minutes)')), ('buffer_time', models.PositiveIntegerField(default=0, verbose_name='Buffer Time (minutes)')), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('candidates', models.ManyToManyField(related_name='interview_schedules', to='recruitment.candidate')), + ('candidates', models.ManyToManyField(blank=True, null=True, related_name='interview_schedules', to='recruitment.candidate')), ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ('job', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='interview_schedules', to='recruitment.jobposting')), ], - options={ - 'abstract': False, - }, ), migrations.AddField( model_name='formtemplate', @@ -402,9 +377,9 @@ class Migration(migrations.Migration): fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('slug', django_extensions.db.fields.RandomCharField(blank=True, editable=False, length=8, unique=True, verbose_name='Slug')), - ('interview_date', models.DateField(verbose_name='Interview Date')), + ('interview_date', models.DateField(db_index=True, verbose_name='Interview Date')), ('interview_time', models.TimeField(verbose_name='Interview Time')), - ('status', models.CharField(choices=[('scheduled', 'Scheduled'), ('confirmed', 'Confirmed'), ('cancelled', 'Cancelled'), ('completed', 'Completed')], default='scheduled', max_length=20)), + ('status', models.CharField(choices=[('scheduled', 'Scheduled'), ('confirmed', 'Confirmed'), ('cancelled', 'Cancelled'), ('completed', 'Completed')], db_index=True, default='scheduled', max_length=20)), ('created_at', models.DateTimeField(auto_now_add=True)), ('updated_at', models.DateTimeField(auto_now=True)), ('candidate', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='scheduled_interviews', to='recruitment.candidate')), @@ -412,8 +387,92 @@ class Migration(migrations.Migration): ('schedule', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='interviews', to='recruitment.interviewschedule')), ('zoom_meeting', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='interview', to='recruitment.zoommeeting')), ], + ), + migrations.CreateModel( + name='MeetingComment', + 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')), + ('content', django_ckeditor_5.fields.CKEditor5Field(verbose_name='Content')), + ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='meeting_comments', to=settings.AUTH_USER_MODEL, verbose_name='Author')), + ('meeting', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='recruitment.zoommeeting', verbose_name='Meeting')), + ], options={ - 'abstract': False, + 'verbose_name': 'Meeting Comment', + 'verbose_name_plural': 'Meeting Comments', + 'ordering': ['-created_at'], }, ), + migrations.CreateModel( + name='FieldResponse', + 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')), + ('value', models.JSONField(blank=True, help_text='Response value (stored as JSON)', null=True)), + ('uploaded_file', models.FileField(blank=True, null=True, upload_to='form_uploads/')), + ('field', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='responses', to='recruitment.formfield')), + ('submission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='responses', to='recruitment.formsubmission')), + ], + options={ + 'verbose_name': 'Field Response', + 'verbose_name_plural': 'Field Responses', + 'indexes': [models.Index(fields=['submission'], name='recruitment_submiss_474130_idx'), models.Index(fields=['field'], name='recruitment_field_i_097e5b_idx')], + }, + ), + migrations.AddIndex( + model_name='formsubmission', + index=models.Index(fields=['submitted_at'], name='recruitment_submitt_7946c8_idx'), + ), + migrations.AddIndex( + model_name='interviewschedule', + index=models.Index(fields=['start_date'], name='recruitment_start_d_15d55e_idx'), + ), + migrations.AddIndex( + model_name='interviewschedule', + index=models.Index(fields=['end_date'], name='recruitment_end_dat_aeb00e_idx'), + ), + migrations.AddIndex( + model_name='interviewschedule', + index=models.Index(fields=['created_by'], name='recruitment_created_d0bdcc_idx'), + ), + migrations.AddIndex( + model_name='formtemplate', + index=models.Index(fields=['created_at'], name='recruitment_created_c21775_idx'), + ), + migrations.AddIndex( + model_name='formtemplate', + index=models.Index(fields=['is_active'], name='recruitment_is_acti_ae5efb_idx'), + ), + migrations.AddIndex( + model_name='candidate', + index=models.Index(fields=['stage'], name='recruitment_stage_f1c6eb_idx'), + ), + migrations.AddIndex( + model_name='candidate', + index=models.Index(fields=['created_at'], name='recruitment_created_73590f_idx'), + ), + migrations.AddIndex( + model_name='jobposting', + index=models.Index(fields=['status', 'created_at', 'title'], name='recruitment_status_8b77aa_idx'), + ), + migrations.AddIndex( + model_name='jobposting', + index=models.Index(fields=['slug'], name='recruitment_slug_004045_idx'), + ), + migrations.AddIndex( + model_name='scheduledinterview', + index=models.Index(fields=['job', 'status'], name='recruitment_job_id_f09e22_idx'), + ), + migrations.AddIndex( + model_name='scheduledinterview', + index=models.Index(fields=['interview_date', 'interview_time'], name='recruitment_intervi_7f5877_idx'), + ), + migrations.AddIndex( + model_name='scheduledinterview', + index=models.Index(fields=['candidate', 'job'], name='recruitment_candida_43d5b0_idx'), + ), ] diff --git a/recruitment/migrations/0014_formtemplate_close_at_formtemplate_max_applications_and_more.py b/recruitment/migrations/0014_formtemplate_close_at_formtemplate_max_applications_and_more.py deleted file mode 100644 index ac56849..0000000 --- a/recruitment/migrations/0014_formtemplate_close_at_formtemplate_max_applications_and_more.py +++ /dev/null @@ -1,33 +0,0 @@ -# Generated by Django 5.2.6 on 2025-10-15 10:33 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0013_alter_formtemplate_created_by'), - ] - - operations = [ - migrations.AddField( - model_name='formtemplate', - name='close_at', - field=models.DateTimeField(blank=True, help_text='Date and time at which applications close', null=True), - ), - migrations.AddField( - model_name='formtemplate', - name='max_applications', - field=models.PositiveIntegerField(default=1000, help_text='Maximum number of applications allowed'), - ), - migrations.AlterField( - model_name='formtemplate', - name='created_at', - field=models.DateTimeField(auto_now_add=True), - ), - migrations.AlterField( - model_name='formtemplate', - name='updated_at', - field=models.DateTimeField(auto_now=True), - ), - ] diff --git a/recruitment/migrations/0015_remove_formtemplate_close_at_and_more.py b/recruitment/migrations/0015_remove_formtemplate_close_at_and_more.py deleted file mode 100644 index b519f91..0000000 --- a/recruitment/migrations/0015_remove_formtemplate_close_at_and_more.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 5.2.6 on 2025-10-15 10:47 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0014_formtemplate_close_at_formtemplate_max_applications_and_more'), - ] - - operations = [ - migrations.RemoveField( - model_name='formtemplate', - name='close_at', - ), - migrations.AlterField( - model_name='formtemplate', - name='created_at', - field=models.DateTimeField(auto_now_add=True, verbose_name='Created at'), - ), - migrations.AlterField( - model_name='formtemplate', - name='updated_at', - field=models.DateTimeField(auto_now=True, verbose_name='Updated at'), - ), - ] diff --git a/recruitment/migrations/0016_remove_formtemplate_max_applications_and_more.py b/recruitment/migrations/0016_remove_formtemplate_max_applications_and_more.py deleted file mode 100644 index e660370..0000000 --- a/recruitment/migrations/0016_remove_formtemplate_max_applications_and_more.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 5.2.6 on 2025-10-15 10:52 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0015_remove_formtemplate_close_at_and_more'), - ] - - operations = [ - migrations.RemoveField( - model_name='formtemplate', - name='max_applications', - ), - migrations.AddField( - model_name='jobposting', - name='max_applications', - field=models.PositiveIntegerField(default=1000, help_text='Maximum number of applications allowed'), - ), - ] diff --git a/recruitment/migrations/0017_remove_interviewschedule_breaks_and_more.py b/recruitment/migrations/0017_remove_interviewschedule_breaks_and_more.py deleted file mode 100644 index 4961d4d..0000000 --- a/recruitment/migrations/0017_remove_interviewschedule_breaks_and_more.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 5.2.6 on 2025-10-15 15:54 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0016_remove_formtemplate_max_applications_and_more'), - ] - - operations = [ - migrations.RemoveField( - model_name='interviewschedule', - name='breaks', - ), - migrations.AddField( - model_name='interviewschedule', - name='break_end', - field=models.TimeField(blank=True, null=True, verbose_name='Break End Time'), - ), - migrations.AddField( - model_name='interviewschedule', - name='break_start', - field=models.TimeField(blank=True, null=True, verbose_name='Break Start Time'), - ), - ] diff --git a/recruitment/migrations/0018_rename_break_end_interviewschedule_break_end_time_and_more.py b/recruitment/migrations/0018_rename_break_end_interviewschedule_break_end_time_and_more.py deleted file mode 100644 index 16b2ed2..0000000 --- a/recruitment/migrations/0018_rename_break_end_interviewschedule_break_end_time_and_more.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 5.2.6 on 2025-10-15 15:55 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0017_remove_interviewschedule_breaks_and_more'), - ] - - operations = [ - migrations.RenameField( - model_name='interviewschedule', - old_name='break_end', - new_name='break_end_time', - ), - migrations.RenameField( - model_name='interviewschedule', - old_name='break_start', - new_name='break_start_time', - ), - ] diff --git a/recruitment/migrations/0019_alter_interviewschedule_candidates.py b/recruitment/migrations/0019_alter_interviewschedule_candidates.py deleted file mode 100644 index d10d068..0000000 --- a/recruitment/migrations/0019_alter_interviewschedule_candidates.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.2.6 on 2025-10-15 16:07 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0018_rename_break_end_interviewschedule_break_end_time_and_more'), - ] - - operations = [ - migrations.AlterField( - model_name='interviewschedule', - name='candidates', - field=models.ManyToManyField(blank=True, null=True, related_name='interview_schedules', to='recruitment.candidate'), - ), - ] diff --git a/recruitment/migrations/0020_alter_interviewschedule_created_at.py b/recruitment/migrations/0020_alter_interviewschedule_created_at.py deleted file mode 100644 index 3824c35..0000000 --- a/recruitment/migrations/0020_alter_interviewschedule_created_at.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.2.6 on 2025-10-15 16:09 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0019_alter_interviewschedule_candidates'), - ] - - operations = [ - migrations.AlterField( - model_name='interviewschedule', - name='created_at', - field=models.DateTimeField(auto_now_add=True, verbose_name='Created at'), - ), - ] diff --git a/recruitment/migrations/0021_meetingcomment.py b/recruitment/migrations/0021_meetingcomment.py deleted file mode 100644 index 734d421..0000000 --- a/recruitment/migrations/0021_meetingcomment.py +++ /dev/null @@ -1,35 +0,0 @@ -# Generated by Django 5.2.6 on 2025-10-16 13:52 - -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', '0020_alter_interviewschedule_created_at'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name='MeetingComment', - 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')), - ('content', django_ckeditor_5.fields.CKEditor5Field(verbose_name='Content')), - ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='meeting_comments', to=settings.AUTH_USER_MODEL, verbose_name='Author')), - ('meeting', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='recruitment.zoommeeting', verbose_name='Meeting')), - ], - options={ - 'verbose_name': 'Meeting Comment', - 'verbose_name_plural': 'Meeting Comments', - 'ordering': ['-created_at'], - }, - ), - ] diff --git a/recruitment/migrations/0022_candidate_resume_parsed_category.py b/recruitment/migrations/0022_candidate_resume_parsed_category.py deleted file mode 100644 index f767301..0000000 --- a/recruitment/migrations/0022_candidate_resume_parsed_category.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.2.6 on 2025-10-16 19:39 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0021_meetingcomment'), - ] - - operations = [ - migrations.AddField( - model_name='candidate', - name='resume_parsed_category', - field=models.TextField(blank=True, verbose_name='Resume Parsed Category'), - ), - ] diff --git a/recruitment/migrations/0023_alter_jobposting_max_applications.py b/recruitment/migrations/0023_alter_jobposting_max_applications.py deleted file mode 100644 index 94104e1..0000000 --- a/recruitment/migrations/0023_alter_jobposting_max_applications.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.2.6 on 2025-10-16 19:47 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0022_candidate_resume_parsed_category'), - ] - - operations = [ - migrations.AlterField( - model_name='jobposting', - name='max_applications', - field=models.PositiveIntegerField(blank=True, default=1000, help_text='Maximum number of applications allowed', null=True), - ), - ] diff --git a/recruitment/migrations/0024_alter_zoommeeting_status.py b/recruitment/migrations/0024_alter_zoommeeting_status.py deleted file mode 100644 index 9a69166..0000000 --- a/recruitment/migrations/0024_alter_zoommeeting_status.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.2.4 on 2025-10-17 20:58 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0023_alter_jobposting_max_applications'), - ] - - operations = [ - migrations.AlterField( - model_name='zoommeeting', - name='status', - field=models.CharField(blank=True, default='waiting', max_length=20, null=True, verbose_name='Status'), - ), - ] diff --git a/recruitment/migrations/0025_candidate_recommendation.py b/recruitment/migrations/0025_candidate_recommendation.py deleted file mode 100644 index 061dbf7..0000000 --- a/recruitment/migrations/0025_candidate_recommendation.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.2.4 on 2025-10-17 21:35 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0024_alter_zoommeeting_status'), - ] - - operations = [ - migrations.AddField( - model_name='candidate', - name='recommendation', - field=models.TextField(blank=True, verbose_name='Recommendation'), - ), - ] diff --git a/recruitment/migrations/0026_remove_candidate_resume_parsed_category_and_more.py b/recruitment/migrations/0026_remove_candidate_resume_parsed_category_and_more.py deleted file mode 100644 index 1a06a70..0000000 --- a/recruitment/migrations/0026_remove_candidate_resume_parsed_category_and_more.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 5.2.4 on 2025-10-17 21:50 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0025_candidate_recommendation'), - ] - - operations = [ - migrations.RemoveField( - model_name='candidate', - name='resume_parsed_category', - ), - migrations.AddField( - model_name='candidate', - name='major_category_name', - field=models.TextField(blank=True, verbose_name='Major Category Name'), - ), - ] diff --git a/recruitment/migrations/0027_alter_candidate_email_and_more.py b/recruitment/migrations/0027_alter_candidate_email_and_more.py deleted file mode 100644 index af3b674..0000000 --- a/recruitment/migrations/0027_alter_candidate_email_and_more.py +++ /dev/null @@ -1,159 +0,0 @@ -# Generated by Django 5.2.6 on 2025-10-18 17:51 - -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0026_remove_candidate_resume_parsed_category_and_more'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.AlterField( - model_name='candidate', - name='email', - field=models.EmailField(db_index=True, max_length=254, verbose_name='Email'), - ), - migrations.AlterField( - model_name='candidate', - name='major_category_name', - field=models.TextField(blank=True, db_index=True, verbose_name='Major Category Name'), - ), - migrations.AlterField( - model_name='candidate', - name='match_score', - field=models.IntegerField(blank=True, db_index=True, null=True), - ), - migrations.AlterField( - model_name='candidate', - name='stage', - field=models.CharField(choices=[('Applied', 'Applied'), ('Exam', 'Exam'), ('Interview', 'Interview'), ('Offer', 'Offer')], db_index=True, default='Applied', max_length=100, verbose_name='Stage'), - ), - migrations.AlterField( - model_name='formsubmission', - name='applicant_email', - field=models.EmailField(blank=True, db_index=True, help_text='Email of the applicant', max_length=254), - ), - migrations.AlterField( - model_name='formsubmission', - name='submitted_at', - field=models.DateTimeField(auto_now_add=True, db_index=True), - ), - migrations.AlterField( - model_name='interviewschedule', - name='end_date', - field=models.DateField(db_index=True, verbose_name='End Date'), - ), - migrations.AlterField( - model_name='interviewschedule', - name='start_date', - field=models.DateField(db_index=True, verbose_name='Start Date'), - ), - migrations.AlterField( - model_name='jobposting', - name='application_deadline', - field=models.DateField(blank=True, db_index=True, null=True), - ), - migrations.AlterField( - model_name='jobposting', - name='published_at', - field=models.DateTimeField(blank=True, db_index=True, null=True), - ), - migrations.AlterField( - model_name='jobposting', - name='status', - field=models.CharField(choices=[('DRAFT', 'Draft'), ('ACTIVE', 'Active'), ('CLOSED', 'Closed'), ('CANCELLED', 'Cancelled'), ('ARCHIVED', 'Archived')], db_index=True, default='DRAFT', max_length=20), - ), - migrations.AlterField( - model_name='scheduledinterview', - name='interview_date', - field=models.DateField(db_index=True, verbose_name='Interview Date'), - ), - migrations.AlterField( - model_name='scheduledinterview', - name='status', - field=models.CharField(choices=[('scheduled', 'Scheduled'), ('confirmed', 'Confirmed'), ('cancelled', 'Cancelled'), ('completed', 'Completed')], db_index=True, default='scheduled', max_length=20), - ), - migrations.AlterField( - model_name='zoommeeting', - name='meeting_id', - field=models.CharField(db_index=True, max_length=20, unique=True, verbose_name='Meeting ID'), - ), - migrations.AlterField( - model_name='zoommeeting', - name='start_time', - field=models.DateTimeField(db_index=True, verbose_name='Start Time'), - ), - migrations.AlterField( - model_name='zoommeeting', - name='status', - field=models.CharField(blank=True, db_index=True, default='waiting', max_length=20, null=True, verbose_name='Status'), - ), - migrations.AddIndex( - model_name='candidate', - index=models.Index(fields=['job', 'stage'], name='recruitment_job_id_766dbe_idx'), - ), - migrations.AddIndex( - model_name='candidate', - index=models.Index(fields=['job', 'stage', 'match_score'], name='recruitment_job_id_bd6512_idx'), - ), - migrations.AddIndex( - model_name='candidate', - index=models.Index(fields=['created_at'], name='recruitment_created_73590f_idx'), - ), - migrations.AddIndex( - model_name='fieldresponse', - index=models.Index(fields=['submission'], name='recruitment_submiss_474130_idx'), - ), - migrations.AddIndex( - model_name='fieldresponse', - index=models.Index(fields=['field'], name='recruitment_field_i_097e5b_idx'), - ), - migrations.AddIndex( - model_name='formsubmission', - index=models.Index(fields=['submitted_at'], name='recruitment_submitt_7946c8_idx'), - ), - migrations.AddIndex( - model_name='formtemplate', - index=models.Index(fields=['created_at'], name='recruitment_created_c21775_idx'), - ), - migrations.AddIndex( - model_name='formtemplate', - index=models.Index(fields=['is_active'], name='recruitment_is_acti_ae5efb_idx'), - ), - migrations.AddIndex( - model_name='interviewschedule', - index=models.Index(fields=['start_date'], name='recruitment_start_d_15d55e_idx'), - ), - migrations.AddIndex( - model_name='interviewschedule', - index=models.Index(fields=['end_date'], name='recruitment_end_dat_aeb00e_idx'), - ), - migrations.AddIndex( - model_name='interviewschedule', - index=models.Index(fields=['created_by'], name='recruitment_created_d0bdcc_idx'), - ), - migrations.AddIndex( - model_name='jobposting', - index=models.Index(fields=['status', 'created_at'], name='recruitment_status_42c036_idx'), - ), - migrations.AddIndex( - model_name='jobposting', - index=models.Index(fields=['slug'], name='recruitment_slug_004045_idx'), - ), - migrations.AddIndex( - model_name='scheduledinterview', - index=models.Index(fields=['job', 'status'], name='recruitment_job_id_f09e22_idx'), - ), - migrations.AddIndex( - model_name='scheduledinterview', - index=models.Index(fields=['interview_date', 'interview_time'], name='recruitment_intervi_7f5877_idx'), - ), - migrations.AddIndex( - model_name='scheduledinterview', - index=models.Index(fields=['candidate', 'job'], name='recruitment_candida_43d5b0_idx'), - ), - ] diff --git a/recruitment/migrations/0028_alter_candidate_interview_status.py b/recruitment/migrations/0028_alter_candidate_interview_status.py deleted file mode 100644 index f9b6c05..0000000 --- a/recruitment/migrations/0028_alter_candidate_interview_status.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.2.4 on 2025-10-18 21:54 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0027_alter_candidate_email_and_more'), - ] - - operations = [ - migrations.AlterField( - model_name='candidate', - name='interview_status', - field=models.CharField(blank=True, choices=[('Passed', 'Passed'), ('Failed', 'Failed')], max_length=100, null=True, verbose_name='Interview Status'), - ), - ] diff --git a/recruitment/migrations/0029_remove_candidate_recruitment_job_id_766dbe_idx_and_more.py b/recruitment/migrations/0029_remove_candidate_recruitment_job_id_766dbe_idx_and_more.py deleted file mode 100644 index 1ab4af2..0000000 --- a/recruitment/migrations/0029_remove_candidate_recruitment_job_id_766dbe_idx_and_more.py +++ /dev/null @@ -1,62 +0,0 @@ -# Generated by Django 5.2.4 on 2025-10-19 10:26 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0028_alter_candidate_interview_status'), - ] - - operations = [ - migrations.RemoveIndex( - model_name='candidate', - name='recruitment_job_id_766dbe_idx', - ), - migrations.RemoveIndex( - model_name='candidate', - name='recruitment_job_id_bd6512_idx', - ), - migrations.RemoveIndex( - model_name='jobposting', - name='recruitment_status_42c036_idx', - ), - migrations.RemoveField( - model_name='candidate', - name='criteria_checklist', - ), - migrations.RemoveField( - model_name='candidate', - name='major_category_name', - ), - migrations.RemoveField( - model_name='candidate', - name='match_score', - ), - migrations.RemoveField( - model_name='candidate', - name='recommendation', - ), - migrations.RemoveField( - model_name='candidate', - name='strengths', - ), - migrations.RemoveField( - model_name='candidate', - name='weaknesses', - ), - migrations.AddField( - model_name='candidate', - name='ai_analysis_data', - field=models.JSONField(default=dict, help_text='Full JSON output from the resume scoring model.', verbose_name='AI Analysis Data'), - ), - migrations.AddIndex( - model_name='candidate', - index=models.Index(fields=['stage'], name='recruitment_stage_f1c6eb_idx'), - ), - migrations.AddIndex( - model_name='jobposting', - index=models.Index(fields=['status', 'created_at', 'title'], name='recruitment_status_8b77aa_idx'), - ), - ] diff --git a/recruitment/migrations/0030_alter_candidate_options.py b/recruitment/migrations/0030_alter_candidate_options.py deleted file mode 100644 index eff8232..0000000 --- a/recruitment/migrations/0030_alter_candidate_options.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.2.6 on 2025-10-19 13:39 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0029_remove_candidate_recruitment_job_id_766dbe_idx_and_more'), - ] - - operations = [ - migrations.AlterModelOptions( - name='candidate', - options={'ordering': ['-ai_analysis_data__match_score', '-created_at'], 'verbose_name': 'Candidate', 'verbose_name_plural': 'Candidates'}, - ), - ] diff --git a/recruitment/migrations/0031_alter_candidate_options.py b/recruitment/migrations/0031_alter_candidate_options.py deleted file mode 100644 index ffdc406..0000000 --- a/recruitment/migrations/0031_alter_candidate_options.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.2.6 on 2025-10-19 13:43 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0030_alter_candidate_options'), - ] - - operations = [ - migrations.AlterModelOptions( - name='candidate', - options={'verbose_name': 'Candidate', 'verbose_name_plural': 'Candidates'}, - ), - ] diff --git a/recruitment/templatetags/__pycache__/form_filters.cpython-312.pyc b/recruitment/templatetags/__pycache__/form_filters.cpython-312.pyc index d53ed79..479f42f 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/views.py b/recruitment/views.py index cf030aa..bdfb538 100644 --- a/recruitment/views.py +++ b/recruitment/views.py @@ -752,6 +752,15 @@ def form_wizard_view(request, template_id): """Display the form as a step-by-step wizard""" template = get_object_or_404(FormTemplate, pk=template_id, is_active=True) job_id = template.job.internal_job_id + job=template.job + is_limit_exceeded=job.is_application_limit_reached + if is_limit_exceeded: + messages.error( + request, + 'Application limit reached: This job is no longer accepting new applications. Please explore other available positions.' + ) + return redirect('job_detail_candidate',slug=job.slug) + return render( request, "forms/form_wizard.html", @@ -763,6 +772,8 @@ def form_wizard_view(request, template_id): def submit_form(request, template_id): """Handle form submission""" template = get_object_or_404(FormTemplate, id=template_id) + + if request.method == "POST": try: with transaction.atomic(): @@ -829,7 +840,7 @@ def submit_form(request, template_id): job=submission.template.job, ) - return redirect('application_success') + return redirect('application_success',slug=job.slug) except Exception as e: logger.error(f"Candidate creation failed,{e}") @@ -2086,10 +2097,10 @@ def set_staff_password(request,pk): -@login_required -def user_detail(requests,pk): - user=get_object_or_404(User,pk=pk) - return render(requests,'user/profile.html') +# @login_required +# def user_detail(requests,pk): +# user=get_object_or_404(User,pk=pk) +# return render(requests,'user/profile.html') @csrf_exempt @@ -2173,3 +2184,11 @@ def edit_meeting_comment(request, slug, comment_id): return redirect('meeting_details', slug=slug) else: form = MeetingCommentForm(instance=comment) + + +def delete_meeting_comment(request): + pass + + +def set_meeting_candidate(request): + pass \ No newline at end of file diff --git a/templates/account/password_reset_done.html b/templates/account/password_reset_done.html new file mode 100644 index 0000000..ca26bef --- /dev/null +++ b/templates/account/password_reset_done.html @@ -0,0 +1,187 @@ +{% load static i18n %} + + + + + + {% trans "Password Reset Sent" %} - KAAUH ATS + + + + {% get_current_language as LANGUAGE_CODE %} + + + + + +
+ +
+
+

+ +
+
{% trans "جامعة الأميرة نورة بنت عبدالرحمن الأكاديمية" %}
+
{% trans "ومستشفى الملك عبدالله بن عبدالعزيز التخصصي" %}
+
{% trans "Princess Nourah bint Abdulrahman University" %}
+
{% trans "King Abdullah bin Abdulaziz University Hospital" %}
+
+
+

+ Powered By TENHAL | تنحل +
+
+ +
+ +
+ +

{% trans "Password Reset Sent" %}

+ +
+ +
+ +
+ +

+ {% blocktrans %} + We've **sent an email** to the address you provided with instructions on how to reset your password. + {% endblocktrans %} +

+ + + + {# Button to return to the login page #} + +
+
+ +
+
+ + + + + \ No newline at end of file diff --git a/templates/account/password_reset_from_key.html b/templates/account/password_reset_from_key.html new file mode 100644 index 0000000..2a208a9 --- /dev/null +++ b/templates/account/password_reset_from_key.html @@ -0,0 +1,237 @@ +{% load static i18n %} + + + + + + {% trans "Set New Password" %} - KAAUH ATS + + + + {% get_current_language as LANGUAGE_CODE %} + + + + + +
+ +
+
+

+ +
+
{% trans "جامعة الأميرة نورة بنت عبدالرحمن الأكاديمية" %}
+
{% trans "ومستشفى الملك عبدالله بن عبدالعزيز التخصصي" %}
+
{% trans "Princess Nourah bint Abdulrahman University" %}
+
{% trans "King Abdullah bin Abdulaziz University Hospital" %}
+
+
+

+ Powered By TENHAL | تنحل +
+
+ +
+ +
+ +

{% trans "Set New Password" %}

+ +
+ + {% if form %} +

+ {% trans 'Please enter your new password below. You can then log in.' %} +

+ +
+ {% csrf_token %} + + {# Display any general form errors #} + {% if form.non_field_errors %} + + {% endif %} + + {# Password 1 Field #} +
+ + + {% if form.password.errors %} +
+ {{ form.password.errors }} +
+ {% endif %} + + +
+ + {# Password 2 Field #} +
+ + + {% if form.password2.errors %} +
+ {{ form.password2.errors }} +
+ {% endif %} + + +
+ + {# Hidden fields, if any (like uidb64, token/key) #} + {{ form.uid }} + {{ form.token }} + + {# Submit Button #} + +
+ + {% else %} + {# Message when the reset key is invalid or expired #} +

{% trans "Password Reset Failed" %}

+

+ {% trans "The password reset link is invalid or has expired." %} +

+ + {% endif %} + + +
+
+ +
+
+ + + + + \ No newline at end of file diff --git a/templates/base.html b/templates/base.html index a3a4293..fb30362 100644 --- a/templates/base.html +++ b/templates/base.html @@ -56,7 +56,7 @@ {% trans 'kaauh logo green bg' %} - + {% trans 'kaauh logo green bg' %} @@ -179,7 +179,7 @@ class="dropdown-item py-2 px-4 text-danger d-flex align-items-center border-0 bg-transparent text-start" aria-label="{% trans 'Sign out' %}" > - + {% trans "Sign Out" %} @@ -221,7 +221,7 @@ - - {% endcomment %} + {% comment %}
  • {% trans "All Candidates" %}
  • {% trans "New Candidates" %}
  • - + {% endcomment %} diff --git a/templates/forms/job_detail_candidate.html b/templates/forms/job_detail_candidate.html index 7bb8472..0ecccf1 100644 --- a/templates/forms/job_detail_candidate.html +++ b/templates/forms/job_detail_candidate.html @@ -8,6 +8,28 @@ + + {# ================================================= #} + {# DJANGO MESSAGE BLOCK - Placed directly below the main navbar #} + {# ================================================= #} + {% if messages %} +
    +
    + {# Using responsive columns to center the message content, similar to your form structure #} +
    + {% for message in messages %} + {# Use 'alert-{{ message.tags }}' to apply Bootstrap styling based on Django's tag (success, error/danger, info, warning) #} + + {% endfor %} +
    +
    +
    + {% endif %} + {# ================================================= #} +
    @@ -85,4 +107,6 @@ {% endif %}
    + + {% endblock content%} \ No newline at end of file diff --git a/templates/forms/partials/candidate_facing_base.html b/templates/forms/partials/candidate_facing_base.html index c171571..65b328b 100644 --- a/templates/forms/partials/candidate_facing_base.html +++ b/templates/forms/partials/candidate_facing_base.html @@ -9,7 +9,7 @@ {% translate "Application Form" %} - + {% comment %} Load the correct Bootstrap CSS file for RTL/LTR {% endcomment %} {% if LANGUAGE_CODE == 'ar' %} @@ -27,13 +27,13 @@ --kaauh-teal-dark: #004a53; --success: #198754; --danger: #dc3545; - --light-bg: #f8f9fa; + --light-bg: #f8f9fa; --gray-text: #6c757d; --kaauh-border: #eaeff3; /* Added for dropdown styling */ - + /* CALCULATED STICKY HEIGHTS */ - --navbar-height: 56px; - --navbar-gap: 16px; + --navbar-height: 56px; + --navbar-gap: 16px; --sticky-navbar-total-height: 128px; } @@ -42,7 +42,7 @@ background-color: #f0f0f5; /* Light gray background for contrast */ padding-top: 0; } - + .btn-main-action { background-color: var(--kaauh-teal); color: white; @@ -60,7 +60,38 @@ .bg-kaauh-teal-dark { background-color: var(--kaauh-teal-dark) !important; } - + + /* ---------------------------------------------------------------------- */ + /* NEW: MESSAGES STYLING */ + /* ---------------------------------------------------------------------- */ + .message-container { + /* Position right below the sticky navbar (56px) with a small top margin */ + margin-top: calc(var(--navbar-height) + 10px); + } + .alert { + padding: 0.75rem 1.25rem; + border-radius: 0.5rem; + font-weight: 500; + box-shadow: 0 4px 8px rgba(0,0,0,0.08); + margin-bottom: 0; /* Handled by container margin */ + border-left: 5px solid; /* Feature highlight */ + } + .alert-success { + color: var(--success); + background-color: #d1e7dd; + border-color: var(--success); + } + .alert-error, .alert-danger { + color: var(--danger); + background-color: #f8d7da; + border-color: var(--danger); + } + .alert-info { + color: var(--kaauh-teal-dark); + background-color: #cff4fc; + border-color: var(--kaauh-teal); + } + /* ---------------------------------------------------------------------- */ /* LANGUAGE TOGGLE STYLES (COPIED FROM MAIN LAYOUT) */ /* ---------------------------------------------------------------------- */ @@ -111,18 +142,18 @@ #topNavbar { z-index: 1040; /* Higher than the bottom bar */ } - + /* 1. Position the dark navbar below the white navbar + gap */ #bottomNavbar { /* 56px (white nav) + 16px (mb-3) = 72px */ - top: calc(var(--navbar-height) + var(--navbar-gap)); + top: calc(var(--navbar-height) + var(--navbar-gap)); z-index: 1030; } /* 2. Pushes the main content down so it's not hidden under the navbars */ .main-content-area { /* Total Sticky Height (128px) + Extra Margin (12px) = 140px */ - margin-top: calc(var(--sticky-navbar-total-height) + 12px); + margin-top: calc(var(--sticky-navbar-total-height) + 12px); } /* 3. Positions the sticky sidebar correctly */ @@ -137,23 +168,29 @@ html[dir="rtl"] { text-align: right; } - + /* Flip Margin Utilities (m-end and m-start) */ html[dir="rtl"] .ms-auto { margin-right: auto !important; margin-left: 0 !important; } html[dir="rtl"] .me-auto { margin-left: auto !important; margin-right: 0 !important; } html[dir="rtl"] .ms-2 { margin-right: 0.5rem !important; margin-left: 0 !important; } html[dir="rtl"] .me-2 { margin-left: 0.5rem !important; margin-right: 0 !important; } html[dir="rtl"] .me-1 { margin-left: 0.25rem !important; margin-right: 0 !important; } /* For the globe icon */ - + /* Flip alignment for text-end/text-start */ html[dir="rtl"] .text-end { text-align: left !important; } html[dir="rtl"] .text-start { text-align: right !important; } + /* Flip border-left for RTL alerts */ + html[dir="rtl"] .alert { + border-right: 5px solid; + border-left: none; + } + /* ---------------------------------------------------------------------- */ /* MOBILE RESPONSIVE STYLES (Below 992px) */ /* ---------------------------------------------------------------------- */ @media (max-width: 991.98px) { - + /* Ensures dropdown items in mobile menu align correctly */ html[dir="rtl"] .navbar-collapse .dropdown-menu { text-align: right; @@ -165,12 +202,12 @@ #bottomNavbar { top: calc(var(--navbar-height) + var(--navbar-gap)); } - + .main-content-area { /* Reduced margin-top for smaller screens */ - margin-top: calc(var(--sticky-navbar-total-height) / 2); + margin-top: calc(var(--sticky-navbar-total-height) / 2); } - + /* Mobile Fixed Footer Bar for Application */ .mobile-fixed-apply-bar { position: fixed; @@ -199,7 +236,7 @@ - + + + {% block content %} {% endblock content %} diff --git a/templates/includes/easy_logs.html b/templates/includes/easy_logs.html index 284b600..babac0b 100644 --- a/templates/includes/easy_logs.html +++ b/templates/includes/easy_logs.html @@ -225,7 +225,7 @@ {% if active_tab == 'crud' %} {{ log.datetime|date:"Y-m-d H:i:s" }} - {{ log.user.get_full_name|default:log.user.username|default:"N/A" }} + {{ log.user.email|default:"N/A" }}
    @@ -193,14 +194,7 @@ {% if form.salary_range.errors %}
    {{ form.salary_range.errors }}
    {% endif %}
    - {% comment %}
    -
    - - {{ form.application_url }} - {% if form.application_url.errors %}
    {{ form.application_url.errors }}
    {% endif %} -
    {% trans "Full URL where candidates will apply" %}
    -
    -
    {% endcomment %} + {% comment %} (application_url comment removed for brevity) {% endcomment %}
    @@ -261,16 +255,16 @@
    - - {{ form.start_date }} - {% if form.start_date.errors %}
    {{ form.start_date.errors }}
    {% endif %} + + {{ form.application_start_date }} + {% if form.application_start_date.errors %}
    {{ form.application_start_date.errors }}
    {% endif %}
    - - {{ form.status }} - {% if form.status.errors %}
    {{ form.status.errors }}
    {% endif %} + + {{ form.joining_date }} + {% if form.joining_date.errors %}
    {{ form.joining_date.errors }}
    {% endif %}
    @@ -300,14 +294,21 @@ {% if form.reporting_to.errors %}
    {{ form.reporting_to.errors }}
    {% endif %} -
    +
    {{ form.open_positions }} {% if form.open_positions.errors %}
    {{ form.open_positions.errors }}
    {% endif %}
    -
    +
    +
    + + {{ form.max_applications }} + {% if form.max_applications.errors %}
    {{ form.max_applications.errors }}
    {% endif %} +
    +
    +
    {{ form.created_by }} @@ -340,7 +341,11 @@
    -{# 💡 2. Add Summernote JS Media at the end of the body #} -{{ form.media.js }} -{% endblock %} \ No newline at end of file + +{% endblock %} + +{% block customJS %} + +{{ form.media.js }} +{% endblock%} \ No newline at end of file diff --git a/templates/jobs/edit_job.html b/templates/jobs/edit_job.html index cfade4d..5e3fa74 100644 --- a/templates/jobs/edit_job.html +++ b/templates/jobs/edit_job.html @@ -1,11 +1,9 @@ {% extends "base.html" %} {% load static i18n %} -{% block title %}Edit {{ job.title }} - University ATS{% endblock %} +{% block title %}Create New Job Post - {{ block.super }}{% endblock %} {% block customCSS %} -{# 💡 1. Add Summernote CSS Media in the head #} -{{ form.media.css }} +{{form.media.css}} {% endblock %} {% block content %} -
    +

    - {# UPDATED TITLE FOR EDIT CONTEXT #} - {% trans "Edit Job Posting" %} - {% if job.title %} - {{ job.title }} {% endif %} + {% if form.instance.pk %} {% trans "Edit Job Posting" %} {% else %} {% trans "Create New Job Posting" %} {% endif %}

    @@ -151,26 +146,12 @@ {% if form.workplace_type.errors %}
    {{ form.workplace_type.errors }}
    {% endif %}
    -
    -
    - - {{ form.application_deadline }} - {% if form.application_deadline.errors %}
    {{ form.application_deadline.errors }}
    {% endif %} -
    -
    -
    -
    - - {{ form.max_applications }} - {% if form.max_applications.errors %}
    {{ form.max_applications.errors }}
    {% endif %} -
    -
    {# ================================================= #} - {# SECTION 2: JOB CONTENT (All Summernote Fields) #} + {# SECTION 2: JOB CONTENT (CKEDITOR 5 Fields) #} {# ================================================= #}
    @@ -181,7 +162,7 @@
    - {{ form.description }} + {{ form.description}} {% if form.description.errors %}
    {{ form.description.errors }}
    {% endif %}
    @@ -189,7 +170,7 @@
    - {{ form.qualifications }} + {{ form.qualifications}} {% if form.qualifications.errors %}
    {{ form.qualifications.errors }}
    {% endif %}
    @@ -213,14 +194,7 @@ {% if form.salary_range.errors %}
    {{ form.salary_range.errors }}
    {% endif %}
    - {% comment %}
    -
    - - {{ form.application_url }} - {% if form.application_url.errors %}
    {{ form.application_url.errors }}
    {% endif %} -
    {% trans "Full URL where candidates will apply" %}
    -
    -
    {% endcomment %} + {% comment %} (application_url comment removed for brevity) {% endcomment %}
    @@ -271,11 +245,26 @@ {% if form.location_country.errors %}
    {{ form.location_country.errors }}
    {% endif %}
    +
    - - {{ form.start_date }} - {% if form.start_date.errors %}
    {{ form.start_date.errors }}
    {% endif %} + + {{ form.application_deadline }} + {% if form.application_deadline.errors %}
    {{ form.application_deadline.errors }}
    {% endif %} +
    +
    +
    +
    + + {{ form.application_start_date }} + {% if form.application_start_date.errors %}
    {{ form.application_start_date.errors }}
    {% endif %} +
    +
    +
    +
    + + {{ form.joining_date }} + {% if form.joining_date.errors %}
    {{ form.joining_date.errors }}
    {% endif %}
    @@ -305,14 +294,21 @@ {% if form.reporting_to.errors %}
    {{ form.reporting_to.errors }}
    {% endif %} -
    +
    {{ form.open_positions }} {% if form.open_positions.errors %}
    {{ form.open_positions.errors }}
    {% endif %}
    -
    +
    +
    + + {{ form.max_applications }} + {% if form.max_applications.errors %}
    {{ form.max_applications.errors }}
    {% endif %} +
    +
    +
    {{ form.created_by }} @@ -335,19 +331,21 @@ {# ACTION BUTTONS #} {# ================================================= #}
    - {# UPDATED CANCEL URL for Job Detail #} - + {% trans "Cancel" %}
    -{# 💡 2. Add Summernote JS Media at the end of the body #} -{{ form.media.js }} -{% endblock %} \ No newline at end of file + +{% endblock %} + +{% block customJS %} + +{{ form.media.js }} +{% endblock%} \ No newline at end of file diff --git a/templates/jobs/job_list.html b/templates/jobs/job_list.html index 115565c..fbd0d64 100644 --- a/templates/jobs/job_list.html +++ b/templates/jobs/job_list.html @@ -81,7 +81,7 @@ /* --- TABLE ALIGNMENT AND SIZING FIXES --- */ .table { - table-layout: fixed; + table-layout: fixed; /* Ensures width calculations are respected */ width: 100%; border-collapse: collapse; } @@ -97,14 +97,28 @@ background-color: #f3f7f9; } - /* Optimized Main Table Column Widths (Total must be 100%) */ - .table th:nth-child(1) { width: 22%; } - .table th:nth-child(2) { width: 12%; } - .table th:nth-child(3) { width: 8%; } - .table th:nth-child(4) { width: 8%; } - .table th:nth-child(5) { width: 50%; } + /* + * OPTIMIZED MAIN TABLE COLUMN WIDTHS (Total must be 100%) + * -------------------------------------------------------- + * 1. Job Title/ID: 25% (Needs the most space) + * 2. Source: 10% + * 3. Max Apps: 7% + * 4. Deadline: 10% + * 5. Actions: 8% + * 6. Manage Forms: 10% + * 7. Applicants Metrics: 30% (Colspan 5) + * TOTAL: 25 + 10 + 7 + 10 + 8 + 10 + 30 = 100% + */ + .table th:nth-child(1) { width: 20%; } /* Job Title */ + .table th:nth-child(2) { width: 10%; } /* Source */ + .table th:nth-child(3) { width: 7%; } /* Max Apps */ + .table th:nth-child(4) { width: 10%; } /* Deadline */ + .table th:nth-child(5) { width: 8%; } /* Actions */ + .table th:nth-child(6) { width: 10%; } /* Manage Forms */ + /* The 7th column (Metrics) is 30% and is handled by its colspan */ - /* Candidate Management Header Row (The one with P/F) */ + + /* Candidate Management Header Row (The one with the stage names) */ .nested-metrics-row th { font-weight: 500; color: #6c757d; @@ -114,23 +128,13 @@ text-align: center; border-left: 1px solid var(--kaauh-border); } - + + /* Metrics Sub-Column Widths (7 total sub-columns, total 30%) */ + /* We have 5 main metrics: Applied, Screened, Exam, Interview, Offer. + * Let's allocate the 30% evenly: 30% / 5 = 6% per metric column. + */ .nested-metrics-row th { - width: calc(50% / 7); - } - .nested-metrics-row th[colspan="2"] { - width: calc(50% / 7 * 2); - position: relative; - } - - /* Inner P/F Headers */ - .nested-stage-metrics { - display: flex; - justify-content: space-around; - padding-top: 5px; - font-weight: 600; - color: var(--kaauh-teal-dark); - font-size: 0.7rem; + width: 6%; /* 30% / 5 metrics = 6% per metric column */ } /* Main TH for Candidate Management Header Title */ @@ -143,7 +147,7 @@ color: var(--kaauh-teal-dark); } - /* Candidate Management Data Cells (7 columns total) */ + /* Candidate Management Data Cells (5 columns total for metrics) */ .candidate-data-cell { text-align: center; vertical-align: middle; @@ -154,7 +158,8 @@ .table tbody td.candidate-data-cell:not(:first-child) { border-left: 1px solid var(--kaauh-border); } - .table tbody tr td:nth-child(5) { + /* Adds a distinctive vertical line before the metrics group (7th column) */ + .table tbody tr td:nth-child(7) { border-left: 2px solid var(--kaauh-teal); } @@ -170,7 +175,7 @@ font-size: 0.75rem; } - /* Additional CSS for Card View layout */ + /* Additional CSS for Card View layout (rest of your styles...) */ .card-view .card { height: 100%; } @@ -193,6 +198,7 @@ {% block content %}
    + {# ... (Rest of the header and filter content) ... #}

    {% trans "Job Postings" %} @@ -202,6 +208,7 @@

    + {# ... (Filter card) ... #}
    @@ -248,7 +255,6 @@ {# --- START OF JOB LIST CONTAINER --- #}
    - {# View Switcher (Contains the Card/Table buttons and JS/CSS logic) #} {% include "includes/_list_view_switcher.html" with list_id="job-list" %} {# 1. TABLE VIEW (Default Active) #} @@ -260,12 +266,12 @@ {# --- Corrected Multi-Row Header Structure --- #} - {% trans "Job Title / ID" %} - {% trans "Source" %} - {% trans "Number Of Applicants" %} - {% trans "Application Deadline" %} - {% trans "Actions" %} - {% trans "Manage Forms" %} + {% trans "Job Title / ID" %} + {% trans "Source" %} + {% trans "Max Apps" %} + {% trans "Deadline" %} + {% trans "Actions" %} + {% trans "Manage Forms" %} {% trans "Applicants Metrics" %} @@ -273,11 +279,11 @@ - {% trans "Applied" %} - {% trans "Screened" %} - {% trans "Exam" %} - {% trans "Interview" %} - {% trans "Offer" %} + {% trans "Applied" %} + {% trans "Screened" %} + {% trans "Exam" %} + {% trans "Interview" %} + {% trans "Offer" %} @@ -333,7 +339,7 @@
    - {# 2. CARD VIEW (Previously Missing) - Added Bootstrap row/col structure for layout #} + {# ... (Card View and Paginator content) ... #}
    {% for job in jobs %}
    diff --git a/templates/recruitment/candidate_list.html b/templates/recruitment/candidate_list.html index 7790d9d..b1ebf7c 100644 --- a/templates/recruitment/candidate_list.html +++ b/templates/recruitment/candidate_list.html @@ -149,59 +149,63 @@
    -
    -
    -
    - -
    -
    - {% include 'includes/search_form.html' %} -
    -
    -
    -
    - {% url 'candidate_list' as candidate_list_url %} - -
    - {% if search_query %}{% endif %} - -
    -
    - -
    - - -
    -
    - -
    -
    - - {% if job_filter or search_query %} - - {% trans "Clear" %} - - {% endif %} -
    -
    +
    +
    +
    + +
    + + {% include 'includes/search_form.html' %}
    + +
    + {% url 'candidate_list' as candidate_list_url %} + +
    + {% if search_query %}{% endif %} + + {# Filter Group #} +
    + + +
    + +
    + + +
    + + {# Buttons Group (pushed to the right/bottom) #} +
    +
    + + {% if job_filter or stage_filter or search_query %} + + {% trans "Clear" %} + + {% endif %} +
    +
    +
    +
    +
    {% if candidates %}
    {# View Switcher - list_id must match the container ID #} diff --git a/templates/user/admin_settings.html b/templates/user/admin_settings.html index dd5098c..11036ee 100644 --- a/templates/user/admin_settings.html +++ b/templates/user/admin_settings.html @@ -108,7 +108,7 @@ {% block content %} -
    +

    diff --git a/templates/user/create_staff.html b/templates/user/create_staff.html index b1ba2c9..9078193 100644 --- a/templates/user/create_staff.html +++ b/templates/user/create_staff.html @@ -29,7 +29,7 @@ {% endblock %} {% block content %} -
    +
    @@ -70,7 +70,7 @@ -
    +

    {% trans "Back to Settings" %} diff --git a/templates/user/profile.html b/templates/user/profile.html index eb057e3..b6ed26c 100644 --- a/templates/user/profile.html +++ b/templates/user/profile.html @@ -122,7 +122,7 @@

    {% trans "Personal Information" %}
    -
    + {% csrf_token %}
    diff --git a/templates/user/staff_password_create.html b/templates/user/staff_password_create.html index 11eedae..351e604 100644 --- a/templates/user/staff_password_create.html +++ b/templates/user/staff_password_create.html @@ -8,7 +8,7 @@ {% block content %} -
    +