image upload
This commit is contained in:
parent
67a951c45a
commit
7b02120508
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,10 +231,10 @@
|
|||||||
<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>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@ -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