Spaces:
Runtime error
Runtime error
| from django.db import models | |
| from django.contrib.auth.models import User | |
| from django.utils import timezone | |
| import uuid | |
| import os | |
| def upload_to_user_folder(instance, filename): | |
| """Upload files to user-specific folders""" | |
| return f'uploads/{instance.uploaded_by.id}/{filename}' | |
| class DocumentType(models.TextChoices): | |
| """Document type choices""" | |
| PDF = 'pdf', 'PDF' | |
| DOCX = 'docx', 'Word Document' | |
| TXT = 'txt', 'Text File' | |
| PPTX = 'pptx', 'PowerPoint' | |
| class ProcessingMode(models.TextChoices): | |
| """Document processing mode choices""" | |
| FAST = 'fast', 'Fast Processing (Text Only)' | |
| OCR = 'ocr', 'Advanced Processing (Text + OCR for Images)' | |
| class Subject(models.Model): | |
| """Subject/Course model""" | |
| name = models.CharField(max_length=200) | |
| code = models.CharField(max_length=20, unique=True) | |
| description = models.TextField(blank=True) | |
| created_by = models.ForeignKey(User, on_delete=models.CASCADE) | |
| created_at = models.DateTimeField(auto_now_add=True) | |
| def __str__(self): | |
| return f"{self.code} - {self.name}" | |
| class Meta: | |
| ordering = ['code'] | |
| class Document(models.Model): | |
| """Uploaded document model""" | |
| id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) | |
| title = models.CharField(max_length=255) | |
| file = models.FileField(upload_to=upload_to_user_folder) | |
| document_type = models.CharField(max_length=10, choices=DocumentType.choices) | |
| processing_mode = models.CharField(max_length=10, choices=ProcessingMode.choices, default=ProcessingMode.FAST) | |
| subject = models.ForeignKey(Subject, on_delete=models.CASCADE, related_name='documents') | |
| uploaded_by = models.ForeignKey(User, on_delete=models.CASCADE) | |
| uploaded_at = models.DateTimeField(auto_now_add=True) | |
| processed = models.BooleanField(default=False) | |
| processed_at = models.DateTimeField(null=True, blank=True) | |
| file_size = models.PositiveIntegerField() # in bytes | |
| page_count = models.PositiveIntegerField(null=True, blank=True) | |
| # Processed text: Combined extracted text + OCR text from images | |
| processed_text = models.TextField(blank=True, help_text="Clean text-only version (document text + OCR text)") | |
| def __str__(self): | |
| return self.title | |
| def save(self, *args, **kwargs): | |
| if self.file: | |
| self.file_size = self.file.size | |
| # Auto-detect document type from file extension | |
| ext = os.path.splitext(self.file.name)[1].lower().lstrip('.') | |
| if ext in ['pdf', 'docx', 'txt', 'pptx']: | |
| self.document_type = ext | |
| super().save(*args, **kwargs) | |
| class Meta: | |
| ordering = ['-uploaded_at'] | |
| class DocumentChunk(models.Model): | |
| """Processed document chunks for RAG""" | |
| id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) | |
| document = models.ForeignKey(Document, on_delete=models.CASCADE, related_name='chunks') | |
| content = models.TextField() | |
| chunk_index = models.PositiveIntegerField() | |
| page_number = models.PositiveIntegerField(null=True, blank=True) | |
| embedding_vector = models.BinaryField(null=True, blank=True) # Store embeddings | |
| created_at = models.DateTimeField(auto_now_add=True) | |
| def __str__(self): | |
| return f"{self.document.title} - Chunk {self.chunk_index}" | |
| class Meta: | |
| ordering = ['chunk_index'] | |
| unique_together = ['document', 'chunk_index'] | |
| class DocumentImage(models.Model): | |
| """Extracted images from documents with OCR text""" | |
| id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) | |
| document = models.ForeignKey(Document, on_delete=models.CASCADE, related_name='images') | |
| image_file = models.ImageField(upload_to='document_images/') | |
| page_number = models.PositiveIntegerField() | |
| image_index = models.PositiveIntegerField() # Index of image on the page | |
| ocr_text = models.TextField(blank=True) # Extracted text from OCR | |
| ocr_processed = models.BooleanField(default=False) | |
| embedding_vector = models.BinaryField(null=True, blank=True) # Store OCR text embeddings | |
| width = models.PositiveIntegerField(null=True, blank=True) | |
| height = models.PositiveIntegerField(null=True, blank=True) | |
| extracted_at = models.DateTimeField(auto_now_add=True) | |
| def __str__(self): | |
| return f"{self.document.title} - Page {self.page_number} - Image {self.image_index}" | |
| class Meta: | |
| ordering = ['page_number', 'image_index'] | |
| unique_together = ['document', 'page_number', 'image_index'] | |
| class ChatSession(models.Model): | |
| """Chat session model""" | |
| id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) | |
| user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="user_chats") | |
| subject = models.ForeignKey(Subject, on_delete=models.CASCADE, null=True, blank=True, related_name="subject_chats") | |
| # For specific document chat | |
| document = models.ForeignKey(Document, on_delete=models.CASCADE, null=True, blank=True, related_name="document_chats") | |
| # For anonymous document chat | |
| temp_document = models.ForeignKey('TempDocument', on_delete=models.CASCADE, null=True, blank=True) | |
| title = models.CharField(max_length=255, blank=True) | |
| chat_type = models.CharField(max_length=20, choices=[ | |
| ('subject', 'Subject Chat'), | |
| ('document', 'Document Chat'), | |
| ('anonymous', 'Anonymous Chat') | |
| ], default='subject') | |
| created_at = models.DateTimeField(auto_now_add=True) | |
| last_activity = models.DateTimeField(auto_now=True) | |
| def __str__(self): | |
| return f"Chat - {self.user.username} - {self.title or 'Untitled'}" | |
| class Meta: | |
| ordering = ['-last_activity'] | |
| class TempDocument(models.Model): | |
| """Temporary document for anonymous chat sessions""" | |
| id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) | |
| title = models.CharField(max_length=255) | |
| file = models.FileField(upload_to='temp_documents/') | |
| processing_mode = models.CharField(max_length=10, choices=ProcessingMode.choices, default=ProcessingMode.FAST) | |
| uploaded_by = models.ForeignKey(User, on_delete=models.CASCADE) | |
| uploaded_at = models.DateTimeField(auto_now_add=True) | |
| processed = models.BooleanField(default=False) | |
| file_size = models.PositiveIntegerField() # in bytes | |
| # Auto-delete after 24 hours | |
| expires_at = models.DateTimeField() | |
| def save(self, *args, **kwargs): | |
| if not self.expires_at: | |
| from django.utils import timezone | |
| self.expires_at = timezone.now() + timezone.timedelta(hours=24) | |
| if self.file: | |
| self.file_size = self.file.size | |
| super().save(*args, **kwargs) | |
| def __str__(self): | |
| return f"Temp: {self.title}" | |
| class Meta: | |
| ordering = ['-uploaded_at'] | |
| class ChatMessage(models.Model): | |
| """Individual chat messages""" | |
| id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) | |
| session = models.ForeignKey(ChatSession, on_delete=models.CASCADE, related_name='messages') | |
| message = models.TextField() | |
| is_user = models.BooleanField() # True for user messages, False for AI responses | |
| timestamp = models.DateTimeField(auto_now_add=True) | |
| response_time = models.FloatField(null=True, blank=True) # AI response time in seconds | |
| relevant_chunks = models.ManyToManyField(DocumentChunk, blank=True) | |
| def __str__(self): | |
| sender = "User" if self.is_user else "AI" | |
| return f"{sender}: {self.message[:50]}..." | |
| class Meta: | |
| ordering = ['timestamp'] | |
| class QuizType(models.TextChoices): | |
| """Quiz type choices""" | |
| MULTIPLE_CHOICE = 'mcq', 'Multiple Choice' | |
| TRUE_FALSE = 'tf', 'True/False' | |
| SHORT_ANSWER = 'sa', 'Short Answer' | |
| FILL_BLANK = 'fb', 'Fill in the Blank' | |
| class Quiz(models.Model): | |
| """Generated quiz model""" | |
| id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) | |
| title = models.CharField(max_length=255) | |
| subject = models.ForeignKey(Subject, on_delete=models.CASCADE, related_name='quizzes') | |
| created_by = models.ForeignKey(User, on_delete=models.CASCADE) | |
| based_on_document = models.ForeignKey(Document, on_delete=models.CASCADE, null=True, blank=True) | |
| description = models.TextField(blank=True) | |
| time_limit = models.PositiveIntegerField(default=30) # in minutes | |
| total_questions = models.PositiveIntegerField(default=10) | |
| created_at = models.DateTimeField(auto_now_add=True) | |
| is_active = models.BooleanField(default=True) | |
| # Optional Google Form integration | |
| google_form_url = models.URLField(null=True, blank=True) | |
| google_form_edit_url = models.URLField(null=True, blank=True) | |
| google_form_owner_email = models.EmailField(null=True, blank=True) | |
| def __str__(self): | |
| return self.title | |
| class Meta: | |
| ordering = ['-created_at'] | |
| class Question(models.Model): | |
| """Quiz question model""" | |
| id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) | |
| quiz = models.ForeignKey(Quiz, on_delete=models.CASCADE, related_name='questions') | |
| question_text = models.TextField() | |
| question_type = models.CharField(max_length=10, choices=QuizType.choices) | |
| points = models.PositiveIntegerField(default=1) | |
| explanation = models.TextField(blank=True) | |
| source_chunk = models.ForeignKey(DocumentChunk, on_delete=models.SET_NULL, null=True, blank=True) | |
| order = models.PositiveIntegerField() | |
| def __str__(self): | |
| return f"Q{self.order}: {self.question_text[:50]}..." | |
| class Meta: | |
| ordering = ['order'] | |
| unique_together = ['quiz', 'order'] | |
| class AnswerChoice(models.Model): | |
| """Multiple choice answer options""" | |
| id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) | |
| question = models.ForeignKey(Question, on_delete=models.CASCADE, related_name='choices') | |
| choice_text = models.TextField() | |
| is_correct = models.BooleanField(default=False) | |
| order = models.PositiveIntegerField() | |
| def __str__(self): | |
| return f"{self.choice_text[:30]}... ({'✓' if self.is_correct else '✗'})" | |
| class Meta: | |
| ordering = ['order'] | |
| unique_together = ['question', 'order'] | |
| class UserProfile(models.Model): | |
| """Extended user profile""" | |
| user = models.OneToOneField(User, on_delete=models.CASCADE) | |
| bio = models.TextField(blank=True) | |
| university = models.CharField(max_length=200, blank=True) | |
| major = models.CharField(max_length=100, blank=True) | |
| year_of_study = models.PositiveIntegerField(null=True, blank=True) | |
| avatar = models.ImageField(upload_to='avatars/', null=True, blank=True) | |
| created_at = models.DateTimeField(auto_now_add=True) | |
| updated_at = models.DateTimeField(auto_now=True) | |
| def __str__(self): | |
| return f"{self.user.username}'s Profile" | |