EduMentorAI / src /rag_app /forms.py
ziadtarek12's picture
Images in slides done!
af376d8
Raw
History Blame Contribute Delete
18.8 kB
"""
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