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