Spaces:
Runtime error
Runtime error
| """ | |
| 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 | |