diff --git a/recruitment/__pycache__/forms.cpython-312.pyc b/recruitment/__pycache__/forms.cpython-312.pyc
index 3a0a81a..54a2743 100644
Binary files a/recruitment/__pycache__/forms.cpython-312.pyc and b/recruitment/__pycache__/forms.cpython-312.pyc differ
diff --git a/recruitment/__pycache__/models.cpython-312.pyc b/recruitment/__pycache__/models.cpython-312.pyc
index 84a3449..649b789 100644
Binary files a/recruitment/__pycache__/models.cpython-312.pyc and b/recruitment/__pycache__/models.cpython-312.pyc differ
diff --git a/recruitment/__pycache__/urls.cpython-312.pyc b/recruitment/__pycache__/urls.cpython-312.pyc
index 06a411f..3222e0f 100644
Binary files a/recruitment/__pycache__/urls.cpython-312.pyc and b/recruitment/__pycache__/urls.cpython-312.pyc differ
diff --git a/recruitment/__pycache__/views.cpython-312.pyc b/recruitment/__pycache__/views.cpython-312.pyc
index 675f4d0..3b9e9d6 100644
Binary files a/recruitment/__pycache__/views.cpython-312.pyc and b/recruitment/__pycache__/views.cpython-312.pyc differ
diff --git a/recruitment/forms.py b/recruitment/forms.py
index 0e969c6..3d33c2a 100644
--- a/recruitment/forms.py
+++ b/recruitment/forms.py
@@ -584,11 +584,12 @@ class InterviewScheduleForm(forms.ModelForm):
class Meta:
model = InterviewSchedule
fields = [
- 'candidates', 'start_date', 'end_date', 'working_days',
+ 'candidates', 'interview_type', 'start_date', 'end_date', 'working_days',
'start_time', 'end_time', 'interview_duration', 'buffer_time',
'break_start_time', 'break_end_time'
]
widgets = {
+ 'interview_type': forms.Select(attrs={'class': 'form-control'}),
'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'}),
@@ -1326,7 +1327,8 @@ class CandidateEmailForm(forms.Form):
"""Generate initial message with candidate and meeting information"""
candidate=self.candidates.first()
message_parts=[]
- if candidate.stage == 'Applied':
+
+ if candidate and candidate.stage == 'Applied':
message_parts = [
f"Than you, for your interest in the {self.job.title} role.",
f"We regret to inform you that you were not selected to move forward to the exam round at this time.",
@@ -1335,7 +1337,7 @@ class CandidateEmailForm(forms.Form):
f"Wishing you the best in your job search,",
f"The KAAUH Hiring team"
]
- elif candidate.stage == 'Exam':
+ elif candidate and candidate.stage == 'Exam':
message_parts = [
f"Than you,for your interest in the {self.job.title} role.",
f"We're pleased to inform you that your initial screening was successful!",
@@ -1346,7 +1348,7 @@ class CandidateEmailForm(forms.Form):
f"Best regards, The KAAUH Hiring team"
]
- elif candidate.stage == 'Interview':
+ elif candidate and candidate.stage == 'Interview':
message_parts = [
f"Than you, for your interest in the {self.job.title} role.",
f"We're pleased to inform you that your initial screening was successful!",
@@ -1357,7 +1359,7 @@ class CandidateEmailForm(forms.Form):
f"Best regards, The KAAUH Hiring team"
]
- elif candidate.stage == 'Offer':
+ elif candidate and candidate.stage == 'Offer':
message_parts = [
f"Congratulations, ! We are delighted to inform you that we are extending a formal offer of employment for the {self.job.title} role.",
f"This is an exciting moment, and we look forward to having you join the KAAUH team.",
@@ -1366,7 +1368,7 @@ class CandidateEmailForm(forms.Form):
f"Welcome to the team!",
f"Best regards, The KAAUH Hiring team"
]
- elif candidate.stage == 'Hired':
+ elif candidate and candidate.stage == 'Hired':
message_parts = [
f"Welcome aboard,!",
f"We are thrilled to officially confirm your employment as our new {self.job.title}.",
@@ -1589,6 +1591,17 @@ KAAUH HIRING TEAM
self.initial['message_for_agency'] = agency_message.strip()
self.initial['message_for_participants'] = participants_message.strip()
+
+
+
+class InterviewScheduleLocationForm(forms.ModelForm):
+ class Meta:
+ model=InterviewSchedule
+ fields=['location']
+ widgets={
+ 'location': forms.TextInput(attrs={'placeholder': 'Enter Interview Location'}),
+ }
+
diff --git a/recruitment/migrations/0005_scheduledinterview_meeting_type.py b/recruitment/migrations/0005_scheduledinterview_meeting_type.py
new file mode 100644
index 0000000..fe94194
--- /dev/null
+++ b/recruitment/migrations/0005_scheduledinterview_meeting_type.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.2.7 on 2025-11-09 11:06
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('recruitment', '0004_remove_jobposting_participants_and_more'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='scheduledinterview',
+ name='meeting_type',
+ field=models.CharField(choices=[('Remote', 'Remote Interview'), ('Onsite', 'In-Person Interview')], default='Remote', max_length=10, verbose_name='Interview Meeting Type'),
+ ),
+ ]
diff --git a/recruitment/migrations/0006_remove_scheduledinterview_meeting_type_and_more.py b/recruitment/migrations/0006_remove_scheduledinterview_meeting_type_and_more.py
new file mode 100644
index 0000000..9270e59
--- /dev/null
+++ b/recruitment/migrations/0006_remove_scheduledinterview_meeting_type_and_more.py
@@ -0,0 +1,22 @@
+# Generated by Django 5.2.7 on 2025-11-09 11:12
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('recruitment', '0005_scheduledinterview_meeting_type'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='scheduledinterview',
+ name='meeting_type',
+ ),
+ migrations.AddField(
+ model_name='interviewschedule',
+ name='meeting_type',
+ field=models.CharField(choices=[('Remote', 'Remote Interview'), ('Onsite', 'In-Person Interview')], default='Remote', max_length=10, verbose_name='Interview Meeting Type'),
+ ),
+ ]
diff --git a/recruitment/migrations/0007_rename_meeting_type_interviewschedule_interview_type.py b/recruitment/migrations/0007_rename_meeting_type_interviewschedule_interview_type.py
new file mode 100644
index 0000000..85e6f83
--- /dev/null
+++ b/recruitment/migrations/0007_rename_meeting_type_interviewschedule_interview_type.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.2.7 on 2025-11-09 11:30
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('recruitment', '0006_remove_scheduledinterview_meeting_type_and_more'),
+ ]
+
+ operations = [
+ migrations.RenameField(
+ model_name='interviewschedule',
+ old_name='meeting_type',
+ new_name='interview_type',
+ ),
+ ]
diff --git a/recruitment/migrations/0008_interviewschedule_location.py b/recruitment/migrations/0008_interviewschedule_location.py
new file mode 100644
index 0000000..2800b75
--- /dev/null
+++ b/recruitment/migrations/0008_interviewschedule_location.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.2.7 on 2025-11-09 12:37
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('recruitment', '0007_rename_meeting_type_interviewschedule_interview_type'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='interviewschedule',
+ name='location',
+ field=models.CharField(blank=True, default='Remote', null=True),
+ ),
+ ]
diff --git a/recruitment/migrations/0009_alter_zoommeeting_meeting_id.py b/recruitment/migrations/0009_alter_zoommeeting_meeting_id.py
new file mode 100644
index 0000000..602917a
--- /dev/null
+++ b/recruitment/migrations/0009_alter_zoommeeting_meeting_id.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.2.7 on 2025-11-09 13:43
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('recruitment', '0008_interviewschedule_location'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='zoommeeting',
+ name='meeting_id',
+ field=models.CharField(blank=True, db_index=True, max_length=20, null=True, unique=True, verbose_name='Meeting ID'),
+ ),
+ ]
diff --git a/recruitment/migrations/0010_alter_zoommeeting_meeting_id.py b/recruitment/migrations/0010_alter_zoommeeting_meeting_id.py
new file mode 100644
index 0000000..d64f578
--- /dev/null
+++ b/recruitment/migrations/0010_alter_zoommeeting_meeting_id.py
@@ -0,0 +1,19 @@
+# Generated by Django 5.2.7 on 2025-11-09 13:48
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('recruitment', '0009_alter_zoommeeting_meeting_id'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='zoommeeting',
+ name='meeting_id',
+ field=models.CharField(db_index=True, default=1, max_length=20, unique=True, verbose_name='Meeting ID'),
+ preserve_default=False,
+ ),
+ ]
diff --git a/recruitment/migrations/0011_alter_scheduledinterview_zoom_meeting.py b/recruitment/migrations/0011_alter_scheduledinterview_zoom_meeting.py
new file mode 100644
index 0000000..dba9c62
--- /dev/null
+++ b/recruitment/migrations/0011_alter_scheduledinterview_zoom_meeting.py
@@ -0,0 +1,19 @@
+# Generated by Django 5.2.7 on 2025-11-09 13:50
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('recruitment', '0010_alter_zoommeeting_meeting_id'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='scheduledinterview',
+ name='zoom_meeting',
+ field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='interview', to='recruitment.zoommeeting'),
+ ),
+ ]
diff --git a/recruitment/models.py b/recruitment/models.py
index b712d0c..a4c54d2 100644
--- a/recruitment/models.py
+++ b/recruitment/models.py
@@ -742,6 +742,7 @@ class ZoomMeeting(Base):
topic = models.CharField(max_length=255, verbose_name=_("Topic"))
meeting_id = models.CharField(
db_index=True, max_length=20, unique=True, verbose_name=_("Meeting ID") # Added index
+
) # Unique identifier for the meeting
start_time = models.DateTimeField(db_index=True, verbose_name=_("Start Time")) # Added index
duration = models.PositiveIntegerField(
@@ -1599,6 +1600,20 @@ class BreakTime(models.Model):
class InterviewSchedule(Base):
"""Stores the scheduling criteria for interviews"""
+ """Stores individual scheduled interviews"""
+
+ class InterviewType(models.TextChoices):
+ REMOTE = 'Remote', 'Remote Interview'
+ ONSITE = 'Onsite', 'In-Person Interview'
+
+ interview_type = models.CharField(
+ max_length=10,
+ choices=InterviewType.choices,
+ default=InterviewType.REMOTE,
+ verbose_name="Interview Meeting Type"
+ )
+
+ location=models.CharField(null=True,blank=True,default='Remote')
job = models.ForeignKey(
JobPosting, on_delete=models.CASCADE, related_name="interview_schedules", db_index=True
@@ -1636,14 +1651,16 @@ class InterviewSchedule(Base):
class ScheduledInterview(Base):
- """Stores individual scheduled interviews"""
-
+
+ #for one candidate
candidate = models.ForeignKey(
Candidate,
on_delete=models.CASCADE,
related_name="scheduled_interviews",
db_index=True
)
+
+
participants = models.ManyToManyField('Participants', blank=True)
system_users=models.ManyToManyField(User,blank=True)
@@ -1653,7 +1670,8 @@ class ScheduledInterview(Base):
"JobPosting", on_delete=models.CASCADE, related_name="scheduled_interviews", db_index=True
)
zoom_meeting = models.OneToOneField(
- ZoomMeeting, on_delete=models.CASCADE, related_name="interview", db_index=True
+ ZoomMeeting, on_delete=models.CASCADE, related_name="interview", db_index=True,
+ null=True, blank=True
)
schedule = models.ForeignKey(
InterviewSchedule, on_delete=models.CASCADE, related_name="interviews",null=True,blank=True, db_index=True
diff --git a/recruitment/tasks.py b/recruitment/tasks.py
index 415aebc..4515e1b 100644
--- a/recruitment/tasks.py
+++ b/recruitment/tasks.py
@@ -461,6 +461,7 @@ def create_interview_and_meeting(
meeting_topic = f"Interview for {job.title} - {candidate.name}"
# 1. External API Call (Slow)
+
result = create_zoom_meeting(meeting_topic, interview_datetime, duration)
if result["status"] == "success":
diff --git a/recruitment/urls.py b/recruitment/urls.py
index e8ed14e..ad838ea 100644
--- a/recruitment/urls.py
+++ b/recruitment/urls.py
@@ -234,5 +234,8 @@ urlpatterns = [
path('jobs/
+ {% trans "Candidate" %}: {% if meeting.interview %}{{ meeting.interview.candidate.name }}{% else %}
+
+ {% endif %} {% trans "Create your first meeting or adjust your filters." %}
+ {% trans "Zoom Meetings" %}
+
+
+ {% trans "Create Meeting" %}
+
+ {{ meeting.topic }}
+
+ {{ meeting.status|title }}
+
+
+ {% trans "Job" %}: {% if meeting.interview %}{{ meeting.interview.job.title }}{% else %}
+
+ {% endif %}
+ {% trans "ID" %}: {{ meeting.meeting_id|default:meeting.id }}
+ {% trans "Start" %}: {{ meeting.start_time|date:"M d, Y H:i" }}
+ {% trans "Duration" %}: {{ meeting.duration }} minutes{% if meeting.password %}
{% trans "Password" %}: Yes{% endif %}
+
+
+
+
+
+
+
+ {% for meeting in meetings %}
+ {% trans "Topic" %}
+ {% trans "Candidate" %}
+ {% trans "Job" %}
+ {% trans "ID" %}
+ {% trans "Start Time" %}
+ {% trans "Duration" %}
+ {% trans "Status" %}
+ {% trans "Actions" %}
+
+
+ {% endfor %}
+
+ {{ meeting.topic }}
+
+ {% if meeting.interview %}
+ {{ meeting.interview.candidate.name }}
+ {% else %}
+
+ {% endif %}
+
+
+ {% if meeting.interview %}
+ {{ meeting.interview.job.title }}
+ {% else %}
+
+ {% endif %}
+
+ {{ meeting.meeting_id|default:meeting.id }}
+ {{ meeting.start_time|date:"M d, Y H:i" }}
+ {{ meeting.duration }} min
+
+ {% if meeting %}
+
+ {% if meeting.status == 'started' %}
+
+ {% endif %}
+ {{ meeting.status|title }}
+
+ {% else %}
+ --
+ {% endif %}
+
+
+
+
+ {% trans "No Zoom meetings found" %}
+
Interview Type: {{interview_type}}
diff --git a/templates/interviews/schedule_interview_location_form.html b/templates/interviews/schedule_interview_location_form.html new file mode 100644 index 0000000..ca21030 --- /dev/null +++ b/templates/interviews/schedule_interview_location_form.html @@ -0,0 +1,34 @@ +{% extends 'base.html' %} +{% load crispy_forms_tags %} + +{% block content %} +