ckeditor added #9
BIN
db.sqlite3
BIN
db.sqlite3
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -5,7 +5,7 @@ from django.utils import timezone
|
|||||||
from .models import (
|
from .models import (
|
||||||
JobPosting, Candidate, TrainingMaterial, ZoomMeeting,
|
JobPosting, Candidate, TrainingMaterial, ZoomMeeting,
|
||||||
FormTemplate, FormStage, FormField, FormSubmission, FieldResponse,
|
FormTemplate, FormStage, FormField, FormSubmission, FieldResponse,
|
||||||
SharedFormTemplate, Source, HiringAgency, IntegrationLog,InterviewSchedule,Profile
|
SharedFormTemplate, Source, HiringAgency, IntegrationLog,InterviewSchedule,Profile,JobPostingImage
|
||||||
)
|
)
|
||||||
|
|
||||||
class FormFieldInline(admin.TabularInline):
|
class FormFieldInline(admin.TabularInline):
|
||||||
@ -263,3 +263,6 @@ admin.site.register(FieldResponse)
|
|||||||
admin.site.register(InterviewSchedule)
|
admin.site.register(InterviewSchedule)
|
||||||
admin.site.register(Profile)
|
admin.site.register(Profile)
|
||||||
# admin.site.register(HiringAgency)
|
# admin.site.register(HiringAgency)
|
||||||
|
|
||||||
|
|
||||||
|
admin.site.register(JobPostingImage)
|
||||||
@ -5,7 +5,10 @@ from django.forms.formsets import formset_factory
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from crispy_forms.helper import FormHelper
|
from crispy_forms.helper import FormHelper
|
||||||
from crispy_forms.layout import Layout, Submit, Row, Column, Field, Div
|
from crispy_forms.layout import Layout, Submit, Row, Column, Field, Div
|
||||||
from .models import ZoomMeeting, Candidate,TrainingMaterial,JobPosting,FormTemplate,InterviewSchedule,BreakTime
|
from .models import (
|
||||||
|
ZoomMeeting, Candidate,TrainingMaterial,JobPosting,
|
||||||
|
FormTemplate,InterviewSchedule,BreakTime,JobPostingImage
|
||||||
|
)
|
||||||
# from django_summernote.widgets import SummernoteWidget
|
# from django_summernote.widgets import SummernoteWidget
|
||||||
from django_ckeditor_5.widgets import CKEditor5Widget
|
from django_ckeditor_5.widgets import CKEditor5Widget
|
||||||
|
|
||||||
@ -192,8 +195,8 @@ class JobPostingForm(forms.ModelForm):
|
|||||||
fields = [
|
fields = [
|
||||||
'title', 'department', 'job_type', 'workplace_type',
|
'title', 'department', 'job_type', 'workplace_type',
|
||||||
'location_city', 'location_state', 'location_country',
|
'location_city', 'location_state', 'location_country',
|
||||||
'description', 'qualifications', 'salary_range', 'benefits',
|
'description', 'qualifications', 'salary_range', 'benefits'
|
||||||
'application_url', 'application_deadline', 'application_instructions',
|
,'application_deadline', 'application_instructions',
|
||||||
'position_number', 'reporting_to', 'start_date', 'status',
|
'position_number', 'reporting_to', 'start_date', 'status',
|
||||||
'created_by','open_positions','hash_tags'
|
'created_by','open_positions','hash_tags'
|
||||||
]
|
]
|
||||||
@ -239,11 +242,11 @@ class JobPostingForm(forms.ModelForm):
|
|||||||
|
|
||||||
|
|
||||||
# Application Information
|
# Application Information
|
||||||
'application_url': forms.URLInput(attrs={
|
# 'application_url': forms.URLInput(attrs={
|
||||||
'class': 'form-control',
|
# 'class': 'form-control',
|
||||||
'placeholder': 'https://university.edu/careers/job123',
|
# 'placeholder': 'https://university.edu/careers/job123',
|
||||||
'required': True
|
# 'required': True
|
||||||
}),
|
# }),
|
||||||
'application_deadline': forms.DateInput(attrs={
|
'application_deadline': forms.DateInput(attrs={
|
||||||
'class': 'form-control',
|
'class': 'form-control',
|
||||||
'type': 'date'
|
'type': 'date'
|
||||||
@ -356,6 +359,10 @@ class JobPostingForm(forms.ModelForm):
|
|||||||
|
|
||||||
return cleaned_data
|
return cleaned_data
|
||||||
|
|
||||||
|
class JobPostingImageForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model=JobPostingImage
|
||||||
|
fields=['post_image']
|
||||||
|
|
||||||
class FormTemplateForm(forms.ModelForm):
|
class FormTemplateForm(forms.ModelForm):
|
||||||
"""Form for creating form templates"""
|
"""Form for creating form templates"""
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
# Generated by Django 5.2.6 on 2025-10-09 10:10
|
# Generated by Django 5.2.7 on 2025-10-11 11:04
|
||||||
|
|
||||||
import django.core.validators
|
import django.core.validators
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
import django_ckeditor_5.fields
|
||||||
import django_countries.fields
|
import django_countries.fields
|
||||||
import django_extensions.db.fields
|
import django_extensions.db.fields
|
||||||
import recruitment.validators
|
import recruitment.validators
|
||||||
@ -183,7 +184,7 @@ class Migration(migrations.Migration):
|
|||||||
('slug', django_extensions.db.fields.RandomCharField(blank=True, editable=False, length=8, unique=True, verbose_name='Slug')),
|
('slug', django_extensions.db.fields.RandomCharField(blank=True, editable=False, length=8, unique=True, verbose_name='Slug')),
|
||||||
('name', models.CharField(help_text='Name of the form template', max_length=200)),
|
('name', models.CharField(help_text='Name of the form template', max_length=200)),
|
||||||
('description', models.TextField(blank=True, help_text='Description of the form template')),
|
('description', models.TextField(blank=True, help_text='Description of the form template')),
|
||||||
('is_active', models.BooleanField(default=True, help_text='Whether this template is active')),
|
('is_active', models.BooleanField(default=False, help_text='Whether this template is active')),
|
||||||
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='form_templates', to=settings.AUTH_USER_MODEL)),
|
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='form_templates', to=settings.AUTH_USER_MODEL)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
@ -215,6 +216,7 @@ class Migration(migrations.Migration):
|
|||||||
('phone', models.CharField(max_length=20, verbose_name='Phone')),
|
('phone', models.CharField(max_length=20, verbose_name='Phone')),
|
||||||
('address', models.TextField(max_length=200, verbose_name='Address')),
|
('address', models.TextField(max_length=200, verbose_name='Address')),
|
||||||
('resume', models.FileField(upload_to='resumes/', verbose_name='Resume')),
|
('resume', models.FileField(upload_to='resumes/', verbose_name='Resume')),
|
||||||
|
('is_resume_parsed', models.BooleanField(default=False, verbose_name='Resume Parsed')),
|
||||||
('parsed_summary', models.TextField(blank=True, verbose_name='Parsed Summary')),
|
('parsed_summary', models.TextField(blank=True, verbose_name='Parsed Summary')),
|
||||||
('applied', models.BooleanField(default=False, verbose_name='Applied')),
|
('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')], default='Applied', max_length=100, verbose_name='Stage')),
|
||||||
@ -249,16 +251,16 @@ class Migration(migrations.Migration):
|
|||||||
('location_city', models.CharField(blank=True, max_length=100)),
|
('location_city', models.CharField(blank=True, max_length=100)),
|
||||||
('location_state', models.CharField(blank=True, max_length=100)),
|
('location_state', models.CharField(blank=True, max_length=100)),
|
||||||
('location_country', models.CharField(default='Saudia Arabia', max_length=100)),
|
('location_country', models.CharField(default='Saudia Arabia', max_length=100)),
|
||||||
('description', models.TextField(help_text='Full job description including responsibilities and requirements')),
|
('description', django_ckeditor_5.fields.CKEditor5Field(verbose_name='Description')),
|
||||||
('qualifications', models.TextField(blank=True, help_text='Required qualifications and skills')),
|
('qualifications', django_ckeditor_5.fields.CKEditor5Field(blank=True, null=True)),
|
||||||
('salary_range', models.CharField(blank=True, help_text='e.g., $60,000 - $80,000', max_length=200)),
|
('salary_range', models.CharField(blank=True, help_text='e.g., $60,000 - $80,000', max_length=200)),
|
||||||
('benefits', models.TextField(blank=True, help_text='Benefits offered')),
|
('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_url', models.URLField(blank=True, help_text='URL where candidates apply', null=True, validators=[django.core.validators.URLValidator()])),
|
||||||
('application_deadline', models.DateField(blank=True, null=True)),
|
('application_deadline', models.DateField(blank=True, null=True)),
|
||||||
('application_instructions', models.TextField(blank=True, help_text='Special instructions for applicants')),
|
('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)),
|
('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)),
|
('created_by', models.CharField(blank=True, help_text='Name of person who created this job', max_length=100)),
|
||||||
('status', models.CharField(blank=True, choices=[('DRAFT', 'Draft'), ('PUBLISHED', 'Published'), ('CLOSED', 'Closed'), ('ARCHIVED', 'Archived')], default='DRAFT', max_length=20, null=True)),
|
('status', models.CharField(blank=True, choices=[('DRAFT', 'Draft'), ('ACTIVE', 'Active'), ('CLOSED', 'Closed'), ('CANCELLED', 'Cancelled'), ('ARCHIVED', 'Archived')], default='DRAFT', max_length=20, null=True)),
|
||||||
('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])),
|
('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_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')),
|
('linkedin_post_url', models.URLField(blank=True, help_text='Direct URL to LinkedIn post')),
|
||||||
@ -270,6 +272,9 @@ class Migration(migrations.Migration):
|
|||||||
('reporting_to', models.CharField(blank=True, help_text='Who this position reports to', max_length=100)),
|
('reporting_to', models.CharField(blank=True, help_text='Who this position reports to', max_length=100)),
|
||||||
('start_date', models.DateField(blank=True, help_text='Desired start date', null=True)),
|
('start_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')),
|
('open_positions', models.PositiveIntegerField(default=1, help_text='Number of open positions for this job')),
|
||||||
|
('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)),
|
||||||
('hiring_agency', models.ManyToManyField(blank=True, help_text='External agency responsible for sourcing candidates for this role', related_name='jobs', to='recruitment.hiringagency', verbose_name='Hiring Agency')),
|
('hiring_agency', models.ManyToManyField(blank=True, help_text='External agency responsible for sourcing candidates for this role', related_name='jobs', to='recruitment.hiringagency', verbose_name='Hiring Agency')),
|
||||||
('source', models.ForeignKey(blank=True, help_text='The system or channel from which this job posting originated or was first published.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='job_postings', to='recruitment.source')),
|
('source', models.ForeignKey(blank=True, help_text='The system or channel from which this job posting originated or was first published.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='job_postings', to='recruitment.source')),
|
||||||
],
|
],
|
||||||
@ -312,6 +317,16 @@ class Migration(migrations.Migration):
|
|||||||
name='job',
|
name='job',
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='candidates', to='recruitment.jobposting', verbose_name='Job'),
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='candidates', to='recruitment.jobposting', verbose_name='Job'),
|
||||||
),
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='JobPostingImage',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('post_image', models.ImageField(height_field='photo_height', upload_to='post/', width_field='photo_width')),
|
||||||
|
('post_image_height', models.PositiveIntegerField(blank=True, null=True)),
|
||||||
|
('post_image_width', models.PositiveIntegerField(blank=True, null=True)),
|
||||||
|
('job', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='post_images', to='recruitment.jobposting')),
|
||||||
|
],
|
||||||
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Profile',
|
name='Profile',
|
||||||
fields=[
|
fields=[
|
||||||
@ -369,7 +384,7 @@ class Migration(migrations.Migration):
|
|||||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated 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')),
|
('slug', django_extensions.db.fields.RandomCharField(blank=True, editable=False, length=8, unique=True, verbose_name='Slug')),
|
||||||
('title', models.CharField(max_length=255, verbose_name='Title')),
|
('title', models.CharField(max_length=255, verbose_name='Title')),
|
||||||
('content', models.TextField(blank=True, verbose_name='Content')),
|
('content', django_ckeditor_5.fields.CKEditor5Field(blank=True, verbose_name='Content')),
|
||||||
('video_link', models.URLField(blank=True, verbose_name='Video Link')),
|
('video_link', models.URLField(blank=True, verbose_name='Video Link')),
|
||||||
('file', models.FileField(blank=True, upload_to='training_materials/', verbose_name='File')),
|
('file', models.FileField(blank=True, upload_to='training_materials/', verbose_name='File')),
|
||||||
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Created by')),
|
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Created by')),
|
||||||
|
|||||||
@ -1,33 +0,0 @@
|
|||||||
# Generated by Django 5.2.6 on 2025-10-09 10:33
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('recruitment', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='jobposting',
|
|
||||||
name='cancel_reason',
|
|
||||||
field=models.TextField(blank=True, help_text='Reason for canceling the job posting', verbose_name='Cancel Reason'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='jobposting',
|
|
||||||
name='cancelled_at',
|
|
||||||
field=models.DateTimeField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='jobposting',
|
|
||||||
name='cancelled_by',
|
|
||||||
field=models.CharField(blank=True, help_text='Name of person who cancelled this job', max_length=100, verbose_name='Cancelled By'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='jobposting',
|
|
||||||
name='status',
|
|
||||||
field=models.CharField(blank=True, choices=[('DRAFT', 'Draft'), ('ACTIVE', 'Active'), ('CLOSED', 'Closed'), ('CANCELLED', 'Cancelled'), ('ARCHIVED', 'Archived')], default='DRAFT', max_length=20, null=True),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-10-11 12:38
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('recruitment', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='jobpostingimage',
|
||||||
|
name='post_image_height',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='jobpostingimage',
|
||||||
|
name='post_image_width',
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='jobpostingimage',
|
||||||
|
name='post_image',
|
||||||
|
field=models.ImageField(upload_to='post/'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -1,23 +0,0 @@
|
|||||||
# Generated by Django 5.2.6 on 2025-10-09 12:59
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('recruitment', '0002_jobposting_cancel_reason_jobposting_cancelled_at_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='candidate',
|
|
||||||
name='is_resume_parsed',
|
|
||||||
field=models.BooleanField(default=False, verbose_name='Resume Parsed'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='formtemplate',
|
|
||||||
name='is_active',
|
|
||||||
field=models.BooleanField(default=False, help_text='Whether this template is active'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-10-10 10:35
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
import django_ckeditor_5.fields
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('recruitment', '0003_candidate_is_resume_parsed_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='jobposting',
|
|
||||||
name='description',
|
|
||||||
field=django_ckeditor_5.fields.CKEditor5Field(help_text='Full job description including responsibilities and requirements'),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='JobPostingImage',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('post_image', models.ImageField(height_field='photo_height', upload_to='post/', width_field='photo_width')),
|
|
||||||
('post_image_height', models.PositiveIntegerField(blank=True, null=True)),
|
|
||||||
('post_image_width', models.PositiveIntegerField(blank=True, null=True)),
|
|
||||||
('job', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='post_images', to='recruitment.jobposting')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-10-10 10:56
|
|
||||||
|
|
||||||
import django_ckeditor_5.fields
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('recruitment', '0004_alter_jobposting_description_jobpostingimage'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='jobposting',
|
|
||||||
name='description',
|
|
||||||
field=django_ckeditor_5.fields.CKEditor5Field(verbose_name='Description'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-10-10 11:10
|
|
||||||
|
|
||||||
import django_ckeditor_5.fields
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('recruitment', '0005_alter_jobposting_description'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='jobposting',
|
|
||||||
name='qualifications',
|
|
||||||
field=django_ckeditor_5.fields.CKEditor5Field(blank=True, help_text='Required qualifications and skills'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -91,7 +91,7 @@ class JobPosting(Base):
|
|||||||
)
|
)
|
||||||
benefits = CKEditor5Field(blank=True,null=True,config_name='extends')
|
benefits = CKEditor5Field(blank=True,null=True,config_name='extends')
|
||||||
|
|
||||||
# Application Information
|
# Application Information ---job detail apply link for the candidates
|
||||||
application_url = models.URLField(
|
application_url = models.URLField(
|
||||||
validators=[URLValidator()],
|
validators=[URLValidator()],
|
||||||
help_text="URL where candidates apply",
|
help_text="URL where candidates apply",
|
||||||
@ -249,11 +249,8 @@ class JobPosting(Base):
|
|||||||
|
|
||||||
class JobPostingImage(models.Model):
|
class JobPostingImage(models.Model):
|
||||||
job=models.ForeignKey('JobPosting',on_delete=models.CASCADE,related_name='post_images')
|
job=models.ForeignKey('JobPosting',on_delete=models.CASCADE,related_name='post_images')
|
||||||
post_image = models.ImageField(upload_to='post/',
|
post_image = models.ImageField(upload_to='post/')
|
||||||
height_field='photo_height',
|
|
||||||
width_field='photo_width')
|
|
||||||
post_image_height = models.PositiveIntegerField(null=True, blank=True)
|
|
||||||
post_image_width = models.PositiveIntegerField(null=True, blank=True)
|
|
||||||
|
|
||||||
class Candidate(Base):
|
class Candidate(Base):
|
||||||
class Stage(models.TextChoices):
|
class Stage(models.TextChoices):
|
||||||
@ -409,6 +406,7 @@ class Candidate(Base):
|
|||||||
return self.STAGE_SEQUENCE.get(old_stage, [])
|
return self.STAGE_SEQUENCE.get(old_stage, [])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
||||||
def submission(self):
|
def submission(self):
|
||||||
return FormSubmission.objects.filter(template__job=self.job).first()
|
return FormSubmission.objects.filter(template__job=self.job).first()
|
||||||
@property
|
@property
|
||||||
|
|||||||
@ -9,6 +9,7 @@ urlpatterns = [
|
|||||||
# Job URLs (using JobPosting model)
|
# Job URLs (using JobPosting model)
|
||||||
path('jobs/', views_frontend.JobListView.as_view(), name='job_list'),
|
path('jobs/', views_frontend.JobListView.as_view(), name='job_list'),
|
||||||
path('jobs/create/', views.create_job, name='job_create'),
|
path('jobs/create/', views.create_job, name='job_create'),
|
||||||
|
path('job/<slug:slug>/upload_image_simple/', views.job_image_upload, name='job_image_upload'),
|
||||||
path('jobs/<slug:slug>/update/', views.edit_job, name='job_update'),
|
path('jobs/<slug:slug>/update/', views.edit_job, name='job_update'),
|
||||||
# path('jobs/<slug:slug>/delete/', views., name='job_delete'),
|
# path('jobs/<slug:slug>/delete/', views., name='job_delete'),
|
||||||
path('jobs/<slug:slug>/', views.job_detail, name='job_detail'),
|
path('jobs/<slug:slug>/', views.job_detail, name='job_detail'),
|
||||||
@ -66,7 +67,7 @@ urlpatterns = [
|
|||||||
|
|
||||||
# path('forms/form/<int:template_id>/submit/', views.submit_form, name='submit_form'),
|
# path('forms/form/<int:template_id>/submit/', views.submit_form, name='submit_form'),
|
||||||
# path('forms/form/<int:template_id>/', views.form_wizard_view, name='form_wizard'),
|
# path('forms/form/<int:template_id>/', views.form_wizard_view, name='form_wizard'),
|
||||||
path('forms/<int:form_id>/submissions/<int:slug>/', views.form_submission_details, name='form_submission_details'),
|
path('forms/<int:form_id>/submissions/<int:submission_id>/', views.form_submission_details, name='form_submission_details'),
|
||||||
path('forms/template/<slug:slug>/submissions/', views.form_template_submissions_list, name='form_template_submissions_list'),
|
path('forms/template/<slug:slug>/submissions/', views.form_template_submissions_list, name='form_template_submissions_list'),
|
||||||
|
|
||||||
# path('forms/<int:form_id>/', views.form_preview, name='form_preview'),
|
# path('forms/<int:form_id>/', views.form_preview, name='form_preview'),
|
||||||
|
|||||||
@ -17,6 +17,7 @@ from .forms import (
|
|||||||
FormTemplateForm,
|
FormTemplateForm,
|
||||||
InterviewScheduleForm,JobPostingStatusForm,
|
InterviewScheduleForm,JobPostingStatusForm,
|
||||||
BreakTimeFormSet,
|
BreakTimeFormSet,
|
||||||
|
JobPostingImageForm
|
||||||
)
|
)
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
@ -215,6 +216,11 @@ def create_job(request):
|
|||||||
job.created_by = request.POST.get("created_by", "").strip()
|
job.created_by = request.POST.get("created_by", "").strip()
|
||||||
if not job.created_by:
|
if not job.created_by:
|
||||||
job.created_by = "University Administrator"
|
job.created_by = "University Administrator"
|
||||||
|
|
||||||
|
job.save()
|
||||||
|
job_apply_url_relative=reverse('job_detail_candidate',kwargs={'slug':job.slug})
|
||||||
|
job_apply_url_absolute=request.build_absolute_uri(job_apply_url_relative)
|
||||||
|
job.application_url=job_apply_url_absolute
|
||||||
job.save()
|
job.save()
|
||||||
messages.success(request, f'Job "{job.title}" created successfully!')
|
messages.success(request, f'Job "{job.title}" created successfully!')
|
||||||
return redirect("job_list")
|
return redirect("job_list")
|
||||||
@ -279,6 +285,7 @@ def job_detail(request, slug):
|
|||||||
offer_count = candidates.filter(stage="Offer").count()
|
offer_count = candidates.filter(stage="Offer").count()
|
||||||
|
|
||||||
status_form = JobPostingStatusForm(instance=job)
|
status_form = JobPostingStatusForm(instance=job)
|
||||||
|
image_upload_form=JobPostingImageForm(instance=job)
|
||||||
|
|
||||||
# 2. Check for POST request (Status Update Submission)
|
# 2. Check for POST request (Status Update Submission)
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
@ -306,10 +313,32 @@ def job_detail(request, slug):
|
|||||||
"applied_count": applied_count,
|
"applied_count": applied_count,
|
||||||
"interview_count": interview_count,
|
"interview_count": interview_count,
|
||||||
"offer_count": offer_count,
|
"offer_count": offer_count,
|
||||||
'status_form':status_form
|
'status_form':status_form,
|
||||||
|
'image_upload_form':image_upload_form
|
||||||
}
|
}
|
||||||
return render(request, "jobs/job_detail.html", context)
|
return render(request, "jobs/job_detail.html", context)
|
||||||
|
|
||||||
|
def job_image_upload(request, slug):
|
||||||
|
#only for handling the post request
|
||||||
|
job=get_object_or_404(JobPosting,slug=slug)
|
||||||
|
if request.method=='POST':
|
||||||
|
image_upload_form=JobPostingImageForm(request.POST,request.FILES)
|
||||||
|
if image_upload_form.is_valid():
|
||||||
|
image_upload_form = image_upload_form.save(commit=False)
|
||||||
|
|
||||||
|
image_upload_form.job = job
|
||||||
|
image_upload_form.save()
|
||||||
|
messages.success(request, f"Image uploaded successfully for {job.title}.")
|
||||||
|
return redirect('job_detail', slug=job.slug)
|
||||||
|
else:
|
||||||
|
|
||||||
|
messages.error(request, "Image upload failed: Please ensure a valid image file was selected.")
|
||||||
|
|
||||||
|
return redirect('job_detail', slug=job.slug)
|
||||||
|
return redirect('job_detail', slug=job.slug)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# job detail facing the candidate:
|
# job detail facing the candidate:
|
||||||
def job_detail_candidate(request, slug):
|
def job_detail_candidate(request, slug):
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 23 KiB |
@ -364,14 +364,14 @@
|
|||||||
.add-option {
|
.add-option {
|
||||||
color: var(--primary);
|
color: var(--primary);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 0.9rem;
|
font-size: 1rem;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
.add-option:hover {
|
.add-option:hover {
|
||||||
text-decoration: underline;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
/* File Upload Specific Styles */
|
/* File Upload Specific Styles */
|
||||||
.file-upload-area {
|
.file-upload-area {
|
||||||
@ -685,12 +685,10 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<!-- Sidebar with form elements -->
|
<!-- Sidebar with form elements -->
|
||||||
<div class="sidebar">
|
<div class="sidebar">
|
||||||
<div class="sidebar-header">
|
<div class="sidebar-header" style="display: flex; flex-direction: column; align-items: center; justify-content: center;">
|
||||||
<a class="" href="{% url 'form_templates_list' %}">
|
<a href="{% url 'form_templates_list' %}">
|
||||||
<img src="{% static 'image/kaauh.jpeg' %}" style="height:100px; width:100px;">
|
<img src="{% static 'image/kaauh.jpeg' %}" style="height:100px; width:100px; display: block; margin: 0 auto;">
|
||||||
|
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="field-categories">
|
<div class="field-categories">
|
||||||
<div class="field-category">
|
<div class="field-category">
|
||||||
|
|||||||
@ -231,7 +231,7 @@
|
|||||||
<td>{{ submission.applicant_email|default:"N/A" }}</td>
|
<td>{{ submission.applicant_email|default:"N/A" }}</td>
|
||||||
<td>{{ submission.submitted_at|date:"M d, Y H:i" }}</td>
|
<td>{{ submission.submitted_at|date:"M d, Y H:i" }}</td>
|
||||||
<td class="text-end">
|
<td class="text-end">
|
||||||
<a href="{% url 'form_submission_details' template_id=template.id submission_id=submission.id %}" class="btn btn-sm btn-outline-primary">
|
<a href="{% url 'form_submission_details' template_id=template.id submission_id=submission.id %}" class="btn btn-sm btn-outline-primary">
|
||||||
<i class="fas fa-eye me-1"></i> {% trans "View Details" %}
|
<i class="fas fa-eye me-1"></i> {% trans "View Details" %}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@ -505,7 +505,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<nav
|
<nav
|
||||||
id="topNavbar"
|
id="topNavbar"
|
||||||
class="navbar navbar-expand-lg sticky-top"
|
class="navbar navbar-expand-lg"
|
||||||
style="background-color: white; z-index: 1030"
|
style="background-color: white; z-index: 1030"
|
||||||
>
|
>
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
|
|||||||
@ -193,14 +193,14 @@
|
|||||||
{% if form.salary_range.errors %}<div class="text-danger small mt-1">{{ form.salary_range.errors }}</div>{% endif %}
|
{% if form.salary_range.errors %}<div class="text-danger small mt-1">{{ form.salary_range.errors }}</div>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
{% comment %} <div class="col-md-6">
|
||||||
<div>
|
<div>
|
||||||
<label for="{{ form.application_url.id_for_label }}" class="form-label">{% trans "Application URL" %} <span class="text-danger">*</span></label>
|
<label for="{{ form.application_url.id_for_label }}" class="form-label">{% trans "Application URL" %} <span class="text-danger">*</span></label>
|
||||||
{{ form.application_url }}
|
{{ form.application_url }}
|
||||||
{% if form.application_url.errors %}<div class="text-danger small mt-1">{{ form.application_url.errors }}</div>{% endif %}
|
{% 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 class="form-text">{% trans "Full URL where candidates will apply" %}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> {% endcomment %}
|
||||||
|
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@ -199,14 +199,14 @@
|
|||||||
{% if form.salary_range.errors %}<div class="text-danger small mt-1">{{ form.salary_range.errors }}</div>{% endif %}
|
{% if form.salary_range.errors %}<div class="text-danger small mt-1">{{ form.salary_range.errors }}</div>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
{% comment %} <div class="col-md-6">
|
||||||
<div>
|
<div>
|
||||||
<label for="{{ form.application_url.id_for_label }}" class="form-label">{% trans "Application URL" %} <span class="text-danger">*</span></label>
|
<label for="{{ form.application_url.id_for_label }}" class="form-label">{% trans "Application URL" %} <span class="text-danger">*</span></label>
|
||||||
{{ form.application_url }}
|
{{ form.application_url }}
|
||||||
{% if form.application_url.errors %}<div class="text-danger small mt-1">{{ form.application_url.errors }}</div>{% endif %}
|
{% 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 class="form-text">{% trans "Full URL where candidates will apply" %}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> {% endcomment %}
|
||||||
|
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@ -409,11 +409,11 @@
|
|||||||
<i class="fas fa-edit"></i> {% trans "Edit Job" %}
|
<i class="fas fa-edit"></i> {% trans "Edit Job" %}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{% if job.application_url %}
|
|
||||||
<button type="button" class="btn btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#myModalForm">
|
<button type="button" class="btn btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#myModalForm">
|
||||||
<i class="fas fa-image me-1"></i> {% trans "Upload Image for Post" %}
|
<i class="fas fa-image me-1"></i> {% trans "Upload Image for Post" %}
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -590,7 +590,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
|
<button type="button" class="btn btn-outline-secondary btn-lg" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
|
||||||
<button type="submit" class="btn btn-main-action">{% trans "Save Changes" %}</button>
|
<button type="submit" class="btn btn-main-action">{% trans "Save Changes" %}</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -246,7 +246,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{job.form_template}}
|
|
||||||
<div class="mobile-fixed-apply-bar d-lg-none">
|
<div class="mobile-fixed-apply-bar d-lg-none">
|
||||||
{% if job.form_template %}
|
{% if job.form_template %}
|
||||||
<a href="{% url 'form_wizard' job.form_template.pk %}" class="btn btn-main-action btn-lg w-100">
|
<a href="{% url 'form_wizard' job.form_template.pk %}" class="btn btn-main-action btn-lg w-100">
|
||||||
|
|||||||
@ -1,17 +1,22 @@
|
|||||||
<div class="modal fade" id="myModalForm" tabindex="-1" aria-labelledby="myModalLabel" aria-hidden="true">
|
<div class="modal fade mt-4" id="myModalForm" tabindex="-1" aria-labelledby="myModalLabel" aria-hidden="true">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title" id="myModalLabel">Add New Image</h5>
|
<h5 class="modal-title" id="myModalLabel">Add New Image for the Post</h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form method="post" action="{% url 'job_detail' job.slug %}">
|
<form method="post" action="{% url 'job_image_upload' job.slug %}" enctype="multipart/form-data" >
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ image_form }}
|
{{ image_upload_form.as_p}}
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
{% if image_upload_form.instance.post_image %}
|
||||||
<button type="submit" class="btn btn-primary">Save changes</button>
|
<p>Current Image:</p>
|
||||||
|
<img src="{{ image_upload_form.instance.post_image.url }}" alt="Post Image" style="max-width: 200px;">
|
||||||
|
{% endif %}
|
||||||
|
<div class="modal-footer mt-2">
|
||||||
|
<button type="button" class="btn btn-lg btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
|
<button type="submit" class="btn btn-main-action ">Save changes</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user