217 lines
5.2 KiB
Python
217 lines
5.2 KiB
Python
from decimal import Decimal
|
|
|
|
from django.db import models
|
|
from django.urls import reverse
|
|
|
|
from accounts.models import Student
|
|
from core.models import Semester
|
|
from course.models import Course
|
|
|
|
# Constants
|
|
YEARS = (
|
|
(1, "1"),
|
|
(2, "2"),
|
|
(3, "3"),
|
|
(4, "4"),
|
|
(5, "5"),
|
|
(6, "6"),
|
|
)
|
|
|
|
BACHELOR_DEGREE = "Bachelor"
|
|
MASTER_DEGREE = "Master"
|
|
|
|
LEVEL_CHOICES = (
|
|
(BACHELOR_DEGREE, "Bachelor Degree"),
|
|
(MASTER_DEGREE, "Master Degree"),
|
|
)
|
|
|
|
FIRST = "First"
|
|
SECOND = "Second"
|
|
THIRD = "Third"
|
|
|
|
SEMESTER_CHOICES = (
|
|
(FIRST, "First"),
|
|
(SECOND, "Second"),
|
|
(THIRD, "Third"),
|
|
)
|
|
|
|
A_PLUS = "A+"
|
|
A = "A"
|
|
A_MINUS = "A-"
|
|
B_PLUS = "B+"
|
|
B = "B"
|
|
B_MINUS = "B-"
|
|
C_PLUS = "C+"
|
|
C = "C"
|
|
C_MINUS = "C-"
|
|
D = "D"
|
|
F = "F"
|
|
NG = "NG"
|
|
|
|
GRADE_CHOICES = (
|
|
(A_PLUS, "A+"),
|
|
(A, "A"),
|
|
(A_MINUS, "A-"),
|
|
(B_PLUS, "B+"),
|
|
(B, "B"),
|
|
(B_MINUS, "B-"),
|
|
(C_PLUS, "C+"),
|
|
(C, "C"),
|
|
(C_MINUS, "C-"),
|
|
(D, "D"),
|
|
(F, "F"),
|
|
(NG, "NG"),
|
|
)
|
|
|
|
PASS = "PASS"
|
|
FAIL = "FAIL"
|
|
|
|
COMMENT_CHOICES = (
|
|
(PASS, "PASS"),
|
|
(FAIL, "FAIL"),
|
|
)
|
|
|
|
GRADE_BOUNDARIES = [
|
|
(90, A_PLUS),
|
|
(85, A),
|
|
(80, A_MINUS),
|
|
(75, B_PLUS),
|
|
(70, B),
|
|
(65, B_MINUS),
|
|
(60, C_PLUS),
|
|
(55, C),
|
|
(50, C_MINUS),
|
|
(45, D),
|
|
(0, F),
|
|
]
|
|
|
|
GRADE_POINT_MAPPING = {
|
|
A_PLUS: 4.0,
|
|
A: 4.0,
|
|
A_MINUS: 3.75,
|
|
B_PLUS: 3.5,
|
|
B: 3.0,
|
|
B_MINUS: 2.75,
|
|
C_PLUS: 2.5,
|
|
C: 2.0,
|
|
C_MINUS: 1.75,
|
|
D: 1.0,
|
|
F: 0.0,
|
|
NG: 0.0,
|
|
}
|
|
|
|
|
|
class TakenCourse(models.Model):
|
|
student = models.ForeignKey(Student, on_delete=models.CASCADE)
|
|
course = models.ForeignKey(
|
|
Course, on_delete=models.CASCADE, related_name="taken_courses"
|
|
)
|
|
assignment = models.DecimalField(
|
|
max_digits=5, decimal_places=2, default=Decimal("0.00")
|
|
)
|
|
mid_exam = models.DecimalField(
|
|
max_digits=5, decimal_places=2, default=Decimal("0.00")
|
|
)
|
|
quiz = models.DecimalField(max_digits=5, decimal_places=2, default=Decimal("0.00"))
|
|
attendance = models.DecimalField(
|
|
max_digits=5, decimal_places=2, default=Decimal("0.00")
|
|
)
|
|
final_exam = models.DecimalField(
|
|
max_digits=5, decimal_places=2, default=Decimal("0.00")
|
|
)
|
|
total = models.DecimalField(
|
|
max_digits=5, decimal_places=2, default=Decimal("0.00"), editable=False
|
|
)
|
|
grade = models.CharField(
|
|
choices=GRADE_CHOICES, max_length=2, blank=True, editable=False
|
|
)
|
|
point = models.DecimalField(
|
|
max_digits=5, decimal_places=2, default=Decimal("0.00"), editable=False
|
|
)
|
|
comment = models.CharField(
|
|
choices=COMMENT_CHOICES, max_length=200, blank=True, editable=False
|
|
)
|
|
|
|
def get_absolute_url(self):
|
|
return reverse("course_detail", kwargs={"slug": self.course.slug})
|
|
|
|
def __str__(self):
|
|
return f"{self.course.title} ({self.course.code})"
|
|
|
|
def get_total(self):
|
|
return sum(
|
|
[
|
|
self.assignment,
|
|
self.mid_exam,
|
|
self.quiz,
|
|
self.attendance,
|
|
self.final_exam,
|
|
]
|
|
)
|
|
|
|
def get_grade(self):
|
|
total = self.total
|
|
for boundary, grade in GRADE_BOUNDARIES:
|
|
if total >= boundary:
|
|
return grade
|
|
return NG
|
|
|
|
def get_comment(self):
|
|
if self.grade in [F, NG]:
|
|
return FAIL
|
|
return PASS
|
|
|
|
def get_point(self):
|
|
credit = self.course.credit
|
|
grade_point = GRADE_POINT_MAPPING.get(self.grade, 0.0)
|
|
return Decimal(credit) * Decimal(grade_point)
|
|
|
|
def save(self, *args, **kwargs):
|
|
self.total = self.get_total()
|
|
self.grade = self.get_grade()
|
|
self.point = self.get_point()
|
|
self.comment = self.get_comment()
|
|
super().save(*args, **kwargs)
|
|
|
|
def calculate_gpa(self):
|
|
current_semester = Semester.objects.filter(is_current_semester=True).first()
|
|
if not current_semester:
|
|
return Decimal("0.00")
|
|
|
|
taken_courses = TakenCourse.objects.filter(
|
|
student=self.student,
|
|
course__level=self.student.level,
|
|
course__semester=current_semester.semester,
|
|
)
|
|
|
|
total_points = sum(tc.point for tc in taken_courses)
|
|
total_credits = sum(tc.course.credit for tc in taken_courses)
|
|
|
|
if total_credits > 0:
|
|
gpa = total_points / Decimal(total_credits)
|
|
return round(gpa, 2)
|
|
return Decimal("0.00")
|
|
|
|
def calculate_cgpa(self):
|
|
taken_courses = TakenCourse.objects.filter(student=self.student)
|
|
|
|
total_points = sum(tc.point for tc in taken_courses)
|
|
total_credits = sum(tc.course.credit for tc in taken_courses)
|
|
|
|
if total_credits > 0:
|
|
cgpa = total_points / Decimal(total_credits)
|
|
return round(cgpa, 2)
|
|
return Decimal("0.00")
|
|
|
|
|
|
class Result(models.Model):
|
|
student = models.ForeignKey(Student, on_delete=models.CASCADE)
|
|
gpa = models.FloatField(null=True)
|
|
cgpa = models.FloatField(null=True)
|
|
semester = models.CharField(max_length=100, choices=SEMESTER_CHOICES)
|
|
session = models.CharField(max_length=100, blank=True, null=True)
|
|
level = models.CharField(max_length=25, choices=LEVEL_CHOICES, null=True)
|
|
|
|
def __str__(self):
|
|
return f"Result for {self.student} - Semester: {self.semester}, Level: {self.level}"
|