fix(firebase): fix save fcm token logic
This commit is contained in:
1 parent
929faab1fa
commit
8f0ce113b4
6 files changed
+309
-204
No files matched your search
+136
-161
@@ -126,14 +126,12 @@ class Course(models.Model):
|
||||
verbose_name = "Course"
|
||||
verbose_name_plural = "Courses"
|
||||
indexes = [
|
||||
models.Index(fields=["name"]),
|
||||
models.Index(fields=["code"]),
|
||||
models.Index(fields=["is_active"]),
|
||||
models.Index(fields=["duration_type"]),
|
||||
# For active course lookups
|
||||
models.Index(fields=["name", "is_active"]),
|
||||
# For active course lookups by code
|
||||
models.Index(fields=["code", "is_active"]),
|
||||
models.Index(fields=['name']),
|
||||
models.Index(fields=['code']),
|
||||
models.Index(fields=['is_active']),
|
||||
models.Index(fields=['duration_type']),
|
||||
models.Index(fields=['name', 'is_active']), # For active course lookups
|
||||
models.Index(fields=['code', 'is_active']), # For active course lookups by code
|
||||
]
|
||||
|
||||
def cleanup_orphaned_data(self):
|
||||
@@ -142,10 +140,10 @@ class Course(models.Model):
|
||||
"""
|
||||
# Clean up subjects using the correct reverse relationship
|
||||
Subject.objects.filter(course=self).delete()
|
||||
|
||||
|
||||
# Clean up course tracking records
|
||||
self.student_trackings.all().delete()
|
||||
|
||||
|
||||
# Clean up routine records
|
||||
self.routines.all().delete()
|
||||
|
||||
@@ -224,13 +222,11 @@ class Subject(models.Model):
|
||||
unique_together = ["name", "course", "period_or_year"]
|
||||
ordering = ["course", "period_or_year", "name"]
|
||||
indexes = [
|
||||
models.Index(fields=["course"]),
|
||||
models.Index(fields=["period_or_year"]),
|
||||
models.Index(fields=["name"]),
|
||||
# For course period subjects
|
||||
models.Index(fields=["course", "period_or_year"]),
|
||||
# For subject lookups
|
||||
models.Index(fields=["name", "course"]),
|
||||
models.Index(fields=['course']),
|
||||
models.Index(fields=['period_or_year']),
|
||||
models.Index(fields=['name']),
|
||||
models.Index(fields=['course', 'period_or_year']), # For course period subjects
|
||||
models.Index(fields=['name', 'course']), # For subject lookups
|
||||
]
|
||||
|
||||
def cleanup_orphaned_data(self):
|
||||
@@ -239,10 +235,10 @@ class Subject(models.Model):
|
||||
"""
|
||||
# Clean up subject files
|
||||
self.subjectfile_set.all().delete()
|
||||
|
||||
|
||||
# Clean up routine records
|
||||
self.routines.all().delete()
|
||||
|
||||
|
||||
# Clean up attendance records
|
||||
self.attendance_records.all().delete()
|
||||
|
||||
@@ -406,12 +402,12 @@ class Student(AbstractUser):
|
||||
with transaction.atomic():
|
||||
# Validate data before saving
|
||||
self.full_clean()
|
||||
|
||||
|
||||
# Call the original save method
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
# Update related records in the same transaction
|
||||
if hasattr(self, "course_trackings"):
|
||||
if hasattr(self, 'course_trackings'):
|
||||
for tracking in self.course_trackings.all():
|
||||
tracking.update_completion_percentage()
|
||||
except ValidationError as e:
|
||||
@@ -423,13 +419,13 @@ class Student(AbstractUser):
|
||||
"""Validate student data before saving"""
|
||||
if self.phone and not self.phone.isdigit():
|
||||
raise ValidationError("Phone number must contain only digits")
|
||||
|
||||
if self.email and "@" not in self.email:
|
||||
|
||||
if self.email and '@' not in self.email:
|
||||
raise ValidationError("Invalid email format")
|
||||
|
||||
|
||||
if self.birth_date and self.birth_date > date.today():
|
||||
raise ValidationError("Birth date cannot be in the future")
|
||||
|
||||
|
||||
if self.joining_date and self.joining_date > date.today():
|
||||
raise ValidationError("Joining date cannot be in the future")
|
||||
|
||||
@@ -439,13 +435,13 @@ class Student(AbstractUser):
|
||||
"""
|
||||
# Clean up attendance records
|
||||
self.attendance_records.all().delete()
|
||||
|
||||
|
||||
# Clean up course tracking
|
||||
self.course_trackings.all().delete()
|
||||
|
||||
|
||||
# Clean up leave records
|
||||
self.leave_requests.all().delete()
|
||||
|
||||
|
||||
# Clean up meeting records
|
||||
self.meetings.clear()
|
||||
|
||||
@@ -467,26 +463,26 @@ class Student(AbstractUser):
|
||||
Get FCM tokens for notifications
|
||||
"""
|
||||
tokens = set()
|
||||
|
||||
|
||||
# Get student's token
|
||||
if self.fcm_token:
|
||||
tokens.add(self.fcm_token)
|
||||
|
||||
|
||||
# Get parent's token
|
||||
if self.parent and self.parent.fcm_token:
|
||||
tokens.add(self.parent.fcm_token)
|
||||
|
||||
|
||||
return tokens
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Student"
|
||||
verbose_name_plural = "Students"
|
||||
indexes = [
|
||||
models.Index(fields=["phone"]),
|
||||
models.Index(fields=["status"]),
|
||||
models.Index(fields=["course"]),
|
||||
models.Index(fields=["current_period"]),
|
||||
models.Index(fields=["joining_date"]),
|
||||
models.Index(fields=['phone']),
|
||||
models.Index(fields=['status']),
|
||||
models.Index(fields=['course']),
|
||||
models.Index(fields=['current_period']),
|
||||
models.Index(fields=['joining_date']),
|
||||
]
|
||||
|
||||
|
||||
@@ -549,9 +545,7 @@ class Staff(AbstractUser):
|
||||
help_text="Course for which this staff member is HOD",
|
||||
)
|
||||
courses_taught = models.ManyToManyField(Course, related_name="teachers", blank=True)
|
||||
meetings = models.ManyToManyField(
|
||||
"TeacherParentMeeting", related_name="staff_members", blank=True
|
||||
)
|
||||
meetings = models.ManyToManyField("TeacherParentMeeting", related_name="staff_members", blank=True)
|
||||
|
||||
USERNAME_FIELD = "phone"
|
||||
REQUIRED_FIELDS = ["name"]
|
||||
@@ -611,13 +605,13 @@ class Staff(AbstractUser):
|
||||
"""Validate staff data before saving"""
|
||||
if self.phone and not self.phone.isdigit():
|
||||
raise ValidationError("Phone number must contain only digits")
|
||||
|
||||
if self.email and "@" not in self.email:
|
||||
|
||||
if self.email and '@' not in self.email:
|
||||
raise ValidationError("Invalid email format")
|
||||
|
||||
|
||||
if self.birth_date and self.birth_date > date.today():
|
||||
raise ValidationError("Birth date cannot be in the future")
|
||||
|
||||
|
||||
if self.joining_date and self.joining_date > date.today():
|
||||
raise ValidationError("Joining date cannot be in the future")
|
||||
|
||||
@@ -627,10 +621,10 @@ class Staff(AbstractUser):
|
||||
"""
|
||||
# Clean up attendance records
|
||||
self.attendance_records.all().delete()
|
||||
|
||||
|
||||
# Clean up course assignments
|
||||
self.courses_taught.clear()
|
||||
|
||||
|
||||
# Clean up meeting records
|
||||
self.meetings.clear()
|
||||
|
||||
@@ -652,27 +646,25 @@ class Staff(AbstractUser):
|
||||
Get FCM tokens for notifications
|
||||
"""
|
||||
tokens = set()
|
||||
|
||||
|
||||
# Get staff's token
|
||||
if self.fcm_token:
|
||||
tokens.add(self.fcm_token)
|
||||
|
||||
|
||||
return tokens
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Staff"
|
||||
verbose_name_plural = "Staffs"
|
||||
indexes = [
|
||||
models.Index(fields=["phone"]),
|
||||
models.Index(fields=["designation"]),
|
||||
models.Index(fields=["is_active"]),
|
||||
models.Index(fields=["course"]),
|
||||
models.Index(fields=['phone']),
|
||||
models.Index(fields=['designation']),
|
||||
models.Index(fields=['is_active']),
|
||||
models.Index(fields=['course']),
|
||||
# Add these composite indexes
|
||||
# For active staff by role
|
||||
models.Index(fields=["designation", "is_active"]),
|
||||
models.Index(fields=["course", "is_active"]), # For course staff
|
||||
# For active staff lookups
|
||||
models.Index(fields=["phone", "is_active"]),
|
||||
models.Index(fields=['designation', 'is_active']), # For active staff by role
|
||||
models.Index(fields=['course', 'is_active']), # For course staff
|
||||
models.Index(fields=['phone', 'is_active']), # For active staff lookups
|
||||
]
|
||||
|
||||
|
||||
@@ -703,15 +695,14 @@ class Routine(models.Model):
|
||||
"period_or_year",
|
||||
]
|
||||
indexes = [
|
||||
models.Index(fields=["course"]),
|
||||
models.Index(fields=["subject"]),
|
||||
models.Index(fields=["teacher"]),
|
||||
models.Index(fields=["is_active"]),
|
||||
models.Index(fields=["period_or_year"]),
|
||||
# For course routines
|
||||
models.Index(fields=["course", "period_or_year"]),
|
||||
models.Index(fields=["teacher", "is_active"]), # For teacher schedules
|
||||
models.Index(fields=["subject", "is_active"]), # For subject schedules
|
||||
models.Index(fields=['course']),
|
||||
models.Index(fields=['subject']),
|
||||
models.Index(fields=['teacher']),
|
||||
models.Index(fields=['is_active']),
|
||||
models.Index(fields=['period_or_year']),
|
||||
models.Index(fields=['course', 'period_or_year']), # For course routines
|
||||
models.Index(fields=['teacher', 'is_active']), # For teacher schedules
|
||||
models.Index(fields=['subject', 'is_active']), # For subject schedules
|
||||
]
|
||||
|
||||
|
||||
@@ -745,17 +736,14 @@ class Attendance(models.Model):
|
||||
verbose_name = "Attendance"
|
||||
verbose_name_plural = "Attendances"
|
||||
indexes = [
|
||||
models.Index(fields=["date"]),
|
||||
models.Index(fields=["routine"]),
|
||||
models.Index(fields=["teacher"]),
|
||||
models.Index(fields=["class_status"]),
|
||||
models.Index(fields=['date']),
|
||||
models.Index(fields=['routine']),
|
||||
models.Index(fields=['teacher']),
|
||||
models.Index(fields=['class_status']),
|
||||
# Add these composite indexes
|
||||
# For date-based routine lookups
|
||||
models.Index(fields=["date", "routine"]),
|
||||
# For teacher attendance history
|
||||
models.Index(fields=["teacher", "date"]),
|
||||
# For class status filtering
|
||||
models.Index(fields=["routine", "class_status"]),
|
||||
models.Index(fields=['date', 'routine']), # For date-based routine lookups
|
||||
models.Index(fields=['teacher', 'date']), # For teacher attendance history
|
||||
models.Index(fields=['routine', 'class_status']), # For class status filtering
|
||||
]
|
||||
|
||||
|
||||
@@ -781,15 +769,13 @@ class AttendanceRecord(models.Model):
|
||||
|
||||
class Meta:
|
||||
indexes = [
|
||||
models.Index(fields=["attendance"]),
|
||||
models.Index(fields=["student"]),
|
||||
models.Index(fields=["student_attend"]),
|
||||
models.Index(fields=['attendance']),
|
||||
models.Index(fields=['student']),
|
||||
models.Index(fields=['student_attend']),
|
||||
# Add these composite indexes
|
||||
# For attendance filtering
|
||||
models.Index(fields=["student", "student_attend"]),
|
||||
models.Index(fields=["attendance", "student"]), # For attendance lookups
|
||||
# For attendance status
|
||||
models.Index(fields=["attendance", "student_attend"]),
|
||||
models.Index(fields=['student', 'student_attend']), # For attendance filtering
|
||||
models.Index(fields=['attendance', 'student']), # For attendance lookups
|
||||
models.Index(fields=['attendance', 'student_attend']), # For attendance status
|
||||
]
|
||||
|
||||
|
||||
@@ -854,14 +840,13 @@ class StudentLeave(models.Model):
|
||||
verbose_name_plural = "Student Leaves"
|
||||
ordering = ["-created_at"]
|
||||
indexes = [
|
||||
models.Index(fields=["student"]),
|
||||
models.Index(fields=["status"]),
|
||||
models.Index(fields=["start_date"]),
|
||||
models.Index(fields=["end_date"]),
|
||||
# For student leave status
|
||||
models.Index(fields=["student", "status"]),
|
||||
models.Index(fields=["start_date", "end_date"]), # For date range queries
|
||||
models.Index(fields=["status", "start_date"]), # For pending leaves
|
||||
models.Index(fields=['student']),
|
||||
models.Index(fields=['status']),
|
||||
models.Index(fields=['start_date']),
|
||||
models.Index(fields=['end_date']),
|
||||
models.Index(fields=['student', 'status']), # For student leave status
|
||||
models.Index(fields=['start_date', 'end_date']), # For date range queries
|
||||
models.Index(fields=['status', 'start_date']), # For pending leaves
|
||||
]
|
||||
|
||||
|
||||
@@ -1072,16 +1057,16 @@ class CourseTracking(models.Model):
|
||||
"""Validate course tracking data"""
|
||||
if self.enrollment_date and self.enrollment_date > date.today():
|
||||
raise ValidationError("Enrollment date cannot be in the future")
|
||||
|
||||
|
||||
if self.start_date and self.start_date > date.today():
|
||||
raise ValidationError("Start date cannot be in the future")
|
||||
|
||||
|
||||
if self.expected_end_date and self.expected_end_date < self.start_date:
|
||||
raise ValidationError("Expected end date cannot be before start date")
|
||||
|
||||
|
||||
if self.actual_end_date and self.actual_end_date < self.start_date:
|
||||
raise ValidationError("Actual end date cannot be before start date")
|
||||
|
||||
|
||||
if self.completion_percentage < 0 or self.completion_percentage > 100:
|
||||
raise ValidationError("Completion percentage must be between 0 and 100")
|
||||
|
||||
@@ -1098,40 +1083,39 @@ class CourseTracking(models.Model):
|
||||
|
||||
# Get completed subjects from SubjectProgress
|
||||
completed_subjects = SubjectProgress.objects.filter(
|
||||
student=self.student, subject__course=self.course, status="Completed"
|
||||
student=self.student,
|
||||
subject__course=self.course,
|
||||
status="Completed"
|
||||
).count()
|
||||
|
||||
# Get attendance percentage for active subjects
|
||||
attended_classes = AttendanceRecord.objects.filter(
|
||||
student=self.student,
|
||||
attendance__routine__subject__course=self.course,
|
||||
student_attend=True,
|
||||
student_attend=True
|
||||
).count()
|
||||
|
||||
total_classes = AttendanceRecord.objects.filter(
|
||||
student=self.student, attendance__routine__subject__course=self.course
|
||||
student=self.student,
|
||||
attendance__routine__subject__course=self.course
|
||||
).count()
|
||||
|
||||
# Calculate weighted completion percentage
|
||||
subject_weight = 0.7 # 70% weight to subject completion
|
||||
attendance_weight = 0.3 # 30% weight to attendance
|
||||
|
||||
subject_percentage = (
|
||||
(completed_subjects / total_subjects) * 100 if total_subjects > 0 else 0
|
||||
)
|
||||
attendance_percentage = (
|
||||
(attended_classes / total_classes) * 100 if total_classes > 0 else 0
|
||||
)
|
||||
subject_percentage = (completed_subjects / total_subjects) * 100 if total_subjects > 0 else 0
|
||||
attendance_percentage = (attended_classes / total_classes) * 100 if total_classes > 0 else 0
|
||||
|
||||
# Calculate final percentage
|
||||
completion_percentage = int(
|
||||
(subject_percentage * subject_weight)
|
||||
+ (attendance_percentage * attendance_weight)
|
||||
(subject_percentage * subject_weight) +
|
||||
(attendance_percentage * attendance_weight)
|
||||
)
|
||||
|
||||
# Ensure percentage is between 0 and 100
|
||||
completion_percentage = max(0, min(100, completion_percentage))
|
||||
|
||||
|
||||
# Update progress status based on completion
|
||||
if completion_percentage >= 100:
|
||||
self.progress_status = "Completed"
|
||||
@@ -1208,13 +1192,13 @@ class CourseTracking(models.Model):
|
||||
try:
|
||||
self.clean()
|
||||
# Update completion percentage without triggering save again
|
||||
if not hasattr(self, "_updating_completion"):
|
||||
if not hasattr(self, '_updating_completion'):
|
||||
self._updating_completion = True
|
||||
try:
|
||||
self.completion_percentage = self.update_completion_percentage()
|
||||
finally:
|
||||
delattr(self, "_updating_completion")
|
||||
|
||||
delattr(self, '_updating_completion')
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
except Exception as e:
|
||||
print(f"Error saving course tracking for {self.student.name}: {str(e)}")
|
||||
@@ -1289,17 +1273,17 @@ class CourseTracking(models.Model):
|
||||
unique_together = ["student", "course"]
|
||||
ordering = ["-created_at"]
|
||||
indexes = [
|
||||
models.Index(fields=["student"]),
|
||||
models.Index(fields=["course"]),
|
||||
models.Index(fields=["progress_status"]),
|
||||
models.Index(fields=["current_period"]),
|
||||
models.Index(fields=["enrollment_date"]),
|
||||
models.Index(fields=["student", "progress_status"]),
|
||||
models.Index(fields=["course", "progress_status"]),
|
||||
models.Index(fields=["student", "course", "progress_status"]),
|
||||
models.Index(fields=["completion_percentage"]),
|
||||
models.Index(fields=["start_date", "expected_end_date"]),
|
||||
models.Index(fields=["current_period", "progress_status"]),
|
||||
models.Index(fields=['student']),
|
||||
models.Index(fields=['course']),
|
||||
models.Index(fields=['progress_status']),
|
||||
models.Index(fields=['current_period']),
|
||||
models.Index(fields=['enrollment_date']),
|
||||
models.Index(fields=['student', 'progress_status']),
|
||||
models.Index(fields=['course', 'progress_status']),
|
||||
models.Index(fields=['student', 'course', 'progress_status']),
|
||||
models.Index(fields=['completion_percentage']),
|
||||
models.Index(fields=['start_date', 'expected_end_date']),
|
||||
models.Index(fields=['current_period', 'progress_status']),
|
||||
]
|
||||
|
||||
|
||||
@@ -1342,13 +1326,12 @@ class ResetToken(models.Model):
|
||||
|
||||
class OTPAttempt(models.Model):
|
||||
"""Model to track OTP attempts for rate limiting"""
|
||||
|
||||
user = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
related_name="otp_attempts",
|
||||
related_name='otp_attempts',
|
||||
null=True,
|
||||
blank=True,
|
||||
blank=True
|
||||
)
|
||||
identifier = models.CharField(max_length=255) # phone or email
|
||||
attempts = models.IntegerField(default=0)
|
||||
@@ -1358,21 +1341,21 @@ class OTPAttempt(models.Model):
|
||||
|
||||
class Meta:
|
||||
indexes = [
|
||||
models.Index(fields=["identifier"]),
|
||||
models.Index(fields=["is_locked"]),
|
||||
models.Index(fields=["lock_until"]),
|
||||
models.Index(fields=['identifier']),
|
||||
models.Index(fields=['is_locked']),
|
||||
models.Index(fields=['lock_until']),
|
||||
]
|
||||
|
||||
def increment_attempts(self):
|
||||
"""Increment attempt count and handle locking"""
|
||||
self.attempts += 1
|
||||
self.last_attempt = timezone.now()
|
||||
|
||||
|
||||
# Lock after 3 failed attempts for 15 minutes
|
||||
if self.attempts >= 3:
|
||||
self.is_locked = True
|
||||
self.lock_until = timezone.now() + timedelta(minutes=15)
|
||||
|
||||
|
||||
self.save()
|
||||
|
||||
def reset_attempts(self):
|
||||
@@ -1386,11 +1369,11 @@ class OTPAttempt(models.Model):
|
||||
"""Check if the user is currently locked out"""
|
||||
if not self.is_locked:
|
||||
return False
|
||||
|
||||
|
||||
if self.lock_until and timezone.now() > self.lock_until:
|
||||
self.reset_attempts()
|
||||
return False
|
||||
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -1475,23 +1458,22 @@ class Parent(AbstractUser):
|
||||
"""
|
||||
# Clean up meeting records
|
||||
self.meetings.all().delete()
|
||||
|
||||
|
||||
# Clean up feedback records
|
||||
self.feedbacks.all().delete()
|
||||
self.institute_feedbacks.all().delete()
|
||||
|
||||
|
||||
# Clean up notification tokens
|
||||
self.fcm_token = None
|
||||
self.save(update_fields=["fcm_token"])
|
||||
self.save(update_fields=['fcm_token'])
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Parent"
|
||||
verbose_name_plural = "Parents"
|
||||
indexes = [
|
||||
models.Index(fields=["phone"]),
|
||||
models.Index(fields=["is_active"]),
|
||||
# For active parent lookups
|
||||
models.Index(fields=["phone", "is_active"]),
|
||||
models.Index(fields=['phone']),
|
||||
models.Index(fields=['is_active']),
|
||||
models.Index(fields=['phone', 'is_active']), # For active parent lookups
|
||||
]
|
||||
|
||||
|
||||
@@ -1525,11 +1507,11 @@ class TeacherParentMeeting(models.Model):
|
||||
ordering = ["-meeting_date", "-meeting_time"]
|
||||
unique_together = ["meeting_date", "meeting_time"]
|
||||
indexes = [
|
||||
models.Index(fields=["meeting_date"]),
|
||||
models.Index(fields=["status"]),
|
||||
models.Index(fields=["is_online"]),
|
||||
models.Index(fields=["meeting_date", "status"]), # For upcoming meetings
|
||||
models.Index(fields=["status", "is_online"]), # For online meetings
|
||||
models.Index(fields=['meeting_date']),
|
||||
models.Index(fields=['status']),
|
||||
models.Index(fields=['is_online']),
|
||||
models.Index(fields=['meeting_date', 'status']), # For upcoming meetings
|
||||
models.Index(fields=['status', 'is_online']), # For online meetings
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
@@ -1586,29 +1568,22 @@ class TeacherParentMeeting(models.Model):
|
||||
|
||||
class SubjectProgress(models.Model):
|
||||
"""Model for tracking student progress in individual subjects"""
|
||||
|
||||
student = models.ForeignKey(
|
||||
Student, on_delete=models.CASCADE, related_name="subject_progress"
|
||||
)
|
||||
subject = models.ForeignKey(
|
||||
Subject, on_delete=models.CASCADE, related_name="student_progress"
|
||||
)
|
||||
status = models.CharField(
|
||||
max_length=20, choices=PROGRESS_STATUS_CHOICES, default="Not Started"
|
||||
)
|
||||
student = models.ForeignKey(Student, on_delete=models.CASCADE, related_name='subject_progress')
|
||||
subject = models.ForeignKey(Subject, on_delete=models.CASCADE, related_name='student_progress')
|
||||
status = models.CharField(max_length=20, choices=PROGRESS_STATUS_CHOICES, default="Not Started")
|
||||
completion_percentage = models.IntegerField(default=0)
|
||||
last_updated = models.DateTimeField(auto_now=True)
|
||||
notes = models.TextField(blank=True, null=True)
|
||||
|
||||
class Meta:
|
||||
unique_together = ["student", "subject"]
|
||||
ordering = ["-last_updated"]
|
||||
unique_together = ['student', 'subject']
|
||||
ordering = ['-last_updated']
|
||||
indexes = [
|
||||
models.Index(fields=["student"]),
|
||||
models.Index(fields=["subject"]),
|
||||
models.Index(fields=["status"]),
|
||||
models.Index(fields=["student", "subject"]),
|
||||
models.Index(fields=["student", "status"]),
|
||||
models.Index(fields=['student']),
|
||||
models.Index(fields=['subject']),
|
||||
models.Index(fields=['status']),
|
||||
models.Index(fields=['student', 'subject']),
|
||||
models.Index(fields=['student', 'status']),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
|
||||
Reference in new issue
Block a user