616 lines
24 KiB
Python
616 lines
24 KiB
Python
from django import forms
|
|
from .validators import validate_hash_tags
|
|
from django.core.validators import URLValidator
|
|
from django.forms.formsets import formset_factory
|
|
from django.utils.translation import gettext_lazy as _
|
|
from crispy_forms.helper import FormHelper
|
|
from crispy_forms.layout import Layout, Submit, Row, Column, Field, Div
|
|
from .models import (
|
|
ZoomMeeting, Candidate,TrainingMaterial,JobPosting,
|
|
FormTemplate,InterviewSchedule,BreakTime,JobPostingImage,MeetingComment,ScheduledInterview
|
|
)
|
|
# from django_summernote.widgets import SummernoteWidget
|
|
from django_ckeditor_5.widgets import CKEditor5Widget
|
|
|
|
|
|
|
|
class CandidateForm(forms.ModelForm):
|
|
class Meta:
|
|
model = Candidate
|
|
fields = ['job', 'first_name', 'last_name', 'phone', 'email', 'resume',]
|
|
labels = {
|
|
'first_name': _('First Name'),
|
|
'last_name': _('Last Name'),
|
|
'phone': _('Phone'),
|
|
'email': _('Email'),
|
|
'resume': _('Resume'),
|
|
}
|
|
widgets = {
|
|
'first_name': forms.TextInput(attrs={'class': 'form-control', 'placeholder': _('Enter first name')}),
|
|
'last_name': forms.TextInput(attrs={'class': 'form-control', 'placeholder': _('Enter last name')}),
|
|
'phone': forms.TextInput(attrs={'class': 'form-control', 'placeholder': _('Enter phone number')}),
|
|
'email': forms.EmailInput(attrs={'class': 'form-control', 'placeholder': _('Enter email')}),
|
|
'stage': forms.Select(attrs={'class': 'form-select'}),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.helper = FormHelper()
|
|
self.helper.form_method = 'post'
|
|
self.helper.form_class = 'form-horizontal'
|
|
self.helper.label_class = 'col-md-3'
|
|
self.helper.field_class = 'col-md-9'
|
|
|
|
# Make job field read-only if it's being pre-populated
|
|
job_value = self.initial.get('job')
|
|
if job_value:
|
|
self.fields['job'].widget.attrs['readonly'] = True
|
|
|
|
self.helper.layout = Layout(
|
|
Field('job', css_class='form-control'),
|
|
Field('first_name', css_class='form-control'),
|
|
Field('last_name', css_class='form-control'),
|
|
Field('phone', css_class='form-control'),
|
|
Field('email', css_class='form-control'),
|
|
Field('stage', css_class='form-control'),
|
|
Field('resume', css_class='form-control'),
|
|
Submit('submit', _('Submit'), css_class='btn btn-primary')
|
|
)
|
|
|
|
class CandidateStageForm(forms.ModelForm):
|
|
"""Form specifically for updating candidate stage with validation"""
|
|
|
|
class Meta:
|
|
model = Candidate
|
|
fields = ['stage']
|
|
labels = {
|
|
'stage': _('New Application Stage'),
|
|
}
|
|
widgets = {
|
|
'stage': forms.Select(attrs={'class': 'form-select'}),
|
|
}
|
|
|
|
# def __init__(self, *args, **kwargs):
|
|
# # Get the current candidate instance for validation
|
|
# self.candidate = kwargs.pop('candidate', None)
|
|
# super().__init__(*args, **kwargs)
|
|
|
|
# # Dynamically filter stage choices based on current stage
|
|
# if self.candidate and self.candidate.pk:
|
|
# current_stage = self.candidate.stage
|
|
# available_stages = self.candidate.get_available_stages()
|
|
|
|
# # Filter choices to only include available stages
|
|
# choices = [(stage, self.candidate.Stage(stage).label)
|
|
# for stage in available_stages]
|
|
# self.fields['stage'].choices = choices
|
|
|
|
# # Set initial value to current stage
|
|
# self.fields['stage'].initial = current_stage
|
|
# else:
|
|
# # For new candidates, only show 'Applied' stage
|
|
# self.fields['stage'].choices = [('Applied', _('Applied'))]
|
|
# self.fields['stage'].initial = 'Applied'
|
|
|
|
# def clean_stage(self):
|
|
# """Validate stage transition"""
|
|
# new_stage = self.cleaned_data.get('stage')
|
|
# if not new_stage:
|
|
# raise forms.ValidationError(_('Please select a stage.'))
|
|
|
|
# # Use model validation for stage transitions
|
|
# if self.candidate and self.candidate.pk:
|
|
# current_stage = self.candidate.stage
|
|
# if new_stage != current_stage:
|
|
# if not self.candidate.can_transition_to(new_stage):
|
|
# allowed_stages = self.candidate.get_available_stages()
|
|
# raise forms.ValidationError(
|
|
# _('Cannot transition from "%(current)s" to "%(new)s". '
|
|
# 'Allowed transitions: %(allowed)s') % {
|
|
# 'current': current_stage,
|
|
# 'new': new_stage,
|
|
# 'allowed': ', '.join(allowed_stages) or 'None (final stage)'
|
|
# }
|
|
# )
|
|
|
|
# return new_stage
|
|
|
|
class ZoomMeetingForm(forms.ModelForm):
|
|
class Meta:
|
|
model = ZoomMeeting
|
|
fields = ['topic', 'start_time', 'duration']
|
|
labels = {
|
|
'topic': _('Topic'),
|
|
'start_time': _('Start Time'),
|
|
'duration': _('Duration'),
|
|
}
|
|
widgets = {
|
|
'topic': forms.TextInput(attrs={'class': 'form-control', 'placeholder': _('Enter meeting topic'),}),
|
|
'start_time': forms.DateTimeInput(attrs={'class': 'form-control','type': 'datetime-local'}),
|
|
'duration': forms.NumberInput(attrs={'class': 'form-control','min': 1, 'placeholder': _('60')}),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.helper = FormHelper()
|
|
self.helper.form_method = 'post'
|
|
self.helper.form_class = 'form-horizontal'
|
|
self.helper.label_class = 'col-md-3'
|
|
self.helper.field_class = 'col-md-9'
|
|
self.helper.layout = Layout(
|
|
Field('topic', css_class='form-control'),
|
|
Field('start_time', css_class='form-control'),
|
|
Field('duration', css_class='form-control'),
|
|
Submit('submit', _('Create Meeting'), css_class='btn btn-primary')
|
|
)
|
|
|
|
# Old JobForm removed - replaced by JobPostingForm
|
|
|
|
class TrainingMaterialForm(forms.ModelForm):
|
|
class Meta:
|
|
model = TrainingMaterial
|
|
fields = ['title', 'content', 'video_link', 'file']
|
|
labels = {
|
|
'title': _('Title'),
|
|
'content': _('Content'),
|
|
'video_link': _('Video Link'),
|
|
'file': _('File'),
|
|
}
|
|
widgets = {
|
|
'title': forms.TextInput(attrs={'class': 'form-control', 'placeholder': _('Enter material title')}),
|
|
# 💡 Use SummernoteWidget here
|
|
# 'content': SummernoteWidget(attrs={'placeholder': _('Enter material content')}),
|
|
'video_link': forms.URLInput(attrs={'class': 'form-control', 'placeholder': _('https://www.youtube.com/watch?v=...')}),
|
|
'file': forms.FileInput(attrs={'class': 'form-control'}),
|
|
}
|
|
|
|
# The __init__ and FormHelper layout remains the same
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.helper = FormHelper()
|
|
self.helper.form_method = 'post'
|
|
self.helper.form_class = 'g-3'
|
|
|
|
self.helper.layout = Layout(
|
|
'title',
|
|
'content', # Summernote is applied via the widgets dictionary
|
|
Row(
|
|
Column('video_link', css_class='col-md-6'),
|
|
Column('file', css_class='col-md-6'),
|
|
css_class='g-3 mb-4'
|
|
),
|
|
Div(
|
|
Submit('submit', _('Create Material'),
|
|
css_class='btn btn-main-action'),
|
|
css_class='col-12 mt-4'
|
|
)
|
|
)
|
|
|
|
|
|
class JobPostingForm(forms.ModelForm):
|
|
"""Form for creating and editing job postings"""
|
|
|
|
class Meta:
|
|
model = JobPosting
|
|
fields = [
|
|
'title', 'department', 'job_type', 'workplace_type',
|
|
'location_city', 'location_state', 'location_country',
|
|
'description', 'qualifications', 'salary_range', 'benefits','application_start_date'
|
|
,'application_deadline', 'application_instructions',
|
|
'position_number', 'reporting_to', 'joining_date',
|
|
'created_by','open_positions','hash_tags','max_applications'
|
|
]
|
|
widgets = {
|
|
# Basic Information
|
|
'title': forms.TextInput(attrs={
|
|
'class': 'form-control',
|
|
'placeholder': 'Assistant Professor of Computer Science',
|
|
'required': True
|
|
}),
|
|
'department': forms.TextInput(attrs={
|
|
'class': 'form-control',
|
|
'placeholder': 'Computer Science, Human Resources, etc.'
|
|
}),
|
|
'job_type': forms.Select(attrs={
|
|
'class': 'form-select',
|
|
'required': True
|
|
}),
|
|
'workplace_type': forms.Select(attrs={
|
|
'class': 'form-select',
|
|
'required': True
|
|
}),
|
|
|
|
# Location
|
|
'location_city': forms.TextInput(attrs={
|
|
'class': 'form-control',
|
|
'placeholder': 'Boston'
|
|
}),
|
|
'location_state': forms.TextInput(attrs={
|
|
'class': 'form-control',
|
|
'placeholder': 'MA'
|
|
}),
|
|
'location_country': forms.TextInput(attrs={
|
|
'class': 'form-control',
|
|
'value': 'United States'
|
|
}),
|
|
|
|
|
|
'salary_range': forms.TextInput(attrs={
|
|
'class': 'form-control',
|
|
'placeholder': '$60,000 - $80,000'
|
|
}),
|
|
|
|
|
|
# Application Information
|
|
# 'application_url': forms.URLInput(attrs={
|
|
# 'class': 'form-control',
|
|
# 'placeholder': 'https://university.edu/careers/job123',
|
|
# 'required': True
|
|
# }),
|
|
'application_start_date': forms.DateInput(attrs={
|
|
'class': 'form-control',
|
|
'type': 'date'
|
|
}),
|
|
'application_deadline': forms.DateInput(attrs={
|
|
'class': 'form-control',
|
|
'type': 'date'
|
|
}),
|
|
|
|
'open_positions': forms.NumberInput(attrs={
|
|
'class': 'form-control',
|
|
'min': 1,
|
|
'placeholder': 'Number of open positions'
|
|
}),
|
|
'hash_tags': forms.TextInput(attrs={
|
|
'class': 'form-control',
|
|
'placeholder': '#hiring,#jobopening',
|
|
# 'validators':validate_hash_tags, # Assuming this is available
|
|
}),
|
|
|
|
# Internal Information
|
|
'position_number': forms.TextInput(attrs={
|
|
'class': 'form-control',
|
|
'placeholder': 'UNIV-2025-001'
|
|
}),
|
|
'reporting_to': forms.TextInput(attrs={
|
|
'class': 'form-control',
|
|
'placeholder': 'Department Chair, Director, etc.'
|
|
}),
|
|
'joining_date': forms.DateInput(attrs={
|
|
'class': 'form-control',
|
|
'type': 'date'
|
|
}),
|
|
|
|
'created_by': forms.TextInput(attrs={
|
|
'class': 'form-control',
|
|
'placeholder': 'University Administrator'
|
|
}),
|
|
'max_applications': forms.NumberInput(attrs={
|
|
'class': 'form-control',
|
|
'min': 1,
|
|
'placeholder': 'Maximum number of applicants'
|
|
}),
|
|
}
|
|
|
|
def __init__(self,*args,**kwargs):
|
|
|
|
# Extract your custom argument BEFORE calling super()
|
|
self.is_anonymous_user = kwargs.pop('is_anonymous_user', False)
|
|
# Now call the parent __init__ with remaining args
|
|
super().__init__(*args, **kwargs)
|
|
|
|
if not self.instance.pk:# Creating new job posting
|
|
if not self.is_anonymous_user:
|
|
self.fields['created_by'].initial = 'University Administrator'
|
|
# self.fields['status'].initial = 'Draft'
|
|
self.fields['location_city'].initial='Riyadh'
|
|
self.fields['location_state'].initial='Riyadh Province'
|
|
self.fields['location_country'].initial='Saudi Arabia'
|
|
|
|
|
|
def clean_hash_tags(self):
|
|
hash_tags=self.cleaned_data.get('hash_tags')
|
|
if hash_tags:
|
|
tags=[tag.strip() for tag in hash_tags.split(',') if tag.strip()]
|
|
for tag in tags:
|
|
if not tag.startswith('#'):
|
|
raise forms.ValidationError("Each hashtag must start with '#' symbol and must be comma(,) sepearted.")
|
|
return ','.join(tags)
|
|
return hash_tags # Allow blank
|
|
|
|
def clean_title(self):
|
|
title=self.cleaned_data.get('title')
|
|
if not title or len(title.strip())<3:
|
|
raise forms.ValidationError("Job title must be at least 3 characters long.")
|
|
if len(title)>200:
|
|
raise forms.ValidationError("Job title cannot exceed 200 characters.")
|
|
return title.strip()
|
|
|
|
def clean_description(self):
|
|
description=self.cleaned_data.get('description')
|
|
if not description or len(description.strip())<20:
|
|
raise forms.ValidationError("Job description must be at least 20 characters long.")
|
|
return description.strip() # to remove leading/trailing whitespace
|
|
|
|
def clean_application_url(self):
|
|
url=self.cleaned_data.get('application_url')
|
|
if url:
|
|
validator=URLValidator()
|
|
try:
|
|
validator(url)
|
|
except forms.ValidationError:
|
|
raise forms.ValidationError('Please enter a valid URL (e.g., https://example.com)')
|
|
return url
|
|
|
|
# def clean(self):
|
|
# """Cross-field validation"""
|
|
# cleaned_data = super().clean()
|
|
|
|
# # Validate dates
|
|
# start_date = cleaned_data.get('start_date')
|
|
# application_deadline = cleaned_data.get('application_deadline')
|
|
|
|
# # Perform cross-field validation only if both fields have values
|
|
# if start_date and application_deadline:
|
|
# if application_deadline > start_date:
|
|
# self.add_error('application_deadline',
|
|
# 'The application deadline must be set BEFORE the job start date.')
|
|
|
|
# # # Validate that if status is ACTIVE, we have required fields
|
|
# # status = cleaned_data.get('status')
|
|
# # if status == 'ACTIVE':
|
|
# # if not cleaned_data.get('application_url'):
|
|
# # self.add_error('application_url',
|
|
# # 'Application URL is required for active jobs.')
|
|
# # if not cleaned_data.get('description'):
|
|
# # self.add_error('description',
|
|
# # 'Job description is required for active jobs.')
|
|
|
|
# return cleaned_data
|
|
|
|
class JobPostingImageForm(forms.ModelForm):
|
|
class Meta:
|
|
model=JobPostingImage
|
|
fields=['post_image']
|
|
|
|
class FormTemplateForm(forms.ModelForm):
|
|
"""Form for creating form templates"""
|
|
class Meta:
|
|
model = FormTemplate
|
|
fields = ['job','name', 'description', 'is_active']
|
|
labels = {
|
|
'job': _('Job'),
|
|
'name': _('Template Name'),
|
|
'description': _('Description'),
|
|
'is_active': _('Active'),
|
|
}
|
|
widgets = {
|
|
'name': forms.TextInput(attrs={
|
|
'class': 'form-control',
|
|
'placeholder': _('Enter template name'),
|
|
'required': True
|
|
}),
|
|
'description': forms.Textarea(attrs={
|
|
'class': 'form-control',
|
|
'rows': 3,
|
|
'placeholder': _('Enter template description (optional)')
|
|
}),
|
|
'is_active': forms.CheckboxInput(attrs={
|
|
'class': 'form-check-input'
|
|
})
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.helper = FormHelper()
|
|
self.helper.form_method = 'post'
|
|
self.helper.form_class = 'form-horizontal'
|
|
self.helper.label_class = 'col-md-3'
|
|
self.helper.field_class = 'col-md-9'
|
|
self.helper.layout = Layout(
|
|
Field('job', css_class='form-control'),
|
|
Field('name', css_class='form-control'),
|
|
Field('description', css_class='form-control'),
|
|
Field('is_active', css_class='form-check-input'),
|
|
Submit('submit', _('Create Template'), css_class='btn btn-primary mt-3')
|
|
)
|
|
# class BreakTimeForm(forms.ModelForm):
|
|
# class Meta:
|
|
# model = BreakTime
|
|
# fields = ['start_time', 'end_time']
|
|
# widgets = {
|
|
# 'start_time': forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}),
|
|
# 'end_time': forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}),
|
|
# }
|
|
|
|
# BreakTimeFormSet = formset_factory(BreakTimeForm, extra=1, can_delete=True)
|
|
|
|
# class InterviewScheduleForm(forms.ModelForm):
|
|
# candidates = forms.ModelMultipleChoiceField(
|
|
# queryset=Candidate.objects.none(),
|
|
# widget=forms.CheckboxSelectMultiple,
|
|
# required=True
|
|
# )
|
|
# working_days = forms.MultipleChoiceField(
|
|
# choices=[
|
|
# (0, 'Monday'),
|
|
# (1, 'Tuesday'),
|
|
# (2, 'Wednesday'),
|
|
# (3, 'Thursday'),
|
|
# (4, 'Friday'),
|
|
# (5, 'Saturday'),
|
|
# (6, 'Sunday'),
|
|
# ],
|
|
# widget=forms.CheckboxSelectMultiple,
|
|
# required=True
|
|
# )
|
|
|
|
# class Meta:
|
|
# model = InterviewSchedule
|
|
# fields = [
|
|
# 'candidates', 'start_date', 'end_date', 'working_days',
|
|
# 'start_time', 'end_time', 'interview_duration', 'buffer_time'
|
|
# ]
|
|
# widgets = {
|
|
# 'start_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
|
|
# 'end_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
|
|
# 'start_time': forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}),
|
|
# 'end_time': forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}),
|
|
# 'interview_duration': forms.NumberInput(attrs={'class': 'form-control'}),
|
|
# 'buffer_time': forms.NumberInput(attrs={'class': 'form-control'}),
|
|
# }
|
|
|
|
# def __init__(self, slug, *args, **kwargs):
|
|
# super().__init__(*args, **kwargs)
|
|
# # Filter candidates based on the selected job
|
|
# self.fields['candidates'].queryset = Candidate.objects.filter(
|
|
# job__slug=slug,
|
|
# stage='Interview'
|
|
# )
|
|
|
|
# def clean_working_days(self):
|
|
# working_days = self.cleaned_data.get('working_days')
|
|
# # Convert string values to integers
|
|
# return [int(day) for day in working_days]
|
|
|
|
|
|
class JobPostingCancelReasonForm(forms.ModelForm):
|
|
class Meta:
|
|
model = JobPosting
|
|
fields = ['cancel_reason']
|
|
class JobPostingStatusForm(forms.ModelForm):
|
|
class Meta:
|
|
model = JobPosting
|
|
fields = ['status']
|
|
widgets = {
|
|
'status': forms.Select(attrs={'class': 'form-select'}),
|
|
}
|
|
class FormTemplateIsActiveForm(forms.ModelForm):
|
|
class Meta:
|
|
model = FormTemplate
|
|
fields = ['is_active']
|
|
|
|
class CandidateExamDateForm(forms.ModelForm):
|
|
class Meta:
|
|
model = Candidate
|
|
fields = ['exam_date']
|
|
widgets = {
|
|
'exam_date': forms.DateTimeInput(attrs={'type': 'datetime-local', 'class': 'form-control'}),
|
|
}
|
|
|
|
|
|
class BreakTimeForm(forms.Form):
|
|
"""
|
|
A simple Form used for the BreakTimeFormSet.
|
|
It is not a ModelForm because the data is stored directly in InterviewSchedule's JSONField,
|
|
not in a separate BreakTime model instance.
|
|
"""
|
|
start_time = forms.TimeField(
|
|
widget=forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}),
|
|
label="Start Time"
|
|
)
|
|
end_time = forms.TimeField(
|
|
widget=forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}),
|
|
label="End Time"
|
|
)
|
|
|
|
# Use the non-model form for the formset factory
|
|
BreakTimeFormSet = formset_factory(BreakTimeForm, extra=1, can_delete=True)
|
|
|
|
# --- InterviewScheduleForm remains unchanged ---
|
|
class InterviewScheduleForm(forms.ModelForm):
|
|
candidates = forms.ModelMultipleChoiceField(
|
|
queryset=Candidate.objects.none(),
|
|
widget=forms.CheckboxSelectMultiple,
|
|
required=True
|
|
)
|
|
working_days = forms.MultipleChoiceField(
|
|
choices=[
|
|
(0, 'Monday'), (1, 'Tuesday'), (2, 'Wednesday'), (3, 'Thursday'),
|
|
(4, 'Friday'), (5, 'Saturday'), (6, 'Sunday'),
|
|
],
|
|
widget=forms.CheckboxSelectMultiple,
|
|
required=True
|
|
)
|
|
|
|
class Meta:
|
|
model = InterviewSchedule
|
|
fields = [
|
|
'candidates', 'start_date', 'end_date', 'working_days',
|
|
'start_time', 'end_time', 'interview_duration', 'buffer_time',
|
|
'break_start_time', 'break_end_time'
|
|
]
|
|
widgets = {
|
|
'start_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
|
|
'end_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
|
|
'start_time': forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}),
|
|
'end_time': forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}),
|
|
'interview_duration': forms.NumberInput(attrs={'class': 'form-control'}),
|
|
'buffer_time': forms.NumberInput(attrs={'class': 'form-control'}),
|
|
'break_start_time': forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}),
|
|
'break_end_time': forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}),
|
|
}
|
|
|
|
def __init__(self, slug, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
# Filter candidates based on the selected job
|
|
self.fields['candidates'].queryset = Candidate.objects.filter(
|
|
job__slug=slug,
|
|
stage='Interview'
|
|
)
|
|
|
|
def clean_working_days(self):
|
|
working_days = self.cleaned_data.get('working_days')
|
|
# Convert string values to integers
|
|
return [int(day) for day in working_days]
|
|
|
|
class MeetingCommentForm(forms.ModelForm):
|
|
"""Form for creating and editing meeting comments"""
|
|
class Meta:
|
|
model = MeetingComment
|
|
fields = ['content']
|
|
widgets = {
|
|
'content': CKEditor5Widget(
|
|
attrs={'class': 'form-control', 'placeholder': _('Enter your comment or note')},
|
|
config_name='extends'
|
|
),
|
|
}
|
|
labels = {
|
|
'content': _('Comment'),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.helper = FormHelper()
|
|
self.helper.form_method = 'post'
|
|
self.helper.form_class = 'form-horizontal'
|
|
self.helper.label_class = 'col-md-3'
|
|
self.helper.field_class = 'col-md-9'
|
|
self.helper.layout = Layout(
|
|
Field('content', css_class='form-control'),
|
|
Submit('submit', _('Add Comment'), css_class='btn btn-primary mt-3')
|
|
)
|
|
|
|
# --- ScheduleInterviewForCandiateForm remains unchanged ---
|
|
class ScheduleInterviewForCandiateForm(forms.ModelForm):
|
|
|
|
class Meta:
|
|
model = InterviewSchedule
|
|
fields = ['start_date', 'end_date', 'start_time', 'end_time', 'interview_duration', 'buffer_time']
|
|
widgets = {
|
|
'start_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
|
|
'end_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
|
|
'start_time': forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}),
|
|
'end_time': forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}),
|
|
'interview_duration': forms.NumberInput(attrs={'class': 'form-control'}),
|
|
'buffer_time': forms.NumberInput(attrs={'class': 'form-control'}),
|
|
'break_start_time': forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}),
|
|
'break_end_time': forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}),
|
|
}
|
|
|
|
|
|
class InterviewForm(forms.ModelForm):
|
|
|
|
class Meta:
|
|
model = ScheduledInterview
|
|
fields = ['job','candidate'] |