changes
This commit is contained in:
parent
a28bfc11f3
commit
d0235bfefe
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -5,7 +5,7 @@ from django.utils import timezone
|
|||||||
from .models import (
|
from .models import (
|
||||||
JobPosting, Application, TrainingMaterial, ZoomMeetingDetails,
|
JobPosting, Application, TrainingMaterial, ZoomMeetingDetails,
|
||||||
FormTemplate, FormStage, FormField, FormSubmission, FieldResponse,
|
FormTemplate, FormStage, FormField, FormSubmission, FieldResponse,
|
||||||
SharedFormTemplate, Source, HiringAgency, IntegrationLog,InterviewSchedule,JobPostingImage,MeetingComment,
|
SharedFormTemplate, Source, HiringAgency, IntegrationLog,InterviewSchedule,JobPostingImage,InterviewNote,
|
||||||
AgencyAccessLink, AgencyJobAssignment
|
AgencyAccessLink, AgencyJobAssignment
|
||||||
)
|
)
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
|||||||
@ -18,7 +18,7 @@ from .models import (
|
|||||||
InterviewSchedule,
|
InterviewSchedule,
|
||||||
BreakTime,
|
BreakTime,
|
||||||
JobPostingImage,
|
JobPostingImage,
|
||||||
MeetingComment,
|
InterviewNote,
|
||||||
ScheduledInterview,
|
ScheduledInterview,
|
||||||
Source,
|
Source,
|
||||||
HiringAgency,
|
HiringAgency,
|
||||||
@ -26,7 +26,7 @@ from .models import (
|
|||||||
AgencyAccessLink,
|
AgencyAccessLink,
|
||||||
Participants,
|
Participants,
|
||||||
Message,
|
Message,
|
||||||
Person,OnsiteMeeting,
|
Person,OnsiteLocationDetails,
|
||||||
Document
|
Document
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -725,7 +725,7 @@ class InterviewScheduleForm(forms.ModelForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = InterviewSchedule
|
model = InterviewSchedule
|
||||||
fields = [
|
fields = [
|
||||||
'schedule_interview_type',
|
'schedule_interview_type',
|
||||||
"applications",
|
"applications",
|
||||||
|
|||||||
@ -983,137 +983,326 @@ class TrainingMaterial(Base):
|
|||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
|
|
||||||
class OnsiteMeeting(Base):
|
class InterviewLocation(Base):
|
||||||
class MeetingStatus(models.TextChoices):
|
"""
|
||||||
|
Base model for all interview location/meeting details (remote or onsite)
|
||||||
|
using Multi-Table Inheritance.
|
||||||
|
"""
|
||||||
|
class LocationType(models.TextChoices):
|
||||||
|
REMOTE = 'Remote', _('Remote (e.g., Zoom, Google Meet)')
|
||||||
|
ONSITE = 'Onsite', _('In-Person (Physical Location)')
|
||||||
|
|
||||||
|
class Status(models.TextChoices):
|
||||||
|
"""Defines the possible real-time statuses for any interview location/meeting."""
|
||||||
WAITING = "waiting", _("Waiting")
|
WAITING = "waiting", _("Waiting")
|
||||||
STARTED = "started", _("Started")
|
STARTED = "started", _("Started")
|
||||||
ENDED = "ended", _("Ended")
|
ENDED = "ended", _("Ended")
|
||||||
CANCELLED = "cancelled", _("Cancelled")
|
CANCELLED = "cancelled", _("Cancelled")
|
||||||
|
|
||||||
# Basic meeting details
|
location_type = models.CharField(
|
||||||
topic = models.CharField(max_length=255, verbose_name=_("Topic"))
|
max_length=10,
|
||||||
start_time = models.DateTimeField(
|
choices=LocationType.choices,
|
||||||
db_index=True, verbose_name=_("Start Time")
|
verbose_name=_("Location Type"),
|
||||||
) # Added index
|
db_index=True
|
||||||
duration = models.PositiveIntegerField(
|
)
|
||||||
verbose_name=_("Duration")
|
|
||||||
) # Duration in minutes
|
details_url = models.URLField(
|
||||||
timezone = models.CharField(max_length=50, verbose_name=_("Timezone"))
|
verbose_name=_("Meeting/Location URL"),
|
||||||
location = models.CharField(null=True, blank=True)
|
max_length=2048,
|
||||||
status = models.CharField(
|
|
||||||
db_index=True,
|
|
||||||
max_length=20, # Added index
|
|
||||||
null=True,
|
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name=_("Status"),
|
null=True
|
||||||
default=MeetingStatus.WAITING,
|
)
|
||||||
|
|
||||||
|
topic = models.CharField( # Renamed from 'description' to 'topic' to match your input
|
||||||
|
max_length=255,
|
||||||
|
verbose_name=_("Location/Meeting Topic"),
|
||||||
|
blank=True,
|
||||||
|
help_text=_("e.g., 'Zoom Topic: Software Interview' or 'Main Conference Room, 3rd Floor'")
|
||||||
|
)
|
||||||
|
|
||||||
|
timezone = models.CharField(
|
||||||
|
max_length=50,
|
||||||
|
verbose_name=_("Timezone"),
|
||||||
|
default='UTC'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
# Use 'topic' instead of 'description'
|
||||||
|
return f"{self.get_location_type_display()} - {self.topic[:50]}"
|
||||||
|
|
||||||
class ZoomMeeting(Base):
|
class Meta:
|
||||||
class MeetingStatus(models.TextChoices):
|
verbose_name = _("Interview Location")
|
||||||
WAITING = "waiting", _("Waiting")
|
verbose_name_plural = _("Interview Locations")
|
||||||
STARTED = "started", _("Started")
|
|
||||||
ENDED = "ended", _("Ended")
|
|
||||||
CANCELLED = "cancelled", _("Cancelled")
|
|
||||||
|
|
||||||
# Basic meeting details
|
|
||||||
topic = models.CharField(max_length=255, verbose_name=_("Topic"))
|
class ZoomMeetingDetails(InterviewLocation):
|
||||||
meeting_id = models.CharField(
|
"""Concrete model for remote interviews (Zoom specifics)."""
|
||||||
|
|
||||||
|
status = models.CharField(
|
||||||
db_index=True,
|
db_index=True,
|
||||||
max_length=20,
|
max_length=20,
|
||||||
unique=True,
|
choices=InterviewLocation.Status.choices,
|
||||||
verbose_name=_("Meeting ID"), # Added index
|
default=InterviewLocation.Status.WAITING,
|
||||||
) # Unique identifier for the meeting
|
)
|
||||||
start_time = models.DateTimeField(
|
start_time = models.DateTimeField(
|
||||||
db_index=True, verbose_name=_("Start Time")
|
db_index=True, verbose_name=_("Start Time")
|
||||||
) # Added index
|
)
|
||||||
duration = models.PositiveIntegerField(
|
duration = models.PositiveIntegerField(
|
||||||
verbose_name=_("Duration")
|
verbose_name=_("Duration (minutes)")
|
||||||
) # Duration in minutes
|
)
|
||||||
timezone = models.CharField(max_length=50, verbose_name=_("Timezone"))
|
meeting_id = models.CharField(
|
||||||
join_url = models.URLField(
|
db_index=True,
|
||||||
verbose_name=_("Join URL")
|
max_length=50,
|
||||||
) # URL for participants to join
|
unique=True,
|
||||||
participant_video = models.BooleanField(
|
verbose_name=_("External Meeting ID")
|
||||||
default=True, verbose_name=_("Participant Video")
|
|
||||||
)
|
)
|
||||||
password = models.CharField(
|
password = models.CharField(
|
||||||
max_length=20, blank=True, null=True, verbose_name=_("Password")
|
max_length=20, blank=True, null=True, verbose_name=_("Password")
|
||||||
)
|
)
|
||||||
|
zoom_gateway_response = models.JSONField(
|
||||||
|
blank=True, null=True, verbose_name=_("Zoom Gateway Response")
|
||||||
|
)
|
||||||
|
participant_video = models.BooleanField(
|
||||||
|
default=True, verbose_name=_("Participant Video")
|
||||||
|
)
|
||||||
join_before_host = models.BooleanField(
|
join_before_host = models.BooleanField(
|
||||||
default=False, verbose_name=_("Join Before Host")
|
default=False, verbose_name=_("Join Before Host")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
host_email=models.CharField(null=True,blank=True)
|
||||||
mute_upon_entry = models.BooleanField(
|
mute_upon_entry = models.BooleanField(
|
||||||
default=False, verbose_name=_("Mute Upon Entry")
|
default=False, verbose_name=_("Mute Upon Entry")
|
||||||
)
|
)
|
||||||
waiting_room = models.BooleanField(default=False, verbose_name=_("Waiting Room"))
|
waiting_room = models.BooleanField(default=False, verbose_name=_("Waiting Room"))
|
||||||
|
|
||||||
zoom_gateway_response = models.JSONField(
|
# *** REVERTED TO @classmethod (Factory Method) for cleaner instantiation ***
|
||||||
blank=True, null=True, verbose_name=_("Zoom Gateway Response")
|
# @classmethod
|
||||||
|
# def create(cls, **kwargs):
|
||||||
|
# """Factory method to ensure location_type is set to REMOTE."""
|
||||||
|
# return cls(location_type=InterviewLocation.LocationType.REMOTE, **kwargs)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("Zoom Meeting Details")
|
||||||
|
verbose_name_plural = _("Zoom Meeting Details")
|
||||||
|
|
||||||
|
|
||||||
|
class OnsiteLocationDetails(InterviewLocation):
|
||||||
|
"""Concrete model for onsite interviews (Room/Address specifics)."""
|
||||||
|
|
||||||
|
physical_address = models.CharField(
|
||||||
|
max_length=255,
|
||||||
|
verbose_name=_("Physical Address"),
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
room_number = models.CharField(
|
||||||
|
max_length=50,
|
||||||
|
verbose_name=_("Room Number/Name"),
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
start_time = models.DateTimeField(
|
||||||
|
db_index=True, verbose_name=_("Start Time")
|
||||||
|
)
|
||||||
|
duration = models.PositiveIntegerField(
|
||||||
|
verbose_name=_("Duration (minutes)")
|
||||||
)
|
)
|
||||||
status = models.CharField(
|
status = models.CharField(
|
||||||
db_index=True,
|
db_index=True,
|
||||||
max_length=20, # Added index
|
max_length=20,
|
||||||
|
choices=InterviewLocation.Status.choices,
|
||||||
|
default=InterviewLocation.Status.WAITING,
|
||||||
|
)
|
||||||
|
|
||||||
|
# *** REVERTED TO @classmethod (Factory Method) for cleaner instantiation ***
|
||||||
|
# @classmethod
|
||||||
|
# def create(cls, **kwargs):
|
||||||
|
# """Factory method to ensure location_type is set to ONSITE."""
|
||||||
|
# return cls(location_type=InterviewLocation.LocationType.ONSITE, **kwargs)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("Onsite Location Details")
|
||||||
|
verbose_name_plural = _("Onsite Location Details")
|
||||||
|
|
||||||
|
|
||||||
|
# --- 2. Scheduling Models ---
|
||||||
|
|
||||||
|
class InterviewSchedule(Base):
|
||||||
|
"""Stores the TEMPLATE criteria for BULK interview generation."""
|
||||||
|
|
||||||
|
# We need a field to store the template location details linked to this bulk schedule.
|
||||||
|
# This location object contains the generic Zoom/Onsite info to be cloned.
|
||||||
|
template_location = models.ForeignKey(
|
||||||
|
InterviewLocation,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
related_name="schedule_templates",
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name=_("Status"),
|
verbose_name=_("Location Template (Zoom/Onsite)")
|
||||||
default=MeetingStatus.WAITING,
|
)
|
||||||
|
|
||||||
|
# NOTE: schedule_interview_type field is needed in the form,
|
||||||
|
# but not on the model itself if we use template_location.
|
||||||
|
# If you want to keep it:
|
||||||
|
schedule_interview_type = models.CharField(
|
||||||
|
max_length=10,
|
||||||
|
choices=InterviewLocation.LocationType.choices,
|
||||||
|
verbose_name=_("Interview Type"),
|
||||||
|
default=InterviewLocation.LocationType.REMOTE
|
||||||
|
)
|
||||||
|
|
||||||
|
job = models.ForeignKey(
|
||||||
|
JobPosting,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="interview_schedules",
|
||||||
|
db_index=True,
|
||||||
|
)
|
||||||
|
applications = models.ManyToManyField(
|
||||||
|
Application, related_name="interview_schedules", blank=True
|
||||||
|
)
|
||||||
|
|
||||||
|
start_date = models.DateField(db_index=True, verbose_name=_("Start Date"))
|
||||||
|
end_date = models.DateField(db_index=True, verbose_name=_("End Date"))
|
||||||
|
|
||||||
|
working_days = models.JSONField(
|
||||||
|
verbose_name=_("Working Days")
|
||||||
|
)
|
||||||
|
|
||||||
|
start_time = models.TimeField(verbose_name=_("Start Time"))
|
||||||
|
end_time = models.TimeField(verbose_name=_("End Time"))
|
||||||
|
|
||||||
|
break_start_time = models.TimeField(
|
||||||
|
verbose_name=_("Break Start Time"), null=True, blank=True
|
||||||
|
)
|
||||||
|
break_end_time = models.TimeField(
|
||||||
|
verbose_name=_("Break End Time"), null=True, blank=True
|
||||||
|
)
|
||||||
|
|
||||||
|
interview_duration = models.PositiveIntegerField(
|
||||||
|
verbose_name=_("Interview Duration (minutes)")
|
||||||
|
)
|
||||||
|
buffer_time = models.PositiveIntegerField(
|
||||||
|
verbose_name=_("Buffer Time (minutes)"), default=0
|
||||||
|
)
|
||||||
|
created_by = models.ForeignKey(
|
||||||
|
User, on_delete=models.CASCADE, db_index=True
|
||||||
)
|
)
|
||||||
# Timestamps
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.topic
|
return f"Schedule for {self.job.title}"
|
||||||
|
|
||||||
@property
|
|
||||||
def get_job(self):
|
|
||||||
return self.interview.job
|
|
||||||
|
|
||||||
@property
|
|
||||||
def get_candidate(self):
|
|
||||||
return self.interview.application.person
|
|
||||||
|
|
||||||
@property
|
|
||||||
def candidate_full_name(self):
|
|
||||||
return self.interview.application.person.full_name
|
|
||||||
|
|
||||||
@property
|
|
||||||
def get_participants(self):
|
|
||||||
return self.interview.job.participants.all()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def get_users(self):
|
|
||||||
return self.interview.job.users.all()
|
|
||||||
|
|
||||||
|
|
||||||
class MeetingComment(Base):
|
class ScheduledInterview(Base):
|
||||||
"""
|
"""Stores individual scheduled interviews (whether bulk or individually created)."""
|
||||||
Model for storing meeting comments/notes
|
|
||||||
"""
|
class InterviewStatus(models.TextChoices):
|
||||||
|
SCHEDULED = "scheduled", _("Scheduled")
|
||||||
|
CONFIRMED = "confirmed", _("Confirmed")
|
||||||
|
CANCELLED = "cancelled", _("Cancelled")
|
||||||
|
COMPLETED = "completed", _("Completed")
|
||||||
|
|
||||||
meeting = models.ForeignKey(
|
application = models.ForeignKey(
|
||||||
ZoomMeeting,
|
Application,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
related_name="comments",
|
related_name="scheduled_interviews",
|
||||||
verbose_name=_("Meeting"),
|
db_index=True,
|
||||||
)
|
)
|
||||||
|
job = models.ForeignKey(
|
||||||
|
JobPosting,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="scheduled_interviews",
|
||||||
|
db_index=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Links to the specific, individual location/meeting details for THIS interview
|
||||||
|
interview_location = models.OneToOneField(
|
||||||
|
InterviewLocation,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
related_name="scheduled_interview",
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
db_index=True,
|
||||||
|
verbose_name=_("Meeting/Location Details")
|
||||||
|
)
|
||||||
|
|
||||||
|
# Link back to the bulk schedule template (optional if individually created)
|
||||||
|
schedule = models.ForeignKey(
|
||||||
|
InterviewSchedule,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
related_name="interviews",
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
db_index=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
participants = models.ManyToManyField('Participants', blank=True)
|
||||||
|
system_users = models.ManyToManyField(User, related_name="attended_interviews", blank=True)
|
||||||
|
|
||||||
|
interview_date = models.DateField(db_index=True, verbose_name=_("Interview Date"))
|
||||||
|
interview_time = models.TimeField(verbose_name=_("Interview Time"))
|
||||||
|
|
||||||
|
status = models.CharField(
|
||||||
|
db_index=True,
|
||||||
|
max_length=20,
|
||||||
|
choices=InterviewStatus.choices,
|
||||||
|
default=InterviewStatus.SCHEDULED,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Interview with {self.application.person.full_name} for {self.job.title}"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=["job", "status"]),
|
||||||
|
models.Index(fields=["interview_date", "interview_time"]),
|
||||||
|
models.Index(fields=["application", "job"]),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# --- 3. Interview Notes Model (Fixed) ---
|
||||||
|
|
||||||
|
class InterviewNote(Base):
|
||||||
|
"""Model for storing notes, feedback, or comments related to a specific ScheduledInterview."""
|
||||||
|
|
||||||
|
class NoteType(models.TextChoices):
|
||||||
|
FEEDBACK = 'Feedback', _('Candidate Feedback')
|
||||||
|
LOGISTICS = 'Logistics', _('Logistical Note')
|
||||||
|
GENERAL = 'General', _('General Comment')
|
||||||
|
|
||||||
|
1
|
||||||
|
interview = models.ForeignKey(
|
||||||
|
ScheduledInterview,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="notes",
|
||||||
|
verbose_name=_("Scheduled Interview"),
|
||||||
|
db_index=True
|
||||||
|
)
|
||||||
|
|
||||||
author = models.ForeignKey(
|
author = models.ForeignKey(
|
||||||
User,
|
User,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
related_name="meeting_comments",
|
related_name="interview_notes",
|
||||||
verbose_name=_("Author"),
|
verbose_name=_("Author"),
|
||||||
|
db_index=True
|
||||||
)
|
)
|
||||||
content = CKEditor5Field(verbose_name=_("Content"), config_name="extends")
|
|
||||||
# Inherited from Base: created_at, updated_at, slug
|
note_type = models.CharField(
|
||||||
|
max_length=50,
|
||||||
|
choices=NoteType.choices,
|
||||||
|
default=NoteType.FEEDBACK,
|
||||||
|
verbose_name=_("Note Type")
|
||||||
|
)
|
||||||
|
|
||||||
|
content = CKEditor5Field(verbose_name=_("Content/Feedback"), config_name="extends")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Meeting Comment")
|
verbose_name = _("Interview Note")
|
||||||
verbose_name_plural = _("Meeting Comments")
|
verbose_name_plural = _("Interview Notes")
|
||||||
ordering = ["-created_at"]
|
ordering = ["created_at"]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Comment by {self.author.get_username()} on {self.meeting.topic}"
|
return f"{self.get_note_type_display()} by {self.author.get_username()} on {self.interview.id}"
|
||||||
|
|
||||||
|
|
||||||
class FormTemplate(Base):
|
class FormTemplate(Base):
|
||||||
"""
|
"""
|
||||||
@ -1926,143 +2115,6 @@ class BreakTime(models.Model):
|
|||||||
return f"{self.start_time} - {self.end_time}"
|
return f"{self.start_time} - {self.end_time}"
|
||||||
|
|
||||||
|
|
||||||
class InterviewSchedule(Base):
|
|
||||||
"""Stores the scheduling criteria for 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",
|
|
||||||
)
|
|
||||||
|
|
||||||
job = models.ForeignKey(
|
|
||||||
JobPosting,
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name="interview_schedules",
|
|
||||||
db_index=True,
|
|
||||||
)
|
|
||||||
applications = models.ManyToManyField(
|
|
||||||
Application, related_name="interview_schedules", blank=True, null=True
|
|
||||||
)
|
|
||||||
start_date = models.DateField(
|
|
||||||
db_index=True, verbose_name=_("Start Date")
|
|
||||||
) # Added index
|
|
||||||
end_date = models.DateField(
|
|
||||||
db_index=True, verbose_name=_("End Date")
|
|
||||||
) # Added index
|
|
||||||
working_days = models.JSONField(
|
|
||||||
verbose_name=_("Working Days")
|
|
||||||
) # Store days of week as [0,1,2,3,4] for Mon-Fri
|
|
||||||
start_time = models.TimeField(verbose_name=_("Start Time"))
|
|
||||||
end_time = models.TimeField(verbose_name=_("End Time"))
|
|
||||||
|
|
||||||
break_start_time = models.TimeField(
|
|
||||||
verbose_name=_("Break Start Time"), null=True, blank=True
|
|
||||||
)
|
|
||||||
break_end_time = models.TimeField(
|
|
||||||
verbose_name=_("Break End Time"), null=True, blank=True
|
|
||||||
)
|
|
||||||
|
|
||||||
interview_duration = models.PositiveIntegerField(
|
|
||||||
verbose_name=_("Interview Duration (minutes)")
|
|
||||||
)
|
|
||||||
buffer_time = models.PositiveIntegerField(
|
|
||||||
verbose_name=_("Buffer Time (minutes)"), default=0
|
|
||||||
)
|
|
||||||
created_by = models.ForeignKey(
|
|
||||||
User, on_delete=models.CASCADE, db_index=True
|
|
||||||
) # Added index
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"Interview Schedule for {self.job.title}"
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
indexes = [
|
|
||||||
models.Index(fields=["start_date"]),
|
|
||||||
models.Index(fields=["end_date"]),
|
|
||||||
models.Index(fields=["created_by"]),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class ScheduledInterview(Base):
|
|
||||||
"""Stores individual scheduled interviews"""
|
|
||||||
|
|
||||||
application = models.ForeignKey(
|
|
||||||
Application,
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name="scheduled_interviews",
|
|
||||||
db_index=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
participants = models.ManyToManyField("Participants", blank=True)
|
|
||||||
system_users = models.ManyToManyField(User, blank=True)
|
|
||||||
|
|
||||||
job = models.ForeignKey(
|
|
||||||
"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,
|
|
||||||
null=True,
|
|
||||||
blank=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
onsite_meeting = models.OneToOneField(
|
|
||||||
OnsiteMeeting,
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name="onsite_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,
|
|
||||||
)
|
|
||||||
|
|
||||||
interview_date = models.DateField(
|
|
||||||
db_index=True, verbose_name=_("Interview Date")
|
|
||||||
) # Added index
|
|
||||||
interview_time = models.TimeField(verbose_name=_("Interview Time"))
|
|
||||||
status = models.CharField(
|
|
||||||
db_index=True,
|
|
||||||
max_length=20, # Added index
|
|
||||||
choices=[
|
|
||||||
("scheduled", _("Scheduled")),
|
|
||||||
("confirmed", _("Confirmed")),
|
|
||||||
("cancelled", _("Cancelled")),
|
|
||||||
("completed", _("Completed")),
|
|
||||||
],
|
|
||||||
default="scheduled",
|
|
||||||
)
|
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return (
|
|
||||||
f"Interview with {self.application.person.full_name} for {self.job.title}"
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
indexes = [
|
|
||||||
models.Index(fields=["job", "status"]),
|
|
||||||
models.Index(fields=["interview_date", "interview_time"]),
|
|
||||||
models.Index(fields=["application", "job"]),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class Notification(models.Model):
|
class Notification(models.Model):
|
||||||
@ -2101,7 +2153,7 @@ class Notification(models.Model):
|
|||||||
verbose_name=_("Status"),
|
verbose_name=_("Status"),
|
||||||
)
|
)
|
||||||
related_meeting = models.ForeignKey(
|
related_meeting = models.ForeignKey(
|
||||||
ZoomMeeting,
|
ZoomMeetingDetails,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
related_name="notifications",
|
related_name="notifications",
|
||||||
null=True,
|
null=True,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user