message on exceeding the applications
This commit is contained in:
parent
a3277c53a7
commit
2f02f10c16
Binary file not shown.
Binary file not shown.
@ -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
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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'),
|
||||
),
|
||||
]
|
||||
|
||||
@ -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),
|
||||
),
|
||||
]
|
||||
@ -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'),
|
||||
),
|
||||
]
|
||||
@ -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'),
|
||||
),
|
||||
]
|
||||
@ -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'),
|
||||
),
|
||||
]
|
||||
@ -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',
|
||||
),
|
||||
]
|
||||
@ -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'),
|
||||
),
|
||||
]
|
||||
@ -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'),
|
||||
),
|
||||
]
|
||||
@ -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'],
|
||||
},
|
||||
),
|
||||
]
|
||||
@ -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'),
|
||||
),
|
||||
]
|
||||
@ -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),
|
||||
),
|
||||
]
|
||||
@ -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'),
|
||||
),
|
||||
]
|
||||
@ -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'),
|
||||
),
|
||||
]
|
||||
@ -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'),
|
||||
),
|
||||
]
|
||||
@ -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'),
|
||||
),
|
||||
]
|
||||
@ -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'),
|
||||
),
|
||||
]
|
||||
@ -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'),
|
||||
),
|
||||
]
|
||||
@ -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'},
|
||||
),
|
||||
]
|
||||
@ -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'},
|
||||
),
|
||||
]
|
||||
Binary file not shown.
@ -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
|
||||
187
templates/account/password_reset_done.html
Normal file
187
templates/account/password_reset_done.html
Normal file
@ -0,0 +1,187 @@
|
||||
{% load static i18n %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% trans "Password Reset Sent" %} - KAAUH ATS</title>
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
|
||||
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
|
||||
<style>
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* CUSTOM TEAL THEME OVERRIDES FOR BOOTSTRAP (Copied from provided login page) */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
:root {
|
||||
/* Define TEAL as the primary color for Bootstrap overrides */
|
||||
--bs-primary: #00636e; /* Dark Teal */
|
||||
--bs-primary-rgb: 0, 99, 110;
|
||||
--bs-primary-light: #007a88; /* Lighter Teal for hover */
|
||||
|
||||
/* Background and Text Colors */
|
||||
--bs-body-bg: #f8f9fa; /* Light gray background */
|
||||
--bs-body-color: #212529; /* Dark text */
|
||||
|
||||
/* Utility colors */
|
||||
--bs-border-color: #dee2e6; /* Bootstrap default border */
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
background-color: var(--bs-body-bg);
|
||||
}
|
||||
|
||||
/* Custom Left Panel (Replicating the original look) */
|
||||
.left-panel {
|
||||
flex: 1;
|
||||
/* NOTE: Static image URL is included here. Ensure 'image/kaauh_banner.png' exists in your static files. */
|
||||
background: url("{% static 'image/kaauh_banner.png' %}") no-repeat center center;
|
||||
background-size: cover;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
padding: 3rem;
|
||||
color: white;
|
||||
z-index: 1;
|
||||
}
|
||||
.left-panel::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(to top, rgba(0,0,0,0.8) 0%, rgba(0,0,0,0) 50%);
|
||||
z-index: 0;
|
||||
}
|
||||
.left-panel-content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* Right Panel Styling */
|
||||
.right-panel {
|
||||
background-color: white;
|
||||
padding: 3rem;
|
||||
}
|
||||
.form-fields {
|
||||
max-height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Component Overrides to use Teal Theme */
|
||||
.btn-primary {
|
||||
background-color: var(--bs-primary);
|
||||
border-color: var(--bs-primary);
|
||||
font-weight: 600;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 4px 8px rgba(0, 99, 110, 0.2);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.btn-primary:hover {
|
||||
background-color: var(--bs-primary-light);
|
||||
border-color: var(--bs-primary-light);
|
||||
box-shadow: 0 6px 10px rgba(0, 99, 110, 0.3);
|
||||
}
|
||||
.form-control:focus {
|
||||
border-color: var(--bs-primary);
|
||||
box-shadow: 0 0 0 0.25rem rgba(0, 99, 110, 0.25);
|
||||
}
|
||||
.text-accent {
|
||||
color: var(--bs-primary) !important;
|
||||
}
|
||||
.text-accent:hover {
|
||||
color: var(--bs-primary-light) !important;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.alert-info-custom {
|
||||
background-color: #f0f8ff; /* Very light blue */
|
||||
border-left: 5px solid var(--bs-primary);
|
||||
color: var(--bs-primary);
|
||||
}
|
||||
|
||||
/* ADJUSTED: Custom size adjustment for right panel on desktop */
|
||||
@media (min-width: 992px) {
|
||||
/* 1. Set a NARROWER fixed width for the right panel container */
|
||||
.right-panel-col {
|
||||
flex: 0 0 450px; /* Width of the panel */
|
||||
}
|
||||
/* 2. Vertically and horizontally center the content within the narrow panel */
|
||||
.right-panel-col > .d-flex {
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
/* 3. Ensure the form container doesn't exceed a smaller size and is centered */
|
||||
.right-panel-content-wrapper {
|
||||
max-width: 350px; /* Max width of the form elements inside the panel */
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="d-flex vh-100 w-100">
|
||||
|
||||
<div class="left-panel d-none d-lg-flex flex-grow-1">
|
||||
<div class="left-panel-content">
|
||||
<h1 class="text-4xl font-weight-bold mb-4" style="font-size: 1.5rem;">
|
||||
<span class="text-white">
|
||||
<div class="hospital-text text-center text-md-start me-3">
|
||||
<div class="ar small">{% trans "جامعة الأميرة نورة بنت عبدالرحمن الأكاديمية" %}</div>
|
||||
<div class="ar small">{% trans "ومستشفى الملك عبدالله بن عبدالعزيز التخصصي" %}</div>
|
||||
<div class="en small">{% trans "Princess Nourah bint Abdulrahman University" %}</div>
|
||||
<div class="en small">{% trans "King Abdullah bin Abdulaziz University Hospital" %}</div>
|
||||
</div>
|
||||
</span>
|
||||
</h1>
|
||||
<small>Powered By TENHAL | تنحل</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-column right-panel right-panel-col flex-grow-1 align-items-center justify-content-center">
|
||||
|
||||
<div class="right-panel-content-wrapper">
|
||||
|
||||
<h2 id="form-title" class="h3 fw-bold mb-4 text-center text-accent">{% trans "Password Reset Sent" %}</h2>
|
||||
|
||||
<div class="form-fields">
|
||||
|
||||
<div class="text-center mb-4">
|
||||
<i class="fas fa-envelope-open-text fa-4x mb-3 text-accent" aria-hidden="true"></i>
|
||||
</div>
|
||||
|
||||
<p class="text-muted text-center mb-4">
|
||||
{% blocktrans %}
|
||||
We've **sent an email** to the address you provided with instructions on how to reset your password.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
<div class="alert alert-info-custom p-3 d-flex align-items-start" role="alert">
|
||||
<i class="fas fa-info-circle me-3 mt-1" aria-hidden="true"></i>
|
||||
<small>
|
||||
{% trans "Please check your inbox (and spam folder). The link in the email is temporary and will expire soon for security reasons." %}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
{# Button to return to the login page #}
|
||||
<div class="d-grid mt-4">
|
||||
<a href="{% url 'account_login' %}" class="btn btn-primary btn-lg">
|
||||
<i class="fas fa-sign-in-alt me-2"></i> {% trans "Return to Login" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/js/all.min.js" crossorigin="anonymous"></script>
|
||||
</body>
|
||||
</html>
|
||||
237
templates/account/password_reset_from_key.html
Normal file
237
templates/account/password_reset_from_key.html
Normal file
@ -0,0 +1,237 @@
|
||||
{% load static i18n %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% trans "Set New Password" %} - KAAUH ATS</title>
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
|
||||
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
|
||||
<style>
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* CUSTOM TEAL THEME OVERRIDES FOR BOOTSTRAP (Copied from provided login page) */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
:root {
|
||||
/* Define TEAL as the primary color for Bootstrap overrides */
|
||||
--bs-primary: #00636e; /* Dark Teal */
|
||||
--bs-primary-rgb: 0, 99, 110;
|
||||
--bs-primary-light: #007a88; /* Lighter Teal for hover */
|
||||
|
||||
/* Background and Text Colors */
|
||||
--bs-body-bg: #f8f9fa; /* Light gray background */
|
||||
--bs-body-color: #212529; /* Dark text */
|
||||
|
||||
/* Utility colors */
|
||||
--bs-border-color: #dee2e6; /* Bootstrap default border */
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
background-color: var(--bs-body-bg);
|
||||
}
|
||||
|
||||
/* Custom Left Panel (Replicating the original look) */
|
||||
.left-panel {
|
||||
flex: 1;
|
||||
/* NOTE: Static image URL is included here. Ensure 'image/kaauh_banner.png' exists in your static files. */
|
||||
background: url("{% static 'image/kaauh_banner.png' %}") no-repeat center center;
|
||||
background-size: cover;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
padding: 3rem;
|
||||
color: white;
|
||||
z-index: 1;
|
||||
}
|
||||
.left-panel::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(to top, rgba(0,0,0,0.8) 0%, rgba(0,0,0,0) 50%);
|
||||
z-index: 0;
|
||||
}
|
||||
.left-panel-content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* Right Panel Styling */
|
||||
.right-panel {
|
||||
background-color: white;
|
||||
padding: 3rem;
|
||||
}
|
||||
.form-fields {
|
||||
max-height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Component Overrides to use Teal Theme */
|
||||
.btn-primary {
|
||||
background-color: var(--bs-primary);
|
||||
border-color: var(--bs-primary);
|
||||
font-weight: 600;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 4px 8px rgba(0, 99, 110, 0.2);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.btn-primary:hover {
|
||||
background-color: var(--bs-primary-light);
|
||||
border-color: var(--bs-primary-light);
|
||||
box-shadow: 0 6px 10px rgba(0, 99, 110, 0.3);
|
||||
}
|
||||
.form-control:focus {
|
||||
border-color: var(--bs-primary);
|
||||
box-shadow: 0 0 0 0.25rem rgba(0, 99, 110, 0.25);
|
||||
}
|
||||
.text-accent {
|
||||
color: var(--bs-primary) !important;
|
||||
}
|
||||
.text-accent:hover {
|
||||
color: var(--bs-primary-light) !important;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.alert-info-custom {
|
||||
background-color: #f0f8ff; /* Very light blue */
|
||||
border-left: 5px solid var(--bs-primary);
|
||||
color: var(--bs-primary);
|
||||
}
|
||||
|
||||
/* ADJUSTED: Custom size adjustment for right panel on desktop */
|
||||
@media (min-width: 992px) {
|
||||
/* 1. Set a NARROWER fixed width for the right panel container */
|
||||
.right-panel-col {
|
||||
flex: 0 0 450px; /* Width of the panel */
|
||||
}
|
||||
/* 2. Vertically and horizontally center the content within the narrow panel */
|
||||
.right-panel-col > .d-flex {
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
/* 3. Ensure the form container doesn't exceed a smaller size and is centered */
|
||||
.right-panel-content-wrapper {
|
||||
max-width: 350px; /* Max width of the form elements inside the panel */
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="d-flex vh-100 w-100">
|
||||
|
||||
<div class="left-panel d-none d-lg-flex flex-grow-1">
|
||||
<div class="left-panel-content">
|
||||
<h1 class="text-4xl font-weight-bold mb-4" style="font-size: 1.5rem;">
|
||||
<span class="text-white">
|
||||
<div class="hospital-text text-center text-md-start me-3">
|
||||
<div class="ar small">{% trans "جامعة الأميرة نورة بنت عبدالرحمن الأكاديمية" %}</div>
|
||||
<div class="ar small">{% trans "ومستشفى الملك عبدالله بن عبدالعزيز التخصصي" %}</div>
|
||||
<div class="en small">{% trans "Princess Nourah bint Abdulrahman University" %}</div>
|
||||
<div class="en small">{% trans "King Abdullah bin Abdulaziz University Hospital" %}</div>
|
||||
</div>
|
||||
</span>
|
||||
</h1>
|
||||
<small>Powered By TENHAL | تنحل</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-column right-panel right-panel-col flex-grow-1 align-items-center justify-content-center">
|
||||
|
||||
<div class="right-panel-content-wrapper">
|
||||
|
||||
<h2 id="form-title" class="h3 fw-bold mb-4 text-center text-accent">{% trans "Set New Password" %}</h2>
|
||||
|
||||
<div class="form-fields">
|
||||
|
||||
{% if form %}
|
||||
<p class="text-muted small mb-4 text-center">
|
||||
{% trans 'Please enter your new password below. You can then log in.' %}
|
||||
</p>
|
||||
|
||||
<form method="post" action="{{ action_url }}">
|
||||
{% csrf_token %}
|
||||
|
||||
{# Display any general form errors #}
|
||||
{% if form.non_field_errors %}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
{% for error in form.non_field_errors %}
|
||||
{{ error }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Password 1 Field #}
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.password.id_for_label }}" class="form-label fw-semibold">{% trans "New Password" %} *</label>
|
||||
|
||||
{% if form.password.errors %}
|
||||
<div class="alert alert-danger p-2 small">
|
||||
{{ form.password.errors }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<input type="password" name="{{ form.password.name }}" id="{{ form.password.id_for_label }}"
|
||||
class="form-control" placeholder="{% trans 'Enter new password' %}" required autofocus>
|
||||
</div>
|
||||
|
||||
{# Password 2 Field #}
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.password2.id_for_label }}" class="form-label fw-semibold">{% trans "Confirm New Password" %} *</label>
|
||||
|
||||
{% if form.password2.errors %}
|
||||
<div class="alert alert-danger p-2 small">
|
||||
{{ form.password2.errors }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<input type="password" name="{{ form.password2.name }}" id="{{ form.password2.id_for_label }}"
|
||||
class="form-control" placeholder="{% trans 'Confirm new password' %}" required>
|
||||
</div>
|
||||
|
||||
{# Hidden fields, if any (like uidb64, token/key) #}
|
||||
{{ form.uid }}
|
||||
{{ form.token }}
|
||||
|
||||
{# Submit Button #}
|
||||
<button type="submit" name="action" class="btn btn-primary w-100 mt-4">
|
||||
{% trans "Change Password" %}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{% else %}
|
||||
{# Message when the reset key is invalid or expired #}
|
||||
<h4 class="text-danger text-center mb-3">{% trans "Password Reset Failed" %}</h4>
|
||||
<p class="text-muted text-center mb-4">
|
||||
{% trans "The password reset link is invalid or has expired." %}
|
||||
</p>
|
||||
<div class="d-grid mt-4">
|
||||
<a href="{% url 'account_reset_password' %}" class="btn btn-primary">
|
||||
<i class="fas fa-redo me-2"></i> {% trans "Request New Reset Link" %}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="text-center mt-3">
|
||||
<a href="{% url 'account_login' %}" class="small text-accent fw-medium">
|
||||
<i class="fas fa-sign-in-alt me-1"></i> {% trans "Return to Sign In" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/js/all.min.js" crossorigin="anonymous"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -56,7 +56,7 @@
|
||||
<img src="{% static 'image/kaauh_green1.png' %}" alt="{% trans 'kaauh logo green bg' %}" class="navbar-brand-mobile">
|
||||
</a>
|
||||
|
||||
<a class="navbar-brand text-white d-none d-lg-block" href="{% url 'dashboard' %}" aria-label="Home">
|
||||
<a class="navbar-brand text-white d-none d-lg-block me-4 pe-4" href="{% url 'dashboard' %}" aria-label="Home">
|
||||
<img src="{% static 'image/kaauh_green1.png' %}" alt="{% trans 'kaauh logo green bg' %}" style="width: 60px; height: 60px;">
|
||||
</a>
|
||||
|
||||
@ -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' %}"
|
||||
>
|
||||
<i class="fas fa-sign-out-alt me-3 fs-5"></i>
|
||||
<i class="fas fa-sign-out-alt me-3 fs-5 text-danger"></i>
|
||||
<span>{% trans "Sign Out" %}</span>
|
||||
</button>
|
||||
</form>
|
||||
@ -221,7 +221,7 @@
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item me-lg-4">
|
||||
{% comment %} <li class="nav-item me-lg-4">
|
||||
<a class="nav-link {% if request.resolver_match.url_name == 'training_list' %}active{% endif %}" href="{% url 'training_list' %}">
|
||||
<span class="d-flex align-items-center gap-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
@ -230,8 +230,8 @@
|
||||
{% trans "Training" %}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item dropdown ms-lg-2">
|
||||
</li> {% endcomment %}
|
||||
{% comment %} <li class="nav-item dropdown ms-lg-2">
|
||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"
|
||||
data-bs-offset="0, 8" data-bs-auto-close="outside">
|
||||
{% trans "More" %}
|
||||
@ -246,7 +246,7 @@
|
||||
<li><a class="dropdown-item" href="#"><i class="fas fa-users me-2"></i> {% trans "All Candidates" %}</a></li>
|
||||
<li><a class="dropdown-item" href="#"><i class="fas fa-user-plus me-2"></i> {% trans "New Candidates" %}</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</li> {% endcomment %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -8,6 +8,28 @@
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
|
||||
{# ================================================= #}
|
||||
{# DJANGO MESSAGE BLOCK - Placed directly below the main navbar #}
|
||||
{# ================================================= #}
|
||||
{% if messages %}
|
||||
<div class="container-fluid message-container">
|
||||
<div class="row">
|
||||
{# Using responsive columns to center the message content, similar to your form structure #}
|
||||
<div class="col-lg-8 offset-lg-2 col-md-10 offset-md-1 col-12">
|
||||
{% for message in messages %}
|
||||
{# Use 'alert-{{ message.tags }}' to apply Bootstrap styling based on Django's tag (success, error/danger, info, warning) #}
|
||||
<div class="alert alert-{{ message.tags|default:'info' }} alert-dismissible fade show" role="alert">
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{# ================================================= #}
|
||||
|
||||
<div class="container">
|
||||
<div class="row mb-5 mt-3 main-content-area">
|
||||
|
||||
@ -85,4 +107,6 @@
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock content%}
|
||||
@ -9,7 +9,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% translate "Application Form" %}</title>
|
||||
|
||||
|
||||
{% comment %} Load the correct Bootstrap CSS file for RTL/LTR {% endcomment %}
|
||||
{% if LANGUAGE_CODE == 'ar' %}
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.rtl.min.css" rel="stylesheet">
|
||||
@ -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 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<nav id="topNavbar" class="navbar navbar-expand-lg sticky-top" style="background-color: white; z-index: 1040;">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand text-white fw-bold" href="{% url 'kaauh_career' %}">
|
||||
@ -211,7 +248,7 @@
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-secondary" href="/applications/">{% translate "Applications" %}</a>
|
||||
</li>
|
||||
@ -223,15 +260,15 @@
|
||||
</li>
|
||||
|
||||
<li class="nav-item dropdown">
|
||||
<button class="language-toggle-btn dropdown-toggle" type="button"
|
||||
data-bs-toggle="dropdown" data-bs-offset="0, 8" aria-expanded="false"
|
||||
<button class="language-toggle-btn dropdown-toggle" type="button"
|
||||
data-bs-toggle="dropdown" data-bs-offset="0, 8" aria-expanded="false"
|
||||
aria-label="{% trans 'Toggle language menu' %}">
|
||||
<i class="fas fa-globe"></i>
|
||||
<span class="d-inline">{{ LANGUAGE_CODE|upper }}</span>
|
||||
</button>
|
||||
|
||||
|
||||
<ul class="dropdown-menu {% if LANGUAGE_CODE == 'ar' %}dropdown-menu-start{% else %}dropdown-menu-end{% endif %}" aria-labelledby="navbarLanguageDropdown">
|
||||
|
||||
|
||||
{% comment %} English Button {% endcomment %}
|
||||
<li>
|
||||
<form action="{% url 'set_language' %}" method="post" class="d-inline">{% csrf_token %}
|
||||
@ -257,6 +294,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
@ -225,7 +225,7 @@
|
||||
{% if active_tab == 'crud' %}
|
||||
<tr>
|
||||
<td>{{ log.datetime|date:"Y-m-d H:i:s" }}</td>
|
||||
<td>{{ log.user.get_full_name|default:log.user.username|default:"N/A" }}</td>
|
||||
<td>{{ log.user.email|default:"N/A" }}</td>
|
||||
<td>
|
||||
<span class="badge rounded-pill
|
||||
{% if log.event_type == 1 %}badge-crud-create
|
||||
|
||||
@ -4,8 +4,6 @@
|
||||
{% block title %}Create New Job Post - {{ block.super }}{% endblock %}
|
||||
|
||||
{% block customCSS %}
|
||||
{# 💡 1. Add Summernote CSS Media in the head #}
|
||||
{{ form.media.css }}
|
||||
|
||||
<style>
|
||||
/* ================================================= */
|
||||
@ -77,25 +75,28 @@
|
||||
}
|
||||
|
||||
/* ================================================= */
|
||||
/* ✅ CORRECTED SUMMERNOTE FULL-WIDTH STYLING */
|
||||
/* REMOVED SUMMERNOTE CSS AND ADDED CKEDITOR 5 BASE STYLES */
|
||||
/* ================================================= */
|
||||
|
||||
/* Make every Summernote editor fill its container */
|
||||
.note-editor {
|
||||
width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
margin: 0 !important;
|
||||
max-width: none !important;
|
||||
border-radius: 0.5rem;
|
||||
/* CKEditor 5 base styling for the container */
|
||||
.ck-editor__editable_inline {
|
||||
/* This sets the content area height/min-height */
|
||||
min-height: 200px; /* A default height */
|
||||
border-radius: 0 0 0.5rem 0.5rem;
|
||||
border: 1px solid #ced4da;
|
||||
}
|
||||
|
||||
/* Set minimum heights for specific fields using sibling selector */
|
||||
#id_description + .note-editor { min-height: 300px; }
|
||||
#id_qualifications + .note-editor { min-height: 200px; }
|
||||
#id_benefits + .note-editor,
|
||||
#id_application_instructions + .note-editor { min-height: 150px; }
|
||||
/* Override min-height for specific fields using ID */
|
||||
#id_description .ck-editor__editable_inline { min-height: 300px; }
|
||||
#id_qualifications .ck-editor__editable_inline { min-height: 200px; }
|
||||
#id_benefits .ck-editor__editable_inline,
|
||||
#id_application_instructions .ck-editor__editable_inline { min-height: 150px; }
|
||||
|
||||
.ck-toolbar {
|
||||
border-radius: 0.5rem 0.5rem 0 0 !important;
|
||||
}
|
||||
</style>
|
||||
{{form.media.css}}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
@ -150,7 +151,7 @@
|
||||
</div>
|
||||
|
||||
{# ================================================= #}
|
||||
{# SECTION 2: JOB CONTENT (All Summernote Fields) #}
|
||||
{# SECTION 2: JOB CONTENT (CKEDITOR 5 Fields) #}
|
||||
{# ================================================= #}
|
||||
<div class="card mb-4 shadow-sm">
|
||||
<div class="card-header-themed">
|
||||
@ -193,14 +194,7 @@
|
||||
{% if form.salary_range.errors %}<div class="text-danger small mt-1">{{ form.salary_range.errors }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% comment %} <div class="col-md-6">
|
||||
<div>
|
||||
<label for="{{ form.application_url.id_for_label }}" class="form-label">{% trans "Application URL" %} <span class="text-danger">*</span></label>
|
||||
{{ form.application_url }}
|
||||
{% if form.application_url.errors %}<div class="text-danger small mt-1">{{ form.application_url.errors }}</div>{% endif %}
|
||||
<div class="form-text">{% trans "Full URL where candidates will apply" %}</div>
|
||||
</div>
|
||||
</div> {% endcomment %}
|
||||
{% comment %} (application_url comment removed for brevity) {% endcomment %}
|
||||
|
||||
<div class="col-12">
|
||||
<div>
|
||||
@ -261,16 +255,16 @@
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div>
|
||||
<label for="{{ form.start_date.id_for_label }}" class="form-label">{% trans "Desired Start Date" %}</label>
|
||||
{{ form.start_date }}
|
||||
{% if form.start_date.errors %}<div class="text-danger small mt-1">{{ form.start_date.errors }}</div>{% endif %}
|
||||
<label for="{{ form.application_start_date.id_for_label }}" class="form-label">{% trans "Application Start Date" %}</label>
|
||||
{{ form.application_start_date }}
|
||||
{% if form.application_start_date.errors %}<div class="text-danger small mt-1">{{ form.application_start_date.errors }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div>
|
||||
<label for="{{ form.status.id_for_label }}" class="form-label">{% trans "Status" %}</label>
|
||||
{{ form.status }}
|
||||
{% if form.status.errors %}<div class="text-danger small mt-1">{{ form.status.errors }}</div>{% endif %}
|
||||
<label for="{{ form.joining_date.id_for_label }}" class="form-label">{% trans "Desired Joining Date" %}</label>
|
||||
{{ form.joining_date }}
|
||||
{% if form.joining_date.errors %}<div class="text-danger small mt-1">{{ form.joining_date.errors }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -300,14 +294,21 @@
|
||||
{% if form.reporting_to.errors %}<div class="text-danger small mt-1">{{ form.reporting_to.errors }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="col-md-4">
|
||||
<div>
|
||||
<label for="{{ form.open_positions.id_for_label }}" class="form-label">{% trans "Open Positions" %}</label>
|
||||
{{ form.open_positions }}
|
||||
{% if form.open_positions.errors %}<div class="text-danger small mt-1">{{ form.open_positions.errors }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="col-md-4">
|
||||
<div>
|
||||
<label for="{{ form.max_applications.id_for_label }}" class="form-label">{% trans "Max Applications" %}</label>
|
||||
{{ form.max_applications }}
|
||||
{% if form.max_applications.errors %}<div class="text-danger small mt-1">{{ form.max_applications.errors }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div>
|
||||
<label for="{{ form.created_by.id_for_label }}" class="form-label">{% trans "Created By" %}</label>
|
||||
{{ form.created_by }}
|
||||
@ -340,7 +341,11 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{# 💡 2. Add Summernote JS Media at the end of the body #}
|
||||
{{ form.media.js }}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block customJS %}
|
||||
|
||||
{{ form.media.js }}
|
||||
{% endblock%}
|
||||
@ -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 }}
|
||||
|
||||
<style>
|
||||
/* ================================================= */
|
||||
@ -44,7 +42,7 @@
|
||||
}
|
||||
.btn-secondary:hover {
|
||||
background-color: #e9ecef;
|
||||
color: var(--kaauh-teal-dark); /* Reverting color to dark theme text */
|
||||
color: white;
|
||||
border-color: var(--kaauh-teal-dark);
|
||||
}
|
||||
.card {
|
||||
@ -73,41 +71,38 @@
|
||||
border: 1px solid #ced4da;
|
||||
width: 100%;
|
||||
padding: 0.375rem 0.75rem;
|
||||
box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* ================================================= */
|
||||
/* ✅ CORRECTED SUMMERNOTE FULL-WIDTH STYLING (Active Fix) */
|
||||
/* REMOVED SUMMERNOTE CSS AND ADDED CKEDITOR 5 BASE STYLES */
|
||||
/* ================================================= */
|
||||
|
||||
/* The most aggressive, universal fix for Summernote within Bootstrap columns */
|
||||
.col-12 .note-editor {
|
||||
/* This compensates for the 0.75rem padding on each side of the col-12 */
|
||||
width: calc(100% + 1.5rem) !important;
|
||||
margin-left: -0.75rem !important;
|
||||
margin-right: -0.75rem !important;
|
||||
|
||||
/* General cleanup to maintain look */
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 0 !important;
|
||||
border-radius: 0.5rem;
|
||||
/* CKEditor 5 base styling for the container */
|
||||
.ck-editor__editable_inline {
|
||||
/* This sets the content area height/min-height */
|
||||
min-height: 200px; /* A default height */
|
||||
border-radius: 0 0 0.5rem 0.5rem;
|
||||
border: 1px solid #ced4da;
|
||||
}
|
||||
|
||||
/* Set minimum heights for specific fields using sibling selector */
|
||||
#id_description + .note-editor { min-height: 300px; }
|
||||
#id_qualifications + .note-editor { min-height: 200px; }
|
||||
#id_benefits + .note-editor,
|
||||
#id_application_instructions + .note-editor { min-height: 150px; }
|
||||
/* Override min-height for specific fields using ID */
|
||||
#id_description .ck-editor__editable_inline { min-height: 300px; }
|
||||
#id_qualifications .ck-editor__editable_inline { min-height: 200px; }
|
||||
#id_benefits .ck-editor__editable_inline,
|
||||
#id_application_instructions .ck-editor__editable_inline { min-height: 150px; }
|
||||
|
||||
.ck-toolbar {
|
||||
border-radius: 0.5rem 0.5rem 0 0 !important;
|
||||
}
|
||||
</style>
|
||||
{{form.media.css}}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid py-4">
|
||||
<div class="container-fluid py-4">
|
||||
<h1 class="h3 mb-4 text-primary fw-bold">
|
||||
{# UPDATED TITLE FOR EDIT CONTEXT #}
|
||||
<i class="fas fa-edit me-2"></i> {% trans "Edit Job Posting" %}
|
||||
{% if job.title %} - {{ job.title }} {% endif %}
|
||||
<i class="fas fa-bullhorn me-2"></i> {% if form.instance.pk %} {% trans "Edit Job Posting" %} {% else %} {% trans "Create New Job Posting" %} {% endif %}
|
||||
</h1>
|
||||
|
||||
<form method="post" id="jobForm" class="mb-5" enctype="multipart/form-data">
|
||||
@ -151,26 +146,12 @@
|
||||
{% if form.workplace_type.errors %}<div class="text-danger small mt-1">{{ form.workplace_type.errors }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div>
|
||||
<label for="{{ form.application_deadline.id_for_label }}" class="form-label">{% trans "Application Deadline" %}</label>
|
||||
{{ form.application_deadline }}
|
||||
{% if form.application_deadline.errors %}<div class="text-danger small mt-1">{{ form.application_deadline.errors }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div>
|
||||
<label for="{{ form.max_applications.id_for_label }}" class="form-label">{% trans "Max Number Of Applicants" %}</label>
|
||||
{{ form.max_applications }}
|
||||
{% if form.max_applications.errors %}<div class="text-danger small mt-1">{{ form.max_applications.errors }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# ================================================= #}
|
||||
{# SECTION 2: JOB CONTENT (All Summernote Fields) #}
|
||||
{# SECTION 2: JOB CONTENT (CKEDITOR 5 Fields) #}
|
||||
{# ================================================= #}
|
||||
<div class="card mb-4 shadow-sm">
|
||||
<div class="card-header-themed">
|
||||
@ -181,7 +162,7 @@
|
||||
<div class="col-12">
|
||||
<div>
|
||||
<label for="{{ form.description.id_for_label }}" class="form-label">{% trans "Job Description" %} <span class="text-danger">*</span></label>
|
||||
{{ form.description }}
|
||||
{{ form.description}}
|
||||
{% if form.description.errors %}<div class="text-danger small mt-1">{{ form.description.errors }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
@ -189,7 +170,7 @@
|
||||
<div class="col-12">
|
||||
<div>
|
||||
<label for="{{ form.qualifications.id_for_label }}" class="form-label">{% trans "Qualifications and Requirements" %}</label>
|
||||
{{ form.qualifications }}
|
||||
{{ form.qualifications}}
|
||||
{% if form.qualifications.errors %}<div class="text-danger small mt-1">{{ form.qualifications.errors }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
@ -213,14 +194,7 @@
|
||||
{% if form.salary_range.errors %}<div class="text-danger small mt-1">{{ form.salary_range.errors }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% comment %} <div class="col-md-6">
|
||||
<div>
|
||||
<label for="{{ form.application_url.id_for_label }}" class="form-label">{% trans "Application URL" %} <span class="text-danger">*</span></label>
|
||||
{{ form.application_url }}
|
||||
{% if form.application_url.errors %}<div class="text-danger small mt-1">{{ form.application_url.errors }}</div>{% endif %}
|
||||
<div class="form-text">{% trans "Full URL where candidates will apply" %}</div>
|
||||
</div>
|
||||
</div> {% endcomment %}
|
||||
{% comment %} (application_url comment removed for brevity) {% endcomment %}
|
||||
|
||||
<div class="col-12">
|
||||
<div>
|
||||
@ -271,11 +245,26 @@
|
||||
{% if form.location_country.errors %}<div class="text-danger small mt-1">{{ form.location_country.errors }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div>
|
||||
<label for="{{ form.start_date.id_for_label }}" class="form-label">{% trans "Desired Start Date" %}</label>
|
||||
{{ form.start_date }}
|
||||
{% if form.start_date.errors %}<div class="text-danger small mt-1">{{ form.start_date.errors }}</div>{% endif %}
|
||||
<label for="{{ form.application_deadline.id_for_label }}" class="form-label">{% trans "Application Deadline" %}</label>
|
||||
{{ form.application_deadline }}
|
||||
{% if form.application_deadline.errors %}<div class="text-danger small mt-1">{{ form.application_deadline.errors }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div>
|
||||
<label for="{{ form.application_start_date.id_for_label }}" class="form-label">{% trans "Application Start Date" %}</label>
|
||||
{{ form.application_start_date }}
|
||||
{% if form.application_start_date.errors %}<div class="text-danger small mt-1">{{ form.application_start_date.errors }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div>
|
||||
<label for="{{ form.joining_date.id_for_label }}" class="form-label">{% trans "Desired Joining Date" %}</label>
|
||||
{{ form.joining_date }}
|
||||
{% if form.joining_date.errors %}<div class="text-danger small mt-1">{{ form.joining_date.errors }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -305,14 +294,21 @@
|
||||
{% if form.reporting_to.errors %}<div class="text-danger small mt-1">{{ form.reporting_to.errors }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="col-md-4">
|
||||
<div>
|
||||
<label for="{{ form.open_positions.id_for_label }}" class="form-label">{% trans "Open Positions" %}</label>
|
||||
{{ form.open_positions }}
|
||||
{% if form.open_positions.errors %}<div class="text-danger small mt-1">{{ form.open_positions.errors }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="col-md-4">
|
||||
<div>
|
||||
<label for="{{ form.max_applications.id_for_label }}" class="form-label">{% trans "Max Applications" %}</label>
|
||||
{{ form.max_applications }}
|
||||
{% if form.max_applications.errors %}<div class="text-danger small mt-1">{{ form.max_applications.errors }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div>
|
||||
<label for="{{ form.created_by.id_for_label }}" class="form-label">{% trans "Created By" %}</label>
|
||||
{{ form.created_by }}
|
||||
@ -335,19 +331,21 @@
|
||||
{# ACTION BUTTONS #}
|
||||
{# ================================================= #}
|
||||
<div class="d-flex justify-content-between pt-2">
|
||||
{# UPDATED CANCEL URL for Job Detail #}
|
||||
<a href="{% url 'job_detail' job.slug %}" class="btn btn-secondary">
|
||||
<a href="{% url 'job_list' %}" class="btn btn-secondary">
|
||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Cancel" %}
|
||||
</a>
|
||||
<button type="submit" class="btn btn-main-action">
|
||||
{# UPDATED BUTTON TEXT for Edit action #}
|
||||
<i class="fas fa-save me-1"></i> {% trans "Update Job" %}
|
||||
<i class="fas fa-save me-1"></i> {% trans "Save Job" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{# 💡 2. Add Summernote JS Media at the end of the body #}
|
||||
{{ form.media.js }}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block customJS %}
|
||||
|
||||
{{ form.media.js }}
|
||||
{% endblock%}
|
||||
@ -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 %}
|
||||
<div class="container-fluid py-4">
|
||||
{# ... (Rest of the header and filter content) ... #}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1 style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
||||
<i class="fas fa-briefcase me-2"></i> {% trans "Job Postings" %}
|
||||
@ -202,6 +208,7 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{# ... (Filter card) ... #}
|
||||
<div class="card mb-4 shadow-sm no-hover">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
@ -248,7 +255,6 @@
|
||||
|
||||
{# --- START OF JOB LIST CONTAINER --- #}
|
||||
<div id="job-list">
|
||||
{# 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 --- #}
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" rowspan="2" style="width: 22%;">{% trans "Job Title / ID" %}</th>
|
||||
<th scope="col" rowspan="2" style="width: 12%;">{% trans "Source" %}</th>
|
||||
<th scope="col" rowspan="2" style="width: 10%;">{% trans "Number Of Applicants" %}</th>
|
||||
<th scope="col" rowspan="2" style="width: 8%;">{% trans "Application Deadline" %}</th>
|
||||
<th scope="col" rowspan="2" style="width: 8%;">{% trans "Actions" %}</th>
|
||||
<th scope="col" rowspan="2" class="text-center" style="width: 8%;">{% trans "Manage Forms" %}</th>
|
||||
<th scope="col" rowspan="2">{% trans "Job Title / ID" %}</th>
|
||||
<th scope="col" rowspan="2">{% trans "Source" %}</th>
|
||||
<th scope="col" rowspan="2">{% trans "Max Apps" %}</th>
|
||||
<th scope="col" rowspan="2">{% trans "Deadline" %}</th>
|
||||
<th scope="col" rowspan="2">{% trans "Actions" %}</th>
|
||||
<th scope="col" rowspan="2" class="text-center">{% trans "Manage Forms" %}</th>
|
||||
|
||||
<th scope="col" colspan="5" class="candidate-management-header-title">
|
||||
{% trans "Applicants Metrics" %}
|
||||
@ -273,11 +279,11 @@
|
||||
</tr>
|
||||
|
||||
<tr class="nested-metrics-row">
|
||||
<th style="width: calc(50% / 7);">{% trans "Applied" %}</th>
|
||||
<th style="width: calc(50% / 7);">{% trans "Screened" %}</th>
|
||||
<th style="width: calc(50% / 7 * 2);">{% trans "Exam" %}</th>
|
||||
<th style="width: calc(50% / 7 * 2);">{% trans "Interview" %}</th>
|
||||
<th style="width: calc(50% / 7);">{% trans "Offer" %}</th>
|
||||
<th scope="col">{% trans "Applied" %}</th>
|
||||
<th scope="col">{% trans "Screened" %}</th>
|
||||
<th scope="col">{% trans "Exam" %}</th>
|
||||
<th scope="col">{% trans "Interview" %}</th>
|
||||
<th scope="col">{% trans "Offer" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@ -333,7 +339,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# 2. CARD VIEW (Previously Missing) - Added Bootstrap row/col structure for layout #}
|
||||
{# ... (Card View and Paginator content) ... #}
|
||||
<div class="card-view row g-4">
|
||||
{% for job in jobs %}
|
||||
<div class="col-xl-4 col-lg-6 col-md-6">
|
||||
|
||||
@ -149,59 +149,63 @@
|
||||
</div>
|
||||
|
||||
<div class="card mb-4 shadow-sm no-hover">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<label for="search" class="form-label small text-muted">{% trans "Search by Name or Email" %}</label>
|
||||
<div class="input-group input-group-lg mb-3">
|
||||
<form method="get" action="" class="w-100">
|
||||
{% include 'includes/search_form.html' %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{% url 'candidate_list' as candidate_list_url %}
|
||||
|
||||
<form method="GET" class="row g-3 align-items-end" >
|
||||
{% if search_query %}<input type="hidden" name="q" value="{{ search_query }}">{% endif %}
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="col-md-4">
|
||||
<label for="job_filter" class="form-label small text-muted">{% trans "Filter by Job" %}</label>
|
||||
<div class="d-flex gap-2">
|
||||
<select name="job" id="job_filter" class="form-select form-select-sm">
|
||||
<option value="">{% trans "All Jobs" %}</option>
|
||||
{% for job in available_jobs %}
|
||||
<option value="{{ job.slug }}" {% if job_filter == job.slug %}selected{% endif %}>{{ job.title }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<select name="stage" id="stage_filter" class="form-select form-select-sm">
|
||||
<option value="">{% trans "All Stages" %}</option>
|
||||
<option value="Applied" {% if stage_filter == 'Applied' %}selected{% endif %}>{% trans "Applied" %}</option>
|
||||
<option value="Exam" {% if stage_filter == 'Exam' %}selected{% endif %}>{% trans "Exam" %}</option>
|
||||
<option value="Interview" {% if stage_filter == 'Interview' %}selected{% endif %}>{% trans "Interview" %}</option>
|
||||
<option value="Offer" {% if stage_filter == 'Offer' %}selected{% endif %}>{% trans "Offer" %}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-8">
|
||||
<div class="filter-buttons">
|
||||
<button type="submit" class="btn btn-main-action btn-sm">
|
||||
<i class="fas fa-filter me-1"></i> {% trans "Apply Filters" %}
|
||||
</button>
|
||||
{% if job_filter or search_query %}
|
||||
<a href="{% url 'candidate_list' %}" class="btn btn-outline-secondary btn-sm">
|
||||
<i class="fas fa-times me-1"></i> {% trans "Clear" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6">
|
||||
<label for="search" class="form-label small text-muted">{% trans "Search by Name or Email" %}</label>
|
||||
<div class="input-group input-group-lg">
|
||||
<form method="get" action="" class="w-100">
|
||||
{% include 'includes/search_form.html' %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
{% url 'candidate_list' as candidate_list_url %}
|
||||
|
||||
<form method="GET" class="row g-3 align-items-end h-100">
|
||||
{% if search_query %}<input type="hidden" name="q" value="{{ search_query }}">{% endif %}
|
||||
|
||||
{# Filter Group #}
|
||||
<div class="col-md-4 col-6">
|
||||
<label for="job_filter" class="form-label small text-muted">{% trans "Filter by Job" %}</label>
|
||||
<select name="job" id="job_filter" class="form-select form-select-sm">
|
||||
<option value="">{% trans "All Jobs" %}</option>
|
||||
{% for job in available_jobs %}
|
||||
<option value="{{ job.slug }}" {% if job_filter == job.slug %}selected{% endif %}>{{ job.title }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 col-6">
|
||||
<label for="stage_filter" class="form-label small text-muted">{% trans "Filter by Stage" %}</label>
|
||||
<select name="stage" id="stage_filter" class="form-select form-select-sm">
|
||||
<option value="">{% trans "All Stages" %}</option>
|
||||
<option value="Applied" {% if stage_filter == 'Applied' %}selected{% endif %}>{% trans "Applied" %}</option>
|
||||
<option value="Exam" {% if stage_filter == 'Exam' %}selected{% endif %}>{% trans "Exam" %}</option>
|
||||
<option value="Interview" {% if stage_filter == 'Interview' %}selected{% endif %}>{% trans "Interview" %}</option>
|
||||
<option value="Offer" {% if stage_filter == 'Offer' %}selected{% endif %}>{% trans "Offer" %}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{# Buttons Group (pushed to the right/bottom) #}
|
||||
<div class="col-md-4 d-flex justify-content-end align-self-end">
|
||||
<div class="filter-buttons">
|
||||
<button type="submit" class="btn btn-main-action btn-sm">
|
||||
<i class="fas fa-filter me-1"></i> {% trans "Apply" %}
|
||||
</button>
|
||||
{% if job_filter or stage_filter or search_query %}
|
||||
<a href="{% url 'candidate_list' %}" class="btn btn-outline-secondary btn-sm">
|
||||
<i class="fas fa-times me-1"></i> {% trans "Clear" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if candidates %}
|
||||
<div id="candidate-list">
|
||||
{# View Switcher - list_id must match the container ID #}
|
||||
|
||||
@ -108,7 +108,7 @@
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="container-fluid mt-4">
|
||||
<div class="container-fluid mt-5">
|
||||
<div class="row px-lg-4">
|
||||
<div class="col-12">
|
||||
<h1 class="h3 fw-bold dashboard-header">
|
||||
|
||||
@ -29,7 +29,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex vh-80 w-100 justify-content-center align-items-center mt-4">
|
||||
<div class="d-flex vh-80 w-100 justify-content-center align-items-center mt-5">
|
||||
|
||||
<div class="form-card">
|
||||
|
||||
@ -70,7 +70,7 @@
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="pt-5 mt-2 text-center border-top border-light-subtle">
|
||||
<div class="pt-3 mt-1 text-center border-top border-light-subtle mb-4">
|
||||
<p class="text-muted small mb-0">
|
||||
<i class="fas fa-arrow-left me-1"></i>
|
||||
<a href="{% url 'admin_settings' %}" class="text-accent text-decoration-none text-secondary">{% trans "Back to Settings" %}</a>
|
||||
|
||||
@ -122,7 +122,7 @@
|
||||
<div class="card p-5">
|
||||
<h5 class="fw-bold mb-4 text-accent">{% trans "Personal Information" %}</h5>
|
||||
|
||||
<form method="post" action="{% url 'user_detail' user.pk %}">
|
||||
<form method="POST" action="{% url 'user_detail' user.pk %}">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="row g-4"> <div class="col-md-6">
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="d-flex vh-80 w-100 justify-content-center align-items-center">
|
||||
<div class="d-flex vh-80 w-100 justify-content-center align-items-center mt-5">
|
||||
|
||||
<div class="form-card">
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user