614 lines
20 KiB
Python
614 lines
20 KiB
Python
import random
|
|
from decimal import Decimal
|
|
|
|
from django.contrib.auth.hashers import make_password
|
|
from django.contrib.auth.models import AbstractUser, Group, User
|
|
from django.core.validators import MaxValueValidator, MinValueValidator, RegexValidator
|
|
from django.db import models
|
|
|
|
|
|
# Abstract BaseUser Model
|
|
class BaseUser(models.Model):
|
|
is_active = models.BooleanField(default=True)
|
|
group = models.ManyToManyField(Group, blank=True)
|
|
first_name = models.CharField(max_length=255)
|
|
middle_name = models.CharField(max_length=255, blank=True, null=True)
|
|
last_name = models.CharField(max_length=255)
|
|
email = models.EmailField(max_length=255, unique=True)
|
|
phone = models.CharField(
|
|
max_length=10, validators=[RegexValidator(r"^\+?1?\d{9,15}$")]
|
|
)
|
|
username = models.CharField(max_length=255, unique=True, blank=True, null=True)
|
|
password = models.CharField(max_length=255)
|
|
# group = models.ManyToManyField(Group, blank=True)
|
|
user = models.OneToOneField(User, on_delete=models.CASCADE, null=True, blank=True)
|
|
|
|
class Meta:
|
|
abstract = True
|
|
|
|
def save(self, *args, **kwargs):
|
|
if not self.username:
|
|
self.username = f"{self.first_name.lower()}_{random.randint(1000, 9999)}"
|
|
|
|
if self._state.adding or not self.password.startswith("pbkdf2_"):
|
|
self.password = make_password(self.password)
|
|
|
|
if not self.user:
|
|
self.user = User.objects.create(
|
|
username=self.username,
|
|
email=self.email,
|
|
first_name=self.first_name,
|
|
last_name=self.last_name,
|
|
is_staff=True,
|
|
is_active=self.is_active,
|
|
password=self.password,
|
|
)
|
|
else:
|
|
self.user.username = self.username
|
|
self.user.email = self.email
|
|
self.user.first_name = self.first_name
|
|
self.user.last_name = self.last_name
|
|
self.user.is_active = self.is_active
|
|
self.user.is_staff = True
|
|
self.user.password = self.password
|
|
|
|
super().save(*args, **kwargs)
|
|
if hasattr(self, "group") and self.group.exists():
|
|
self.user.groups.set(self.group.all())
|
|
self.user.save()
|
|
|
|
def __str__(self):
|
|
return f"{self.first_name} {self.last_name}"
|
|
|
|
|
|
# Teacher model
|
|
class Teacher(BaseUser):
|
|
address = models.CharField(max_length=255)
|
|
gender = models.CharField(max_length=1, choices=[("M", "Male"), ("F", "Female")])
|
|
qualification_choice = (
|
|
("BA", "BA"),
|
|
("BBS", "BBS"),
|
|
("BE", "BE"),
|
|
("BEd", "BEd"),
|
|
("BSc", "BSc"),
|
|
("MA", "MA"),
|
|
("MSc", "MSc"),
|
|
("MBS", "MBS"),
|
|
("ME", "ME"),
|
|
("PhD", "PhD"),
|
|
("Others", "Others"),
|
|
)
|
|
qualification = models.CharField(choices=qualification_choice, max_length=45)
|
|
specialization = models.CharField(max_length=255)
|
|
marital_status_choice = (
|
|
("married", "Married"),
|
|
("widowed", "Widowed"),
|
|
("separated", "Separated"),
|
|
("divorced", "Divorced"),
|
|
("single", "Single"),
|
|
)
|
|
marital_status = models.CharField(choices=marital_status_choice, max_length=10)
|
|
date_of_joining = models.DateField()
|
|
|
|
class Meta:
|
|
verbose_name = "Teacher"
|
|
verbose_name_plural = "Teachers"
|
|
|
|
|
|
# Course model
|
|
class Course(models.Model):
|
|
name = models.CharField(max_length=100)
|
|
level_choice = (
|
|
("basic", "Basic"),
|
|
("primary", "Primary"),
|
|
("secondary", "Secondary"),
|
|
("vocational training", "Vocational Training"),
|
|
("higher secondary", "Higher Secondary"),
|
|
("graduation", "Graduation"),
|
|
("post graduation", "Post Graduation"),
|
|
)
|
|
level = models.CharField(choices=level_choice, max_length=45)
|
|
course_type = models.CharField(
|
|
max_length=10,
|
|
choices=[("semester", "Semester-based"), ("yearly", "Yearly-based")],
|
|
)
|
|
duration = models.PositiveIntegerField()
|
|
|
|
@property
|
|
def duration_display(self):
|
|
if self.course_type == "semester":
|
|
return (
|
|
f"{self.duration} {'Semester' if self.duration == 1 else 'Semesters'}"
|
|
)
|
|
else:
|
|
return f"{self.duration} {'Year' if self.duration == 1 else 'Years'}"
|
|
|
|
def __str__(self):
|
|
return f"{self.name} "
|
|
|
|
class Meta:
|
|
verbose_name = "Course"
|
|
verbose_name_plural = "Courses"
|
|
|
|
|
|
# AcademicYear model
|
|
class AcademicYear(models.Model):
|
|
year = models.CharField(max_length=255, unique=True)
|
|
|
|
def __str__(self):
|
|
return self.year
|
|
|
|
class Meta:
|
|
verbose_name = "Academic Year"
|
|
verbose_name_plural = "Academic Years"
|
|
|
|
|
|
# Student model
|
|
class Student(BaseUser):
|
|
date_of_birth = models.DateField(blank=False)
|
|
gender = models.CharField(max_length=1, choices=[("M", "Male"), ("F", "Female")])
|
|
address = models.CharField(max_length=255)
|
|
course = models.ForeignKey(Course, on_delete=models.CASCADE)
|
|
academic_year = models.ForeignKey(AcademicYear, on_delete=models.CASCADE)
|
|
father_name = models.CharField(max_length=100)
|
|
father_phone_no = models.CharField(
|
|
max_length=10, validators=[RegexValidator(r"^\+?1?\d{9,15}$")]
|
|
)
|
|
father_email = models.EmailField(blank=True, null=True)
|
|
occupation_choice = (
|
|
("agriculture", "Agriculture"),
|
|
("banker", "Banker"),
|
|
("business", "Business"),
|
|
("doctor", "Doctor"),
|
|
("farmer", "Farmer"),
|
|
("fisherman", "Fisherman"),
|
|
("public service", "Public Service"),
|
|
("private service", "Private Service"),
|
|
("shopkeeper", "Shopkeeper"),
|
|
("driver", "Driver"),
|
|
("housewife", "HouseWife"),
|
|
("worker", "Worker"),
|
|
("N/A", "N/A"),
|
|
)
|
|
father_occupation = models.CharField(
|
|
choices=occupation_choice, max_length=45, default="N/A"
|
|
)
|
|
mother_name = models.CharField(max_length=100)
|
|
mother_phone_no = models.CharField(
|
|
max_length=10,
|
|
validators=[RegexValidator(r"^\+?1?\d{9,15}$")],
|
|
blank=True,
|
|
null=True,
|
|
)
|
|
mother_email = models.EmailField(blank=True, null=True)
|
|
mother_occupation = models.CharField(
|
|
choices=occupation_choice, max_length=45, default="N/A"
|
|
)
|
|
guardian_name = models.CharField(max_length=100)
|
|
guardian_phone_no = models.CharField(
|
|
max_length=10, validators=[RegexValidator(r"^\+?1?\d{9,15}$")]
|
|
)
|
|
guardian_email = models.EmailField(blank=True, null=True)
|
|
relationship_choice = (
|
|
("father", "Father"),
|
|
("mother", "Mother"),
|
|
("brother", "Brother"),
|
|
("sister", "Sister"),
|
|
("uncle", "Uncle"),
|
|
("aunt", "Aunt"),
|
|
("grandfather", "Grandfather"),
|
|
)
|
|
relationship_with_student = models.CharField(
|
|
choices=relationship_choice, max_length=45
|
|
)
|
|
|
|
def save(self, *args, **kwargs):
|
|
super().save(*args, **kwargs)
|
|
|
|
if hasattr(self, "group") and self.group.exists():
|
|
if hasattr(self, "user"):
|
|
self.user.groups.set(self.group.all())
|
|
super().save(*args, **kwargs)
|
|
|
|
class Meta:
|
|
verbose_name = "Student"
|
|
verbose_name_plural = "Students"
|
|
|
|
|
|
# Staff model
|
|
class Staff(BaseUser):
|
|
gender = models.CharField(max_length=1, choices=[("M", "Male"), ("F", "Female")])
|
|
position = models.CharField(
|
|
max_length=255,
|
|
choices=[
|
|
("lab Assistant", "Lab Assistant"),
|
|
("librarian", "Librarian"),
|
|
("accountant", "Accountant"),
|
|
("guard", "Guard"),
|
|
("driver", "Driver"),
|
|
("helper", "Helper"),
|
|
("cleaner", "Cleaner"),
|
|
],
|
|
)
|
|
address = models.CharField(max_length=255)
|
|
marital_status_choice = (
|
|
("married", "Married"),
|
|
("widowed", "Widowed"),
|
|
("separated", "Separated"),
|
|
("divorced", "Divorced"),
|
|
("single", "Single"),
|
|
)
|
|
marital_status = models.CharField(choices=marital_status_choice, max_length=10)
|
|
date_of_joining = models.DateField()
|
|
|
|
class Meta:
|
|
verbose_name = "Staff"
|
|
verbose_name_plural = "Staff"
|
|
|
|
|
|
# Subject model
|
|
class Subject(models.Model):
|
|
name = models.CharField(max_length=255)
|
|
course = models.ForeignKey(
|
|
Course, on_delete=models.CASCADE, related_name="subjects"
|
|
)
|
|
semester_or_year_number = models.PositiveIntegerField()
|
|
teacher = models.ForeignKey(
|
|
Teacher,
|
|
on_delete=models.CASCADE,
|
|
related_name="subjects",
|
|
null=True,
|
|
blank=True,
|
|
)
|
|
|
|
@property
|
|
def period_display(self):
|
|
period_type = "Semester" if self.course.course_type == "semester" else "Year"
|
|
return f"{period_type} {self.semester_or_year_number}"
|
|
|
|
def __str__(self):
|
|
return f"{self.name} ({self.period_display})"
|
|
|
|
def clean(self):
|
|
from django.core.exceptions import ValidationError
|
|
|
|
if self.semester_or_year_number > self.course.duration:
|
|
period_type = (
|
|
"semester" if self.course.course_type == "semester" else "year"
|
|
)
|
|
raise ValidationError(
|
|
f"The {period_type} number cannot be greater than the course duration "
|
|
f"({self.course.duration})"
|
|
)
|
|
|
|
class Meta:
|
|
verbose_name = "Subject"
|
|
verbose_name_plural = "Subjects"
|
|
ordering = ["course", "semester_or_year_number", "name"]
|
|
|
|
|
|
# NewsEvent model
|
|
class NewsEvent(models.Model):
|
|
class NewsEventType(models.TextChoices):
|
|
NEWS = "news", "News"
|
|
EVENT = "event", "Event"
|
|
|
|
title = models.CharField(max_length=255)
|
|
summary = models.TextField()
|
|
type = models.CharField(max_length=5, choices=NewsEventType.choices)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
def __str__(self):
|
|
return f"{self.title} ({self.type})"
|
|
|
|
class Meta:
|
|
verbose_name = "News/Event"
|
|
verbose_name_plural = "News/Events"
|
|
|
|
|
|
# Attendance model
|
|
class Attendance(models.Model):
|
|
id = models.AutoField(primary_key=True)
|
|
student = models.ForeignKey(Student, on_delete=models.CASCADE)
|
|
session_year = models.ForeignKey(AcademicYear, on_delete=models.CASCADE)
|
|
subject = models.ForeignKey(Subject, on_delete=models.CASCADE)
|
|
date = models.DateField()
|
|
status = models.CharField(
|
|
max_length=10, choices=[("present", "Present"), ("absent", "Absent")]
|
|
)
|
|
|
|
class Meta:
|
|
verbose_name = "Attendance"
|
|
verbose_name_plural = "Attendance Records"
|
|
|
|
|
|
# Result model
|
|
class Result(models.Model):
|
|
id = models.AutoField(primary_key=True)
|
|
student = models.ForeignKey(Student, on_delete=models.CASCADE)
|
|
subject = models.ForeignKey(Subject, on_delete=models.CASCADE)
|
|
practical_marks = models.DecimalField(
|
|
max_digits=5, decimal_places=2, default=Decimal("0")
|
|
)
|
|
exam_marks = models.DecimalField(
|
|
max_digits=5, decimal_places=2, default=Decimal("0")
|
|
)
|
|
total_marks = models.DecimalField(
|
|
max_digits=5, decimal_places=2, default=Decimal("0"), editable=False
|
|
)
|
|
|
|
def get_total(self):
|
|
return Decimal(str(self.practical_marks or 0)) + Decimal(
|
|
str(self.exam_marks or 0)
|
|
)
|
|
|
|
def save(self, *args, **kwargs):
|
|
self.total_marks = self.get_total()
|
|
super().save(*args, **kwargs)
|
|
|
|
def __str__(self):
|
|
return f"{self.student} - {self.subject} - Total: {self.total_marks}"
|
|
|
|
class Meta:
|
|
verbose_name = "Result"
|
|
verbose_name_plural = "Results"
|
|
|
|
|
|
class Library(models.Model):
|
|
category = models.CharField(max_length=255)
|
|
book_name = models.CharField(max_length=255)
|
|
description = models.CharField(max_length=255)
|
|
publication = models.CharField(max_length=255)
|
|
publication_year = models.PositiveIntegerField()
|
|
quantity = models.PositiveIntegerField()
|
|
available = models.CharField(max_length=1, choices=[("Y", "Yes"), ("N", "NO")])
|
|
date_added = models.DateField(auto_now_add=True)
|
|
|
|
def __str__(self):
|
|
return self.book_name
|
|
|
|
class Meta:
|
|
verbose_name = "Library"
|
|
verbose_name_plural = "Library"
|
|
|
|
|
|
class Marksheet(models.Model):
|
|
student = models.ForeignKey(
|
|
Student, on_delete=models.CASCADE, related_name="marksheets"
|
|
)
|
|
course = models.ForeignKey(
|
|
Course, on_delete=models.CASCADE, related_name="marksheets"
|
|
)
|
|
period_number = models.PositiveIntegerField(
|
|
help_text="Select the semester/year number within the course duration."
|
|
)
|
|
total_marks = models.PositiveIntegerField(
|
|
validators=[MinValueValidator(0)],
|
|
default=Decimal("0.00"),
|
|
blank=True,
|
|
null=True,
|
|
)
|
|
obtained_marks = models.PositiveIntegerField(
|
|
validators=[MinValueValidator(0)], default=Decimal("0.00")
|
|
)
|
|
grade = models.CharField(
|
|
max_length=2,
|
|
choices=[
|
|
("A+", "A+"),
|
|
("A", "A"),
|
|
("B+", "B+"),
|
|
("B", "B"),
|
|
("C+", "C+"),
|
|
("C", "C"),
|
|
("D", "D"),
|
|
("E", "E"),
|
|
],
|
|
blank=True,
|
|
null=True,
|
|
)
|
|
|
|
class Meta:
|
|
verbose_name = "Marksheet"
|
|
verbose_name_plural = "Marksheets"
|
|
unique_together = ("student", "course", "period_number")
|
|
|
|
def calculate_totals(self):
|
|
subject_marks = self.subject_marks.all()
|
|
total_marks = sum(
|
|
subject.total_practical_marks + subject.total_theory_marks
|
|
for subject in subject_marks
|
|
) or Decimal("0.00")
|
|
obtained_marks = sum(
|
|
subject.obtained_practical_marks + subject.obtained_theory_marks
|
|
for subject in subject_marks
|
|
) or Decimal("0.00")
|
|
return total_marks, obtained_marks
|
|
|
|
def save(self, *args, **kwargs):
|
|
if not self.pk:
|
|
super().save(*args, **kwargs)
|
|
|
|
if self.period_number > self.course.duration:
|
|
raise ValueError(
|
|
f"The period number cannot exceed the course duration ({self.course.duration})."
|
|
)
|
|
|
|
self.total_marks, self.obtained_marks = self.calculate_totals()
|
|
|
|
if self.total_marks > 0:
|
|
percentage = (self.obtained_marks / self.total_marks) * 100
|
|
if percentage >= 90:
|
|
self.grade = "A+"
|
|
elif percentage >= 80:
|
|
self.grade = "A"
|
|
elif percentage >= 70:
|
|
self.grade = "B+"
|
|
elif percentage >= 60:
|
|
self.grade = "B"
|
|
elif percentage >= 50:
|
|
self.grade = "C+"
|
|
elif percentage >= 40:
|
|
self.grade = "C"
|
|
elif percentage >= 30:
|
|
self.grade = "D"
|
|
else:
|
|
self.grade = "E"
|
|
|
|
super().save(*args, **kwargs)
|
|
|
|
def __str__(self):
|
|
period_type = "Semester" if self.course.course_type == "semester" else "Year"
|
|
return f"{self.student} - {period_type} {self.period_number}"
|
|
|
|
|
|
class SubjectMark(models.Model):
|
|
marksheet = models.ForeignKey(
|
|
Marksheet, on_delete=models.CASCADE, related_name="subject_marks"
|
|
)
|
|
subject = models.ForeignKey(
|
|
"Subject", on_delete=models.CASCADE, related_name="subject_marks"
|
|
)
|
|
obtained_practical_marks = models.DecimalField(
|
|
max_digits=5, decimal_places=2, default=Decimal("0.00")
|
|
)
|
|
total_practical_marks = models.DecimalField(
|
|
max_digits=5, decimal_places=2, default=Decimal("20.00")
|
|
)
|
|
obtained_theory_marks = models.DecimalField(
|
|
max_digits=5, decimal_places=2, default=Decimal("0.00")
|
|
)
|
|
total_theory_marks = models.DecimalField(
|
|
max_digits=5, decimal_places=2, default=Decimal("80.00")
|
|
)
|
|
total_marks = models.DecimalField(
|
|
max_digits=5, decimal_places=2, default=Decimal("0.00")
|
|
)
|
|
|
|
def get_total(self):
|
|
return Decimal(str(self.obtained_practical_marks or 0)) + Decimal(
|
|
str(self.obtained_theory_marks or 0)
|
|
)
|
|
|
|
def save(self, *args, **kwargs):
|
|
self.total_marks = self.get_total()
|
|
super().save(*args, **kwargs)
|
|
self.marksheet.save()
|
|
|
|
class Meta:
|
|
unique_together = ("marksheet", "subject")
|
|
|
|
def __str__(self):
|
|
return f"{self.subject.name} - {self.marksheet.student.first_name}"
|
|
|
|
|
|
class Certificate(models.Model):
|
|
id = models.AutoField(primary_key=True)
|
|
student = models.ForeignKey(
|
|
Student, on_delete=models.CASCADE, related_name="certificate"
|
|
)
|
|
finished_year = models.CharField(max_length=5)
|
|
issued_by = models.CharField(max_length=255)
|
|
checked_by = models.CharField(max_length=255)
|
|
principle = models.CharField(max_length=255)
|
|
date = models.DateField()
|
|
|
|
def __str__(self):
|
|
return f"Certificate for {self.student.first_name} {self.student.last_name}"
|
|
|
|
|
|
class ClassRoutine(models.Model):
|
|
id = models.AutoField(primary_key=True)
|
|
course = models.ForeignKey(Course, on_delete=models.CASCADE)
|
|
subject = models.ForeignKey(Subject, on_delete=models.CASCADE)
|
|
teacher = models.ForeignKey(Teacher, on_delete=models.CASCADE)
|
|
start_time = models.TimeField()
|
|
end_time = models.TimeField()
|
|
|
|
|
|
class Complaint(models.Model):
|
|
id = models.AutoField(primary_key=True)
|
|
subject = models.CharField(max_length=255)
|
|
description = models.TextField()
|
|
date = models.DateField()
|
|
|
|
def __str__(self):
|
|
return self.subject
|
|
|
|
|
|
class Notes(models.Model):
|
|
id = models.AutoField(primary_key=True)
|
|
course = models.ForeignKey(Course, on_delete=models.CASCADE)
|
|
subject = models.ForeignKey(Subject, on_delete=models.CASCADE)
|
|
notes = models.FileField(upload_to="media/notes")
|
|
|
|
def __str__(self):
|
|
return self.subject.name
|
|
|
|
|
|
class StudentLeave(models.Model):
|
|
student_id = models.ForeignKey(Student, on_delete=models.CASCADE)
|
|
date = models.DateField()
|
|
message = models.TextField()
|
|
status = models.IntegerField(default=0)
|
|
created_at = models.DateTimeField(auto_now_add=True, null=True, blank=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
def __str__(self):
|
|
return self.student_id.first_name + self.student_id.last_name
|
|
|
|
|
|
class TeacherLeave(models.Model):
|
|
teacher_id = models.ForeignKey(Teacher, on_delete=models.CASCADE)
|
|
date = models.DateField()
|
|
message = models.TextField()
|
|
status = models.IntegerField(default=0)
|
|
created_at = models.DateTimeField(auto_now_add=True, null=True, blank=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
def __str__(self):
|
|
return self.teacher_id.first_name + self.teacher_id.last_name
|
|
|
|
|
|
class FeedbackStudent(models.Model):
|
|
id = models.AutoField(primary_key=True)
|
|
student_id = models.ForeignKey(Student, on_delete=models.CASCADE)
|
|
feedback = models.TextField()
|
|
feedback_reply = models.TextField(blank=True, null=True)
|
|
|
|
def __str__(self):
|
|
return self.feedback
|
|
|
|
|
|
class FeedbackTeacher(models.Model):
|
|
id = models.AutoField(primary_key=True)
|
|
staff_id = models.ForeignKey(Teacher, on_delete=models.CASCADE)
|
|
feedback = models.TextField()
|
|
feedback_reply = models.TextField(blank=True, null=True)
|
|
|
|
def __str__(self):
|
|
return self.feedback
|
|
|
|
|
|
class StudyMaterial(models.Model):
|
|
id = models.AutoField(primary_key=True)
|
|
course = models.ForeignKey(Course, on_delete=models.CASCADE)
|
|
subject = models.ForeignKey(Subject, on_delete=models.CASCADE)
|
|
title = models.CharField(max_length=255)
|
|
description = models.TextField()
|
|
resource_type = models.CharField(
|
|
max_length=7, choices=[("Video", "Video"), ("Article", "Article")]
|
|
)
|
|
url = models.URLField()
|
|
|
|
def __str__(self):
|
|
return self.subject.name
|
|
|
|
|
|
class Syllabus(models.Model):
|
|
id = models.AutoField(primary_key=True)
|
|
course = models.ForeignKey(Course, on_delete=models.CASCADE)
|
|
syllabus_file = models.FileField(upload_to="media/syllabus")
|
|
|
|
def __str__(self):
|
|
return self.course.name
|