import re from django import forms 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 django.contrib.auth import get_user_model from django.contrib.auth.forms import UserCreationForm from .models import ( Application, JobPosting, FormTemplate, BulkInterviewTemplate, JobPostingImage, Note, ScheduledInterview, Source, HiringAgency, AgencyJobAssignment, AgencyAccessLink, Message, Person, Document, CustomUser, Settings, Interview ) from django_ckeditor_5.widgets import CKEditor5Widget import secrets import string from django.core.exceptions import ValidationError from django.utils import timezone User = get_user_model() def generate_api_key(length=32): """Generate a secure API key""" alphabet = string.ascii_letters + string.digits return "".join(secrets.choice(alphabet) for _ in range(length)) def generate_api_secret(length=64): """Generate a secure API secret""" alphabet = string.ascii_letters + string.digits + "-._~" return "".join(secrets.choice(alphabet) for _ in range(length)) class SourceForm(forms.ModelForm): """Simple form for creating and editing sources""" class Meta: model = Source fields = ["name", "source_type", "description", "ip_address","trusted_ips", "is_active"] widgets = { "name": forms.TextInput( attrs={ "class": "form-control", "required": True, } ), "source_type": forms.TextInput( attrs={ "class": "form-control", "required": True, } ), "description": forms.Textarea( attrs={ "class": "form-control", "rows": 3, } ), "ip_address": forms.TextInput( attrs={"class": "form-control", "required":True}, ), "trusted_ips":forms.TextInput( attrs={"class": "form-control", "required": False} ), "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("name", css_class="form-control"), Field("source_type", css_class="form-control"), Field("ip_address", css_class="form-control"), Field("is_active", css_class="form-check-input"), Submit("submit", "Save Source", css_class="btn btn-primary mt-3"), ) def clean_name(self): """Ensure source name is unique""" name = self.cleaned_data.get("name") if name: # Check for duplicates excluding current instance if editing instance = self.instance if not instance.pk: # Creating new instance if Source.objects.filter(name=name).exists(): raise ValidationError("A source with this name already exists.") else: # Editing existing instance if Source.objects.filter(name=name).exclude(pk=instance.pk).exists(): raise ValidationError("A source with this name already exists.") return name def clean_ip_address(self): ip_address=self.cleaned_data.get('ip_address') if not ip_address: raise ValidationError(_("Ip address should not be empty")) return ip_address class SourceAdvancedForm(forms.ModelForm): """Advanced form for creating and editing sources with API key generation""" # Hidden field to trigger API key generation generate_keys = forms.CharField( widget=forms.HiddenInput(), required=False, help_text="Set to 'true' to generate new API keys", ) # Display fields for generated keys (read-only) api_key_generated = forms.CharField( label="Generated API Key", required=False, widget=forms.TextInput(attrs={"readonly": True, "class": "form-control"}), ) api_secret_generated = forms.CharField( label="Generated API Secret", required=False, widget=forms.TextInput(attrs={"readonly": True, "class": "form-control"}), ) class Meta: model = Source fields = [ "name", "source_type", "description", "ip_address", "trusted_ips", "is_active", "integration_version", ] widgets = { "name": forms.TextInput( attrs={ "class": "form-control", "placeholder": "e.g., ATS System, ERP Integration", "required": True, } ), "source_type": forms.TextInput( attrs={ "class": "form-control", "placeholder": "e.g., ATS, ERP, API", "required": True, } ), "description": forms.Textarea( attrs={ "class": "form-control", "rows": 3, "placeholder": "Brief description of the source system", } ), "ip_address": forms.TextInput( attrs={"class": "form-control", "placeholder": "192.168.1.100"} ), "trusted_ips": forms.Textarea( attrs={ "class": "form-control", "rows": 2, "placeholder": "Comma-separated IP addresses (e.g., 192.168.1.100, 10.0.0.1)", } ), "integration_version": forms.TextInput( attrs={"class": "form-control", "placeholder": "v1.0, v2.1"} ), } 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" # Add generate keys button self.helper.layout = Layout( Field("name", css_class="form-control"), Field("source_type", css_class="form-control"), Field("description", css_class="form-control"), Field("ip_address", css_class="form-control"), Field("trusted_ips", css_class="form-control"), Field("integration_version", css_class="form-control"), Field("is_active", css_class="form-check-input"), # Hidden field for key generation trigger Field("generate_keys", type="hidden"), # Display fields for generated keys Field("api_key_generated", css_class="form-control"), Field("api_secret_generated", css_class="form-control"), Submit("submit", "Save Source", css_class="btn btn-primary mt-3"), ) def clean_name(self): """Ensure source name is unique""" name = self.cleaned_data.get("name") if name: # Check for duplicates excluding current instance if editing instance = self.instance if not instance.pk: # Creating new instance if Source.objects.filter(name=name).exists(): raise ValidationError("A source with this name already exists.") else: # Editing existing instance if Source.objects.filter(name=name).exclude(pk=instance.pk).exists(): raise ValidationError("A source with this name already exists.") return name def clean_trusted_ips(self): """Validate and format trusted IP addresses""" trusted_ips = self.cleaned_data.get("trusted_ips") if trusted_ips: # Split by comma and strip whitespace ips = [ip.strip() for ip in trusted_ips.split(",") if ip.strip()] # Validate each IP address for ip in ips: try: # Basic IP validation (can be enhanced) if not (ip.replace(".", "").isdigit() and len(ip.split(".")) == 4): raise ValidationError(f"Invalid IP address: {ip}") except Exception: raise ValidationError(f"Invalid IP address: {ip}") return ", ".join(ips) return trusted_ips def clean(self): """Custom validation for the form""" cleaned_data = super().clean() # Check if we need to generate API keys generate_keys = cleaned_data.get("generate_keys") if generate_keys == "true": # Generate new API key and secret cleaned_data["api_key"] = generate_api_key() cleaned_data["api_secret"] = generate_api_secret() # Set display fields for the frontend cleaned_data["api_key_generated"] = cleaned_data["api_key"] cleaned_data["api_secret_generated"] = cleaned_data["api_secret"] return cleaned_data class PersonForm(forms.ModelForm): class Meta: model = Person fields = ["first_name","middle_name", "last_name", "email", "phone","date_of_birth","gpa","national_id","nationality","gender","address"] widgets = { "first_name": forms.TextInput(attrs={'class': 'form-control'}), "middle_name": forms.TextInput(attrs={'class': 'form-control'}), "last_name": forms.TextInput(attrs={'class': 'form-control'}), "email": forms.EmailInput(attrs={'class': 'form-control'}), "phone": forms.TextInput(attrs={'class': 'form-control'}), "gender": forms.Select(attrs={'class': 'form-control'}), "date_of_birth": forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}), "nationality": forms.Select(attrs={'class': 'form-control select2'}), "address": forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), "gpa": forms.TextInput(attrs={'class': 'custom-decimal-input'}), "national_id":forms.NumberInput(attrs={'min': 0, 'step': 1}), } def clean_email(self): email = self.cleaned_data.get('email') if not email: return email if email: instance = self.instance qs = CustomUser.objects.filter(email=email) | CustomUser.objects.filter(username=email) if not instance.pk: # Creating new instance if qs.exists(): raise ValidationError(_("A user account with this email address already exists. Please use a different email.")) else: # Editing existing instance # if ( # qs # .exclude(pk=instance.user.pk) # .exists() # ): # raise ValidationError(_("An user with this email already exists.")) pass return email.strip() class ApplicationForm(forms.ModelForm): class Meta: model = Application fields = [ 'person', "job", "hiring_source", "hiring_agency", "resume", ] labels = { "person":_("Applicant"), "resume": _("Resume"), "hiring_source": _("Hiring Type"), "hiring_agency": _("Hiring Agency"), } widgets = { "hiring_source": forms.Select(attrs={"class": "form-select"}), "hiring_agency": forms.Select(attrs={"class": "form-select"}), } def __init__(self, *args,current_agency=None,current_job=None,**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" if current_agency: # IMPORTANT: Replace 'agency' below with the actual field name # on your Person model that links it back to the Agency model. self.fields['person'].queryset = self.fields['person'].queryset.filter( agency=current_agency ) self.fields['job'].queryset = self.fields['job'].queryset.filter( pk=current_job.id ) self.fields['job'].initial = current_job self.fields['job'].widget.attrs['readonly'] = True # 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("hiring_source", css_class="form-control"), Field("hiring_agency", css_class="form-control"), Field("resume", css_class="form-control"), Submit("submit", _("Submit"), css_class="btn btn-primary"), ) # def clean(self): # cleaned_data = super().clean() # job = cleaned_data.get("job") # agency = cleaned_data.get("hiring_agency") # person = cleaned_data.get("person") # if Application.objects.filter(person=person,job=job, hiring_agency=agency).exists(): # raise forms.ValidationError("You have already applied for this job.") # return cleaned_data # def save(self, commit=True): # """Override save to handle person creation/update""" # instance = super().save(commit=False) # # Get or create person # if instance.person: # person = instance.person # else: # # Create new person # from .models import Person # person = Person() # # Update person fields # person.first_name = self.cleaned_data['first_name'] # person.last_name = self.cleaned_data['last_name'] # person.email = self.cleaned_data['email'] # person.phZoomone = self.cleaned_data['phone'] # if commit: # person.save() # instance.person = person # instance.save() # return instance class ApplicationStageForm(forms.ModelForm): """Form specifically for updating candidate stage with validation""" class Meta: model = Application fields = ["stage"] labels = { "stage": _("New Application Stage"), } widgets = { "stage": forms.Select(attrs={"class": "form-select"}), } 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_deadline", "application_instructions", "position_number", "reporting_to", "open_positions", "hash_tags", "max_applications", ] widgets = { # Basic Information "title": forms.TextInput( attrs={"class": "form-control", "placeholder": "", "required": True} ), "department": forms.TextInput( attrs={"class": "form-control", "placeholder": ""} ), "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_deadline": forms.DateInput( attrs={"class": "form-control", "type": "date", "required": True} ), "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.", } ), "max_applications": forms.NumberInput( attrs={ "class": "form-control", "min": 1, } ), } def __init__(self, *args, **kwargs): # Now call the parent __init__ with remaining args super().__init__(*args, **kwargs) if not self.instance.pk: # Creating new job posting # 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 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 BulkInterviewTemplateForm(forms.ModelForm): applications = forms.ModelMultipleChoiceField( queryset=Application.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 = BulkInterviewTemplate fields = [ 'schedule_interview_type', 'topic', 'physical_address', "applications", "start_date", "end_date", "working_days", "start_time", "end_time", "interview_duration", "buffer_time", "break_start_time", "break_end_time", ] widgets = { "topic": forms.TextInput(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"} ), "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"} ), "schedule_interview_type":forms.RadioSelect(), "physical_address": forms.Textarea( attrs={"class": "form-control", "rows": 3, "placeholder": "Enter physical address if 'In-Person' is selected"} ), } def __init__(self, slug, *args, **kwargs): super().__init__(*args, **kwargs) self.fields["applications"].queryset = Application.objects.filter( job__slug=slug, stage="Interview" ) self.fields["topic"].initial = "Interview for " + str( self.fields["applications"].queryset.first().job.title ) self.fields["start_date"].initial = timezone.now().date() working_days_initial = [0, 1, 2, 3, 6] # Monday to Friday self.fields["working_days"].initial = working_days_initial self.fields["start_time"].initial = "08:00" self.fields["end_time"].initial = "14:00" self.fields["interview_duration"].initial = 30 self.fields["buffer_time"].initial = 10 self.fields["break_start_time"].initial = "11:30" self.fields["break_end_time"].initial = "12:00" self.fields["physical_address"].initial = "Airport Road, King Khalid International Airport, Riyadh 11564, Saudi Arabia" def clean_working_days(self): working_days = self.cleaned_data.get("working_days") return [int(day) for day in working_days] class InterviewCancelForm(forms.ModelForm): class Meta: model = ScheduledInterview fields = ["cancelled_reason","cancelled_at"] widgets = { "cancelled_reason": forms.Textarea( attrs={"class": "form-control", "rows": 3} ), "cancelled_at": forms.DateTimeInput( attrs={"class": "form-control", "type": "datetime-local"} ), } class NoteForm(forms.ModelForm): """Form for creating and editing meeting comments""" class Meta: model = Note fields = "__all__" widgets = { "content": CKEditor5Widget( attrs={ "class": "form-control", "placeholder": _("Enter your comment or note"), }, config_name="extends", ), } labels = { "content": _("Note"), } class ProfileImageUploadForm(forms.ModelForm): class Meta: model = User fields = ["profile_image"] class StaffUserCreationForm(UserCreationForm): email = forms.EmailField(label=_("Email"), required=True) first_name = forms.CharField(label=_("First Name"),max_length=30, required=True) last_name = forms.CharField(label=_("Last Name"),max_length=150, required=True) class Meta: model = User fields = ("email", "first_name", "last_name", "password1", "password2") def clean_email(self): email = self.cleaned_data["email"] if User.objects.filter(email=email).exists(): raise forms.ValidationError("A user with this email already exists.") return email def generate_username(self, email): """Generate a valid, unique username from email.""" prefix = email.split("@")[0].lower() username = re.sub(r"[^a-z0-9._]", "", prefix) if not username: username = "user" base = username counter = 1 while User.objects.filter(username=username).exists(): username = f"{base}{counter}" counter += 1 return username def save(self, commit=True): user = super().save(commit=False) user.email = self.cleaned_data["email"] user.first_name = self.cleaned_data["first_name"] user.last_name = self.cleaned_data["last_name"] user.username = self.generate_username(user.email) user.password1=self.cleaned_data["password1"] user.password2=self.cleaned_data["password2"] user.is_staff = True if commit: user.save() return user class ToggleAccountForm(forms.Form): pass 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"}), } def clean_status(self): status = self.cleaned_data.get("status") if status == "ACTIVE": if self.instance and self.instance.pk: print(self.instance.assigned_to) if not self.instance.assigned_to: raise ValidationError("Please assign the job posting before setting it to Active.") return status class LinkedPostContentForm(forms.ModelForm): class Meta: model = JobPosting fields = ["linkedin_post_formated_data"] class FormTemplateIsActiveForm(forms.ModelForm): class Meta: model = FormTemplate fields = ["is_active"] class ApplicationExamDateForm(forms.ModelForm): class Meta: model = Application fields = ["exam_date"] widgets = { "exam_date": forms.DateTimeInput( attrs={"type": "datetime-local", "class": "form-control"} ), } class HiringAgencyForm(forms.ModelForm): """Form for creating and editing hiring agencies""" class Meta: model = HiringAgency fields = [ "name", "contact_person", "email", "phone", "website", "country", "address", "notes", ] widgets = { "name": forms.TextInput( attrs={ "class": "form-control", "required": True, } ), "contact_person": forms.TextInput( attrs={ "class": "form-control", } ), "email": forms.EmailInput( attrs={"class": "form-control","required": True} ), "phone": forms.TextInput( attrs={"class": "form-control"} ), "website": forms.URLInput( attrs={"class": "form-control"} ), "country": forms.Select(attrs={"class": "form-select"}), "address": forms.Textarea( attrs={ "class": "form-control", "rows": 3, } ), "notes": forms.Textarea( attrs={ "class": "form-control", "rows": 3, } ), } labels = { "name": _("Agency Name"), "contact_person": _("Contact Person"), "email": _("Email Address"), "phone": _("Phone Number"), "website": _("Website"), "country": _("Country"), "address": _("Address"), "notes": _("Internal Notes"), } 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.fields['email'].required=True self.helper.layout = Layout( Field("name", css_class="form-control"), Field("contact_person", css_class="form-control"), Row( Column("email", css_class="col-md-6"), Column("phone", css_class="col-md-6"), css_class="g-3 mb-3", ), Field("website", css_class="form-control"), Field("country", css_class="form-control"), Field("address", css_class="form-control"), Field("notes", css_class="form-control"), Div( Submit("submit", _("Save Agency"), css_class="btn btn-main-action"), css_class="col-12 mt-4", ), ) def clean_name(self): """Ensure agency name is unique""" name = self.cleaned_data.get("name") if name: instance = self.instance if not instance.pk: # Creating new instance if HiringAgency.objects.filter(name=name).exists(): raise ValidationError("An agency with this name already exists.") else: # Editing existing instance if ( HiringAgency.objects.filter(name=name) .exclude(pk=instance.pk) .exists() ): raise ValidationError("An agency with this name already exists.") return name.strip() def clean_email(self): """Validate email format and uniqueness""" email = self.cleaned_data.get("email") instance=self.instance if email: # Check email format if not "@" in email or "." not in email.split("@")[1]: raise ValidationError("Please enter a valid email address.") # Check uniqueness (optional - remove if multiple agencies can have same email) # instance = self.instance email = email.lower().strip() if not instance.pk: # Creating new instance if HiringAgency.objects.filter(email=email).exists() or User.objects.filter(email=email): raise ValidationError("An agency with this email already exists.") else: # Editing existing instance if ( HiringAgency.objects.filter(email=email) .exclude(pk=instance.pk) .exists() ): raise ValidationError("An agency with this email already exists.") # if not instance.pk: # Creating new instance # if HiringAgency.objects.filter(email=email).exists(): # raise ValidationError("An agency with this email already exists.") # else: # Editing existing instance # if ( # HiringAgency.objects.filter(email=email) # .exclude(pk=instance.pk) # .exists() # ): # raise ValidationError("An agency with this email already exists.") return email.lower().strip() if email else email def clean_phone(self): """Validate phone number format""" phone = self.cleaned_data.get("phone") if phone: # Remove common formatting characters clean_phone = "".join(c for c in phone if c.isdigit() or c in "+") if len(clean_phone) < 10: raise ValidationError("Phone number must be at least 10 digits long.") return phone.strip() if phone else phone def clean_website(self): """Validate website URL""" website = self.cleaned_data.get("website") if website: if not website.startswith(("http://", "https://")): website = "https://" + website validator = URLValidator() try: validator(website) except ValidationError: raise ValidationError("Please enter a valid website URL.") return website class AgencyJobAssignmentForm(forms.ModelForm): """Form for creating and editing agency job assignments""" class Meta: model = AgencyJobAssignment fields = ["agency", "job", "max_candidates", "deadline_date", "admin_notes"] widgets = { "agency": forms.Select(attrs={"class": "form-select"}), "job": forms.Select(attrs={"class": "form-select"}), "max_candidates": forms.NumberInput( attrs={ "class": "form-control", "min": 1, } ), "deadline_date": forms.DateTimeInput( attrs={"class": "form-control", "type": "datetime-local"} ), "is_active": forms.CheckboxInput(attrs={"class": "form-check-input"}), "status": forms.Select(attrs={"class": "form-select"}), "admin_notes": forms.Textarea( attrs={ "class": "form-control", "rows": 3, } ), } labels = { "agency": _("Agency"), "job": _("Job Posting"), "max_candidates": _("Maximum Candidates"), "deadline_date": _("Deadline Date"), "is_active": _("Is Active"), "status": _("Status"), "admin_notes": _("Admin Notes"), } 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" # Filter jobs to only show active jobs self.fields["job"].queryset = JobPosting.objects.filter( status="ACTIVE" ).order_by("-created_at") self.helper.layout = Layout( Row( Column("agency", css_class="col-md-6"), Column("job", css_class="col-md-6"), css_class="g-3 mb-3", ), Row( Column("max_candidates", css_class="col-md-6"), Column("deadline_date", css_class="col-md-6"), css_class="g-3 mb-3", ), Row( Column("is_active", css_class="col-md-6"), Column("status", css_class="col-md-6"), css_class="g-3 mb-3", ), Field("admin_notes", css_class="form-control"), Div( Submit("submit", _("Save Assignment"), css_class="btn btn-main-action"), css_class="col-12 mt-4", ), ) def clean_deadline_date(self): """Validate deadline date is in the future""" deadline_date = self.cleaned_data.get("deadline_date") if deadline_date and deadline_date <= timezone.now(): raise ValidationError("Deadline date must be in the future.") return deadline_date def clean_max_candidates(self): """Validate maximum candidates is positive""" max_candidates = self.cleaned_data.get("max_candidates") if max_candidates and max_candidates <= 0: raise ValidationError("Maximum candidates must be greater than 0.") return max_candidates def clean(self): """Check for duplicate assignments""" cleaned_data = super().clean() agency = cleaned_data.get("agency") job = cleaned_data.get("job") if agency and job: # Check if this assignment already exists existing = ( AgencyJobAssignment.objects.filter(agency=agency, job=job) .exclude(pk=self.instance.pk) .first() ) if existing: raise ValidationError( f"This job is already assigned to {agency.name}. " f"Current status: {existing.get_status_display()}" ) return cleaned_data class AgencyAccessLinkForm(forms.ModelForm): """Form for creating and managing agency access links""" class Meta: model = AgencyAccessLink fields = ["assignment", "expires_at", "is_active"] widgets = { "assignment": forms.Select(attrs={"class": "form-select"}), "expires_at": forms.DateTimeInput( attrs={"class": "form-control", "type": "datetime-local"} ), "is_active": forms.CheckboxInput(attrs={"class": "form-check-input"}), } labels = { "assignment": _("Assignment"), "expires_at": _("Expires At"), "is_active": _("Is Active"), } 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" # Filter assignments to only show active ones without existing links self.fields["assignment"].queryset = ( AgencyJobAssignment.objects.filter(is_active=True, status="ACTIVE") .exclude(access_link__isnull=False) .order_by("-created_at") ) self.helper.layout = Layout( Field("assignment", css_class="form-control"), Field("expires_at", css_class="form-control"), Field("is_active", css_class="form-check-input"), Div( Submit( "submit", _("Create Access Link"), css_class="btn btn-main-action" ), css_class="col-12 mt-4", ), ) def clean_expires_at(self): """Validate expiration date is in the future""" expires_at = self.cleaned_data.get("expires_at") if expires_at and expires_at <= timezone.now(): raise ValidationError("Expiration date must be in the future.") return expires_at # Agency messaging forms removed - AgencyMessage model has been deleted class AgencyApplicationSubmissionForm(forms.ModelForm): """Form for agencies to submit candidates""" class Meta: model = Application fields = ["person", "resume"] labels = { "resume": _("Resume"), } def __init__(self, assignment, *args, **kwargs): super().__init__(*args, **kwargs) self.assignment = assignment self.helper = FormHelper() self.helper.form_method = "post" self.helper.form_class = "g-3" self.helper.enctype = "multipart/form-data" self.helper.layout = Layout( Field("person", css_class="form-control"), Field("resume", css_class="form-control"), Div( Submit("submit", _("Submit Candidate"), css_class="btn btn-main-action"), css_class="col-12 mt-4", ), ) def clean_resume(self): """Validate resume file""" resume = self.cleaned_data.get("resume") if resume: # Check file size (max 5MB) if resume.size > 5 * 1024 * 1024: raise ValidationError("Resume file size must be less than 5MB.") # Check file extension allowed_extensions = [".pdf", ".doc", ".docx"] file_extension = resume.name.lower().split(".")[-1] if f".{file_extension}" not in allowed_extensions: raise ValidationError("Resume must be in PDF, DOC, or DOCX format.") return resume def save(self, commit=True): """Override save to set additional fields""" instance = super().save(commit=False) # Set required fields for agency submission instance.job = self.assignment.job instance.hiring_agency = self.assignment.agency instance.stage = Application.Stage.APPLIED instance.applicant_status = Application.ApplicantType.CANDIDATE instance.applied = True if commit: instance.save() # Increment the assignment's submitted count self.assignment.increment_submission_count() return instance class AgencyLoginForm(forms.Form): """Form for agencies to login with token and password""" token = forms.CharField( widget=forms.TextInput( attrs={"class": "form-control", "placeholder": "Enter your access token"} ), label=_("Access Token"), required=True, ) password = forms.CharField( widget=forms.PasswordInput( attrs={"class": "form-control", "placeholder": "Enter your password"} ), label=_("Password"), required=True, ) class PortalLoginForm(forms.Form): """Unified login form for agency and candidate""" USER_TYPE_CHOICES = [ ("", _("Select User Type")), ("agency", _("Agency")), ("candidate", _("Candidate")), ] email = forms.EmailField( widget=forms.EmailInput( attrs={"class": "form-control", "placeholder": "Enter your email"} ), label=_("Email"), required=True, ) password = forms.CharField( widget=forms.PasswordInput( attrs={"class": "form-control", "placeholder": "Enter your password"} ), label=_("Password"), required=True, ) user_type = forms.ChoiceField( choices=USER_TYPE_CHOICES, widget=forms.Select(attrs={"class": "form-control"}), label=_("User Type"), required=True, ) def clean(self): """Validate token and password combination""" cleaned_data = super().clean() token = cleaned_data.get("token") password = cleaned_data.get("password") if token and password: try: access_link = AgencyAccessLink.objects.get( unique_token=token, is_active=True ) if not access_link.is_valid: if access_link.is_expired: raise ValidationError("This access link has expired.") else: raise ValidationError("This access link is no longer active.") if access_link.access_password != password: raise ValidationError("Invalid password.") # Store the access_link for use in the view self.validated_access_link = access_link except AgencyAccessLink.DoesNotExist: print("Access link does not exist") raise ValidationError("Invalid access token.") return cleaned_data class CandidateEmailForm(forms.Form): """Form for composing emails to participants about a candidate""" to = forms.MultipleChoiceField( widget=forms.CheckboxSelectMultiple(attrs={ 'class': 'form-check' }), label=_('Select Candidates'), # Use a descriptive label required=False ) subject = forms.CharField( max_length=200, widget=forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': 'Enter email subject', 'required': True }), label=_('Subject'), required=True ) message = forms.CharField( widget=forms.Textarea(attrs={ 'class': 'form-control', 'rows': 8, 'placeholder': 'Enter your message here...', 'required': True }), label=_('Message'), required=True ) def __init__(self, job, candidates, *args, **kwargs): super().__init__(*args, **kwargs) self.job = job self.candidates=candidates candidate_choices=[] for candidate in candidates: candidate_choices.append( (f'candidate_{candidate.id}', f'{candidate.email}') ) self.fields['to'].choices =candidate_choices self.fields['to'].initial = [choice[0] for choice in candidate_choices] # Set initial message with candidate and meeting info initial_message = self._get_initial_message() if initial_message: self.fields['message'].initial = initial_message def _get_initial_message(self): """Generate initial message with candidate and meeting information""" candidate=self.candidates.first() message_parts=[] 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.", f"We encourage you to check our career page for further updates and future opportunities:", f"https://kaauh/careers", f"Wishing you the best in your job search,", f"The KAAUH Hiring team" ] 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!", f"The next step is the mandatory online assessment exam.", f"Please complete the assessment by using the following link:", f"https://kaauh/hire/exam", f"We look forward to reviewing your results.", f"Best regards, The KAAUH Hiring team" ] 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 you have cleared your exam!", f"The next step is the mandatory interview.", f"Please complete the assessment by using the following link:", f"https://kaauh/hire/exam", f"We look forward to reviewing your results.", f"Best regards, The KAAUH Hiring team" ] 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.", f"A detailed offer letter and compensation package will be sent to you via email within 24 hours.", f"In the meantime, please contact our HR department at [HR Contact] if you have immediate questions.", f"Welcome to the team!", f"Best regards, The KAAUH Hiring team" ] 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}.", f"You will receive a separate email shortly with details regarding your start date, first-day instructions, and onboarding documents.", f"We look forward to seeing you at KAAUH.", f"If you have any questions before your start date, please contact [Onboarding Contact].", f"Best regards, The KAAUH Hiring team" ] elif candidate: message_parts="" return '\n'.join(message_parts) def get_email_addresses(self): """Extract email addresses from selected recipients""" email_addresses = [] candidates=self.cleaned_data.get('to',[]) print(f"candidates are {candidates}") if candidates: for candidate in candidates: if candidate.startswith('candidate_'): print("candidadte: {candidate}") candidate_id = candidate.split('_')[1] try: candidate = Application.objects.get(id=candidate_id) email_addresses.append(candidate.email) except Application.DoesNotExist: continue return list(set(email_addresses)) # Remove duplicates def get_formatted_message(self): """Get the formatted message with optional additional information""" message = self.cleaned_data.get('message', '') return message class MessageForm(forms.ModelForm): """Form for creating and editing messages between users""" class Meta: model = Message fields = ["job","recipient", "subject", "content", "message_type"] widgets = { "recipient": forms.Select( attrs={"class": "form-select", "placeholder": "Select recipient","required": True,} ), "job": forms.Select( attrs={"class": "form-select", "placeholder": "Select job"} ), "subject": forms.TextInput( attrs={ "class": "form-control", "placeholder": "Enter message subject", "required": True, } ), "content": forms.Textarea( attrs={ "class": "form-control", "rows": 6, "placeholder": "Enter your message here...", "required": True, 'spellcheck': 'true', } ), "message_type": forms.Select(attrs={"class": "form-select"}), } labels = { "recipient": _("Recipient"), "job": _("Related Job"), "subject": _("Subject"), "content": _("Message"), "message_type": _("Message Type"), } def __init__(self, user, *args, **kwargs): super().__init__(*args, **kwargs) self.user = user self.helper = FormHelper() self.helper.form_method = "post" self.helper.form_class = "g-3" self._filter_recipient_field() self._filter_job_field() self.helper.layout = Layout( Row( Column("recipient", css_class="col-md-6"), Column("job", css_class="col-md-6"), css_class="g-3 mb-3", ), Field("subject", css_class="form-control"), Field("message_type", css_class="form-control"), Field("content", css_class="form-control"), Div( Submit("submit", _("Send Message"), css_class="btn btn-main-action"), css_class="col-12 mt-4", ), ) def _filter_job_field(self): """Filter job options based on user type""" if self.user.user_type == "agency": print("jhjkshfjksd") job_assignments =AgencyJobAssignment.objects.filter( agency__user=self.user, job__status="ACTIVE" ) job_ids = job_assignments.values_list('job__id', flat=True) self.fields["job"].queryset = JobPosting.objects.filter( id__in=job_ids ).order_by("-created_at") print("Agency user job queryset:", self.fields["job"].queryset) elif self.user.user_type == "candidate": print("sjhdakjhsdkjashkdjhskd") # Candidates can only see jobs they applied for person=self.user.person_profile print(person) applications=person.applications.all() print(applications) self.fields["job"].queryset = JobPosting.objects.filter( applications__in=applications, ).distinct().order_by("-created_at") else: print("shhadjkhkd") # Staff can see all jobs self.fields["job"].queryset = JobPosting.objects.filter( status="ACTIVE" ).order_by("-created_at") def _filter_recipient_field(self): """Filter recipient options based on user type""" if self.user.user_type == "staff": # Staff can message anyone self.fields["recipient"].queryset = User.objects.all().order_by("username") elif self.user.user_type == "agency": # Agency can message staff and their candidates from django.db.models import Q self.fields["recipient"].queryset = User.objects.filter( user_type="staff" ).distinct().order_by("username") elif self.user.user_type == "candidate": # Candidates can only message staff self.fields["recipient"].queryset = User.objects.filter( user_type="staff" ).order_by("username") def clean(self): """Validate message form data""" cleaned_data = super().clean() job = cleaned_data.get("job") recipient = cleaned_data.get("recipient") # If job is selected but no recipient, auto-assign to job.assigned_to if job and not recipient: if job.assigned_to: cleaned_data["recipient"] = job.assigned_to # Set message type to job_related cleaned_data["message_type"] = Message.MessageType.JOB_RELATED else: raise forms.ValidationError( _("Selected job is not assigned to any user. Please assign the job first.") ) # Validate messaging permissions if self.user and cleaned_data.get("recipient"): self._validate_messaging_permissions(cleaned_data) if self.cleaned_data.get('recipient')==self.user: raise forms.ValidationError(_("You cannot message yourself")) return cleaned_data def _validate_messaging_permissions(self, cleaned_data): """Validate if user can message the recipient""" recipient = cleaned_data.get("recipient") job = cleaned_data.get("job") # Staff can message anyone if self.user.user_type == "staff": return # Agency users validation if self.user.user_type == "agency": if recipient.user_type not in ["staff", "candidate"]: raise forms.ValidationError( _("Agencies can only message staff or candidates.") ) # If messaging a candidate, ensure candidate is from their agency if recipient.user_type == "candidate" and job: if not job.hiring_agency.filter(user=self.user).exists(): raise forms.ValidationError( _("You can only message candidates from your assigned jobs.") ) # Candidate users validation if self.user.user_type == "candidate": if recipient.user_type != "staff": raise forms.ValidationError( _("Candidates can only message staff.") ) # If job-related, ensure candidate applied for the job if job: if not Application.objects.filter(job=job, person=self.user.person_profile).exists(): raise forms.ValidationError( _("You can only message about jobs you have applied for.") ) class ApplicantSignupForm(forms.ModelForm): password = forms.CharField(widget=forms.PasswordInput(attrs={'class': 'form-control'})) confirm_password = forms.CharField(widget=forms.PasswordInput(attrs={'class': 'form-control'})) class Meta: model = Person fields = ["first_name","middle_name","last_name", "email","phone","gpa","nationality","national_id", "date_of_birth","gender","address"] widgets = { 'first_name': forms.TextInput(attrs={'class': 'form-control'}), 'middle_name': forms.TextInput(attrs={'class': 'form-control'}), 'last_name': forms.TextInput(attrs={'class': 'form-control'}), 'email': forms.EmailInput(attrs={'class': 'form-control'}), 'phone': forms.TextInput(attrs={'class': 'form-control'}), "nationality": forms.Select(attrs={'class': 'form-control select2'}), 'date_of_birth': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}), 'gender': forms.Select(attrs={'class': 'form-control'}), 'address': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), 'national_id': forms.TextInput(attrs={'class': 'form-control'}), } def clean(self): cleaned_data = super().clean() password = cleaned_data.get("password") confirm_password = cleaned_data.get("confirm_password") if password and confirm_password and password != confirm_password: raise forms.ValidationError("Passwords do not match.") return cleaned_data def email_clean(self): email = self.cleaned_data.get('email') if CustomUser.objects.filter(email=email).exists(): raise forms.ValidationError("Email is already in use.") return email class DocumentUploadForm(forms.ModelForm): """Form for uploading documents for candidates""" class Meta: model = Document fields = ['document_type', 'description', 'file'] widgets = { 'document_type': forms.Select( choices=Document.DocumentType.choices, attrs={'class': 'form-control'} ), 'description': forms.Textarea( attrs={ 'class': 'form-control', 'rows': 3, 'placeholder': 'Enter document description (optional)' } ), 'file': forms.FileInput( attrs={ 'class': 'form-control', 'accept': '.pdf,.doc,.docx,.jpg,.jpeg,.png' } ), } labels = { 'document_type': _('Document Type'), 'description': _('Description'), 'file': _('Document File'), } def clean_file(self): """Validate uploaded file""" file = self.cleaned_data.get('file') if file: # Check file size (max 10MB) if file.size > 10 * 1024 * 1024: # 10MB raise forms.ValidationError( _('File size must be less than 10MB.') ) # Check file extension allowed_extensions = ['.pdf', '.doc', '.docx', '.jpg', '.jpeg', '.png'] file_extension = file.name.lower().split('.')[-1] if f'.{file_extension}' not in allowed_extensions: raise forms.ValidationError( _('File type must be one of: PDF, DOC, DOCX, JPG, JPEG, PNG.') ) return file def clean(self): """Custom validation for document upload""" cleaned_data = super().clean() self.clean_file() return cleaned_data class PasswordResetForm(forms.Form): old_password = forms.CharField( widget=forms.PasswordInput(attrs={'class': 'form-control'}), label=_('Old Password') ) new_password1 = forms.CharField( widget=forms.PasswordInput(attrs={'class': 'form-control'}), label=_('New Password') ) new_password2 = forms.CharField( widget=forms.PasswordInput(attrs={'class': 'form-control'}), label=_('Confirm New Password') ) def clean(self): """Custom validation for password reset""" cleaned_data = super().clean() old_password = cleaned_data.get('old_password') new_password1 = cleaned_data.get('new_password1') new_password2 = cleaned_data.get('new_password2') if old_password: if not self.data.get('old_password'): raise forms.ValidationError(_('Old password is incorrect.')) if new_password1 and new_password2: if new_password1 != new_password2: raise forms.ValidationError(_('New passwords do not match.')) return cleaned_data class StaffAssignmentForm(forms.ModelForm): """Form for assigning staff to a job posting""" class Meta: model = JobPosting fields = ['assigned_to'] widgets = { 'assigned_to': forms.Select(attrs={ 'class': 'form-select', 'placeholder': _('Select staff member'), 'required': True }), } labels = { 'assigned_to': _('Assign Staff Member'), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Filter users to only show staff members self.fields['assigned_to'].queryset = User.objects.filter( user_type='staff',is_superuser=False ).order_by('first_name', 'last_name') # Add empty choice for unassigning self.fields['assigned_to'].required = False self.helper = FormHelper() self.helper.form_method = 'post' self.helper.form_class = 'g-3' self.helper.form_id = 'staff-assignment-form' self.helper.layout = Layout( Field('assigned_to', css_class='form-control'), Div( Submit('submit', _('Assign Staff'), css_class='btn btn-primary'), css_class='col-12 mt-3' ), ) def clean_assigned_to(self): """Validate that assigned user is a staff member""" assigned_to = self.cleaned_data.get('assigned_to') if assigned_to and assigned_to.user_type != 'staff': raise forms.ValidationError(_('Only staff members can be assigned to jobs.')) return assigned_to class RemoteInterviewForm(forms.Form): """Form for creating remote interviews""" # Add Interview model fields to the form topic = forms.CharField( max_length=255, required=False, widget=forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': 'e.g., Software Interview' }), label=_('Meeting Topic') ) interview_date = forms.DateField( required=False, widget=forms.DateInput(attrs={ 'class': 'form-control', 'type': 'date' }), label=_('Interview Date') ) interview_time = forms.TimeField( required=False, widget=forms.TimeInput(attrs={ 'class': 'form-control', 'type': 'time' }), label=_('Interview Time') ) duration = forms.IntegerField( min_value=1, required=False, widget=forms.NumberInput(attrs={ 'class': 'form-control', 'placeholder': 'Duration in minutes' }), label=_('Duration (minutes)') ) class OnsiteInterviewForm(forms.Form): """Form for creating onsite interviews""" # Add Interview model fields to the form topic = forms.CharField( max_length=255, required=False, widget=forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': 'e.g., In-person Interview' }), label=_('Interview Topic') ) physical_address = forms.CharField( max_length=255, required=False, widget=forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': 'Physical address' }), label=_('Physical Address') ) room_number = forms.CharField( max_length=50, required=False, widget=forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': 'Room number' }), label=_('Room Number') ) interview_date = forms.DateField( widget=forms.DateInput(attrs={ 'class': 'form-control', 'type': 'date', 'required': True }), label=_('Interview Date') ) interview_time = forms.TimeField( widget=forms.TimeInput(attrs={ 'class': 'form-control', 'type': 'time', 'required': True }), label=_('Interview Time') ) duration = forms.IntegerField( min_value=1, required=False, widget=forms.NumberInput(attrs={ 'class': 'form-control', 'placeholder': 'Duration in minutes' }), label=_('Duration (minutes)') ) class ScheduledInterviewForm(forms.Form): topic = forms.CharField( max_length=255, required=False, widget=forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': 'e.g., Interview Topic' }), label=_('Interview Topic') ) start_time = forms.DateTimeField( widget=forms.DateTimeInput(attrs={ 'class': 'form-control', 'type': 'datetime-local', 'required': True }), label=_('Start Time') ) duration = forms.IntegerField( min_value=1, required=False, widget=forms.NumberInput(attrs={ 'class': 'form-control', 'placeholder': 'Duration in minutes' }), label=_('Duration (minutes)') ) def clean_start_time(self): """Validate start time is not in the past""" start_time = self.cleaned_data.get('start_time') if start_time and start_time < timezone.now(): raise forms.ValidationError(_('Start time cannot be in the past.')) return start_time class OnsiteScheduleInterviewUpdateForm(forms.Form): topic = forms.CharField( max_length=255, required=False, widget=forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': 'e.g., Interview Topic' }), label=_('Interview Topic') ) start_time = forms.DateTimeField( widget=forms.DateTimeInput(attrs={ 'class': 'form-control', 'type': 'datetime-local', 'required': True }), label=_('Start Time') ) duration = forms.IntegerField( min_value=1, required=False, widget=forms.NumberInput(attrs={ 'class': 'form-control', 'placeholder': 'Duration in minutes' }), label=_('Duration (minutes)') ) physical_address = forms.CharField( max_length=255, required=False, widget=forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': 'Physical address' }), label=_('Physical Address') ) room_number = forms.CharField( max_length=50, required=False, widget=forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': 'Room number' }), label=_('Room Number') ) class ScheduledInterviewUpdateStatusForm(forms.Form): status = forms.ChoiceField( choices=ScheduledInterview.InterviewStatus.choices, widget=forms.Select(attrs={ 'class': 'form-control', 'required': True }), label=_('Interview Status') ) class Meta: model = ScheduledInterview fields = ['status'] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Filter the choices here EXCLUDED_STATUS = ScheduledInterview.InterviewStatus.CANCELLED filtered_choices = [ choice for choice in ScheduledInterview.InterviewStatus.choices if choice[0]!= EXCLUDED_STATUS ] # Apply the filtered list back to the field self.fields['status'].choices = filtered_choices class SettingsForm(forms.ModelForm): """Form for creating and editing settings""" class Meta: model = Settings fields = ['name','key', 'value'] widgets = { 'name': forms.TextInput(attrs={ 'class': 'form-control mb-3', 'placeholder': 'e.g., Zoom', 'required': True }), 'key': forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': 'Enter setting key', 'required': True }), 'value': forms.Textarea(attrs={ 'class': 'form-control', 'rows': 3, 'placeholder': 'Enter setting value', 'required': True }), } labels = { 'key': _('Setting Key'), 'value': _('Setting Value'), } 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( Field('key', css_class='form-control'), Field('value', css_class='form-control'), Div( Submit('submit', _('Save Setting'), css_class='btn btn-main-action'), css_class='col-12 mt-4', ), ) def clean_key(self): """Ensure key is unique and properly formatted""" key = self.cleaned_data.get('key') if key: # Convert to uppercase for consistency key = key.upper().strip() # Check for duplicates excluding current instance if editing instance = self.instance if not instance.pk: # Creating new instance if Settings.objects.filter(key=key).exists(): raise forms.ValidationError("A setting with this key already exists.") else: # Editing existing instance if Settings.objects.filter(key=key).exclude(pk=instance.pk).exists(): raise forms.ValidationError("A setting with this key already exists.") # Validate key format (alphanumeric and underscores only) import re if not re.match(r'^[A-Z][A-Z0-9_]*$', key): raise forms.ValidationError( "Setting key must start with a letter and contain only uppercase letters, numbers, and underscores." ) return key def clean_value(self): """Validate setting value""" value = self.cleaned_data.get('value') if value: value = value.strip() if not value: raise forms.ValidationError("Setting value cannot be empty.") return value class InterviewEmailForm(forms.Form): """Form for composing emails to participants about a candidate""" to = forms.CharField( label=_('To'), # Use a descriptive label required=True, ) subject = forms.CharField( max_length=200, widget=forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': 'Enter email subject', 'required': True }), label=_('Subject'), required=True ) message = forms.CharField( widget=forms.Textarea(attrs={ 'class': 'form-control', 'rows': 8, 'placeholder': 'Enter your message here...', 'required': True }), label=_('Message'), required=True ) def __init__(self, job, application,schedule, *args, **kwargs): applicant=application.person.user interview=schedule.interview super().__init__(*args, **kwargs) if application.hiring_agency: self.fields['to'].initial=application.hiring_agency.email self.fields['to'].disabled= True else: self.fields['to'].initial=application.person.email self.fields['to'].disabled= True # Set initial message with candidate and meeting info initial_message = f""" Dear {applicant.first_name} {applicant.last_name}, Your interview details are as follows: Date: {interview.start_time.strftime("%d-%m-%Y")} Time: {interview.start_time.strftime("%I:%M %p")} Interview Duration: {interview.duration} minutes Job: {job.title} """ if interview.location_type == 'Remote': initial_message += f"Pease join using meeting link {interview.details_url} .\n\n" else: initial_message += "This is an onsite schedule. Please arrive 10 minutes early.\n\n" initial_message += """ Best regards, KAAUH Hiring Team """ self.fields['message'].initial = initial_message class InterviewResultForm(forms.ModelForm): class Meta: model = Interview fields = ['interview_result', 'result_comments'] widgets = { 'interview_result': forms.Select(attrs={ 'class': 'form-select', # Standard Bootstrap class 'required': 'required' }), 'result_comments': forms.Textarea(attrs={ 'class': 'form-control', 'rows': 3, 'placeholder': 'Enter setting value', 'required': True }), }