""" Django forms for EduMentorAI """ from django import forms from django.contrib.auth.models import User from django.contrib.auth.forms import UserCreationForm, AuthenticationForm, PasswordResetForm from django.core.exceptions import ValidationError import logging from .models import Subject, Document, Quiz, UserProfile, ChatMessage from allauth.account.forms import SignupForm from django.contrib.auth import get_user_model logger = logging.getLogger(__name__) User = get_user_model() class CustomSignupForm(SignupForm): """Custom signup form to handle existing users properly""" def clean_email(self): """Override to add custom email validation""" email = self.cleaned_data.get('email') if User.objects.filter(email__iexact=email).exists(): raise forms.ValidationError( "An account with this email address already exists. " "Please sign in instead or use the 'Forgot Password' option if you can't remember your password." ) return email class MultipleFileInput(forms.ClearableFileInput): allow_multiple_selected = True class MultipleFileField(forms.FileField): def __init__(self, *args, **kwargs): kwargs.setdefault("widget", MultipleFileInput()) super().__init__(*args, **kwargs) def clean(self, data, initial=None): single_file_clean = super().clean if isinstance(data, (list, tuple)): result = [single_file_clean(d, initial) for d in data] else: result = single_file_clean(data, initial) return result class SubjectForm(forms.ModelForm): """Form for creating/editing subjects""" class Meta: model = Subject fields = ['name', 'code', 'description'] widgets = { 'name': forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': 'Enter subject name' }), 'code': forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': 'e.g., CS101' }), 'description': forms.Textarea(attrs={ 'class': 'form-control', 'rows': 3, 'placeholder': 'Enter subject description (optional)' }) } class DocumentUploadForm(forms.ModelForm): """Form for uploading documents""" class Meta: model = Document fields = ['title', 'file', 'subject', 'processing_mode'] widgets = { 'title': forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': 'Enter document title' }), 'file': forms.FileInput(attrs={ 'class': 'form-control', 'accept': '.pdf,.docx,.txt,.pptx,.doc,.ppt' }), 'subject': forms.Select(attrs={ 'class': 'form-control' }), 'processing_mode': forms.RadioSelect(attrs={ 'class': 'form-check-input' }) } def __init__(self, *args, **kwargs): user = kwargs.pop('user', None) super().__init__(*args, **kwargs) if user: self.fields['subject'].queryset = Subject.objects.filter(created_by=user) # Add help text self.fields['file'].help_text = 'Supported formats: PDF, DOCX, TXT, PPTX (Max 50MB)' self.fields['processing_mode'].help_text = 'Choose processing mode based on your document type and time preference' # Make title optional and auto-generate from filename if not provided self.fields['title'].required = False def clean_file(self): file = self.cleaned_data.get('file') if file: # Check file size (50MB limit to match template) if file.size > 50 * 1024 * 1024: raise forms.ValidationError('File size must be less than 50MB') # Check file extension allowed_extensions = ['.pdf', '.docx', '.txt', '.pptx', '.doc', '.ppt'] file_extension = '.' + file.name.split('.')[-1].lower() if file_extension not in allowed_extensions: raise forms.ValidationError( 'File type not supported. Please upload PDF, DOCX, TXT, or PPTX files.' ) return file def clean_title(self): title = self.cleaned_data.get('title') file = self.cleaned_data.get('file') # Auto-generate title from filename if not provided if not title and file: title = file.name.rsplit('.', 1)[0] # Remove file extension return title class QuizCreateForm(forms.ModelForm): """Form for creating quizzes""" class Meta: model = Quiz fields = ['title', 'subject', 'based_on_document', 'description', 'time_limit', 'total_questions'] widgets = { 'title': forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': 'Enter quiz title' }), 'subject': forms.Select(attrs={ 'class': 'form-control' }), 'based_on_document': forms.Select(attrs={ 'class': 'form-control' }), 'description': forms.Textarea(attrs={ 'class': 'form-control', 'rows': 3, 'placeholder': 'Enter quiz description (optional)' }), 'time_limit': forms.NumberInput(attrs={ 'class': 'form-control', 'min': 5, 'max': 180, 'placeholder': 'Time limit in minutes' }), 'total_questions': forms.NumberInput(attrs={ 'class': 'form-control', 'min': 1, 'max': 50, 'placeholder': 'Number of questions' }) } def __init__(self, *args, **kwargs): user = kwargs.pop('user', None) super().__init__(*args, **kwargs) if user: self.fields['subject'].queryset = Subject.objects.filter(created_by=user) self.fields['based_on_document'].queryset = Document.objects.filter( uploaded_by=user, processed=True ) # Make based_on_document optional self.fields['based_on_document'].required = False self.fields['based_on_document'].empty_label = "Generate from all subject documents" class ProfileForm(forms.ModelForm): """Form for editing user profile""" # Add user fields first_name = forms.CharField( max_length=30, required=False, widget=forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': 'First name' }) ) last_name = forms.CharField( max_length=30, required=False, widget=forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': 'Last name' }) ) email = forms.EmailField( widget=forms.EmailInput(attrs={ 'class': 'form-control', 'placeholder': 'Email address' }) ) class Meta: model = UserProfile fields = ['bio', 'university', 'major', 'year_of_study', 'avatar'] widgets = { 'bio': forms.Textarea(attrs={ 'class': 'form-control', 'rows': 4, 'placeholder': 'Tell us about yourself' }), 'university': forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': 'Your university' }), 'major': forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': 'Your major/field of study' }), 'year_of_study': forms.NumberInput(attrs={ 'class': 'form-control', 'min': 1, 'max': 8, 'placeholder': 'Year of study' }), 'avatar': forms.FileInput(attrs={ 'class': 'form-control', 'accept': 'image/*' }) } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Populate user fields if instance exists if self.instance and self.instance.user: user = self.instance.user self.fields['first_name'].initial = user.first_name self.fields['last_name'].initial = user.last_name self.fields['email'].initial = user.email def save(self, commit=True): profile = super().save(commit=False) if commit: # Update user fields user = profile.user user.first_name = self.cleaned_data['first_name'] user.last_name = self.cleaned_data['last_name'] user.email = self.cleaned_data['email'] user.save() profile.save() return profile class ChatMessageForm(forms.ModelForm): """Form for chat messages""" class Meta: model = ChatMessage fields = ['message'] widgets = { 'message': forms.Textarea(attrs={ 'class': 'form-control', 'rows': 3, 'placeholder': 'Ask a question about your uploaded materials...', 'style': 'resize: none;' }) } class QuizQuestionForm(forms.Form): """Dynamic form for quiz questions""" def __init__(self, *args, **kwargs): questions = kwargs.pop('questions', []) super().__init__(*args, **kwargs) for question in questions: field_name = f'question_{question.id}' if question.question_type == 'mcq': choices = [(choice.id, choice.choice_text) for choice in question.choices.all()] self.fields[field_name] = forms.ChoiceField( choices=choices, widget=forms.RadioSelect(attrs={'class': 'form-check-input'}), required=True, label=question.question_text ) elif question.question_type == 'tf': self.fields[field_name] = forms.ChoiceField( choices=[('True', 'True'), ('False', 'False')], widget=forms.RadioSelect(attrs={'class': 'form-check-input'}), required=True, label=question.question_text ) elif question.question_type in ['sa', 'fb']: self.fields[field_name] = forms.CharField( widget=forms.Textarea(attrs={ 'class': 'form-control', 'rows': 3, 'placeholder': 'Enter your answer...' }), required=True, label=question.question_text ) class SearchForm(forms.Form): """Form for searching documents""" query = forms.CharField( max_length=500, widget=forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': 'Search your documents...', 'autocomplete': 'off' }) ) subject = forms.ModelChoiceField( queryset=Subject.objects.none(), required=False, empty_label="All subjects", widget=forms.Select(attrs={'class': 'form-control'}) ) def __init__(self, *args, **kwargs): user = kwargs.pop('user', None) super().__init__(*args, **kwargs) if user: self.fields['subject'].queryset = Subject.objects.filter(created_by=user) class SlideGenerationForm(forms.Form): """Form for generating slides""" document = forms.ModelChoiceField( queryset=Document.objects.none(), widget=forms.Select(attrs={'class': 'form-control'}), help_text="Select a document to generate slides from" ) topic = forms.CharField( max_length=200, required=False, widget=forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': 'Specific topic or leave empty for full document' }), help_text="Optional: Specify a particular topic to focus on" ) slide_count = forms.IntegerField( min_value=3, max_value=20, initial=10, widget=forms.NumberInput(attrs={ 'class': 'form-control', 'min': 3, 'max': 20 }), help_text="Number of slides to generate (3-20)" ) include_images = forms.BooleanField( required=False, initial=True, widget=forms.CheckboxInput(attrs={'class': 'form-check-input'}), help_text="Search and add relevant images from the internet to slides" ) def __init__(self, *args, **kwargs): user = kwargs.pop('user', None) super().__init__(*args, **kwargs) if user: self.fields['document'].queryset = Document.objects.filter( uploaded_by=user, processed=True ) class ChatModeSelectionForm(forms.Form): """Form for selecting chat mode and document/subject""" CHAT_MODE_CHOICES = [ ('document', 'Chat with a specific document'), ('subject', 'Chat within a subject (all documents)'), ] chat_mode = forms.ChoiceField( choices=CHAT_MODE_CHOICES, widget=forms.RadioSelect(attrs={'class': 'form-check-input'}), initial='document' ) # For document mode document = forms.ModelChoiceField( queryset=Document.objects.none(), required=False, empty_label="Select a document...", widget=forms.Select(attrs={ 'class': 'form-control', 'id': 'id_document' }) ) # For subject mode subject = forms.ModelChoiceField( queryset=Subject.objects.none(), required=False, empty_label="Select a subject...", widget=forms.Select(attrs={ 'class': 'form-control', 'id': 'id_subject' }) ) def __init__(self, *args, **kwargs): user = kwargs.pop('user', None) super().__init__(*args, **kwargs) if user: self.fields['document'].queryset = Document.objects.filter( uploaded_by=user, processed=True ).order_by('title') self.fields['subject'].queryset = Subject.objects.filter( created_by=user ).filter( documents__processed=True ).distinct().order_by('name') def clean(self): cleaned_data = super().clean() chat_mode = cleaned_data.get('chat_mode') document = cleaned_data.get('document') subject = cleaned_data.get('subject') if chat_mode == 'document' and not document: raise forms.ValidationError('Please select a document for document chat mode.') if chat_mode == 'subject' and not subject: raise forms.ValidationError('Please select a subject for subject chat mode.') return cleaned_data class BulkDocumentUploadForm(forms.Form): """Form for bulk document upload""" files = MultipleFileField( help_text="Select multiple files to upload at once" ) subject = forms.ModelChoiceField( queryset=Subject.objects.none(), widget=forms.Select(attrs={'class': 'form-control'}) ) def __init__(self, *args, **kwargs): user = kwargs.pop('user', None) super().__init__(*args, **kwargs) if user: self.fields['subject'].queryset = Subject.objects.filter(created_by=user) class UserProfileForm(forms.ModelForm): """Form for user profile management""" # Add user fields that can be edited first_name = forms.CharField( max_length=30, required=False, widget=forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': 'First name' }) ) last_name = forms.CharField( max_length=30, required=False, widget=forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': 'Last name' }) ) email = forms.EmailField( widget=forms.EmailInput(attrs={ 'class': 'form-control', 'placeholder': 'Email address', 'readonly': True # Email changes should go through AllAuth }) ) class Meta: model = UserProfile fields = ['bio', 'university', 'major', 'year_of_study', 'avatar'] widgets = { 'bio': forms.Textarea(attrs={ 'class': 'form-control', 'rows': 4, 'placeholder': 'Tell us about yourself...' }), 'university': forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': 'University or Institution' }), 'major': forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': 'Major or Field of Study' }), 'year_of_study': forms.NumberInput(attrs={ 'class': 'form-control', 'placeholder': 'Year of Study (e.g., 1, 2, 3, 4)', 'min': 1, 'max': 10 }), 'avatar': forms.FileInput(attrs={ 'class': 'form-control', 'accept': 'image/*' }) } def __init__(self, *args, **kwargs): self.user = kwargs.pop('user', None) super().__init__(*args, **kwargs) if self.user: self.fields['first_name'].initial = self.user.first_name self.fields['last_name'].initial = self.user.last_name self.fields['email'].initial = self.user.email def save(self, commit=True): profile = super().save(commit=False) if self.user: # Update user fields self.user.first_name = self.cleaned_data['first_name'] self.user.last_name = self.cleaned_data['last_name'] if commit: self.user.save() profile.user = self.user if commit: profile.save() return profile