Coverage for website/education/models.py: 93.51%
77 statements
« prev ^ index » next coverage.py v7.6.7, created at 2025-08-14 10:31 +0000
« prev ^ index » next coverage.py v7.6.7, created at 2025-08-14 10:31 +0000
1import datetime
3from django.db import models
4from django.db.models import Count, Q
5from django.urls import reverse
6from django.utils.translation import gettext_lazy as _
8from queryable_properties.managers import QueryablePropertiesManager
9from queryable_properties.properties import AggregateProperty
11from members.models import Member
12from utils.snippets import datetime_to_lectureyear
15class Category(models.Model):
16 """Describes a course category."""
18 name = models.CharField(
19 max_length=64,
20 )
22 def __str__(self):
23 return str(self.name)
25 def get_absolute_url(self):
26 return reverse("education:category", args=[str(self.pk)])
28 class Meta:
29 verbose_name = _("category")
30 verbose_name_plural = _("categories")
33class Course(models.Model):
34 """Describes a course."""
36 objects = QueryablePropertiesManager()
38 name = models.CharField(max_length=255)
40 categories = models.ManyToManyField(
41 Category, verbose_name=_("categories"), blank=True
42 )
44 old_courses = models.ManyToManyField(
45 "self", symmetrical=False, verbose_name=_("old courses"), blank=True
46 )
48 course_code = models.CharField(max_length=16)
50 ec = models.IntegerField(verbose_name=_("EC"))
52 since = models.IntegerField()
53 until = models.IntegerField(blank=True, null=True)
55 def __str__(self):
56 return f"{self.name} ({self.course_code})"
58 def get_absolute_url(self):
59 return reverse("education:course", args=[str(self.pk)])
61 summary_count = AggregateProperty(
62 Count("summary", filter=Q(summary__accepted=True), distinct=True)
63 )
64 exam_count = AggregateProperty(
65 Count("exam", filter=Q(exam__accepted=True), distinct=True)
66 )
68 class Meta:
69 ordering = ["-pk"]
70 verbose_name = _("course")
71 verbose_name_plural = _("courses")
74class Exam(models.Model):
75 """Describes an exam."""
77 EXAM_TYPES = (
78 ("document", _("Document")),
79 ("exam", _("Exam")),
80 ("partial", _("Partial Exam")),
81 ("resit", _("Resit")),
82 ("practice", _("Practice Exam")),
83 ("exam_answers", _("Exam Answers")),
84 ("partial_answers", _("Partial Exam Answers")),
85 ("resit_answers", _("Resit Answers")),
86 ("practice_answers", _("Practice Exam Answers")),
87 )
89 type = models.CharField(
90 max_length=40,
91 choices=EXAM_TYPES,
92 verbose_name=_("exam type"),
93 )
95 name = models.CharField(max_length=255, verbose_name=_("exam name"), blank=True)
97 uploader = models.ForeignKey(
98 Member,
99 verbose_name=_("uploader"),
100 on_delete=models.SET_NULL,
101 null=True,
102 )
104 uploader_date = models.DateField(default=datetime.date.today)
106 accepted = models.BooleanField(
107 verbose_name=_("accepted"),
108 default=False,
109 )
111 exam_date = models.DateField(
112 verbose_name=_("exam date"),
113 )
115 file = models.FileField(
116 upload_to="education/files/exams/",
117 help_text=_(
118 "Use the 'View on site' button to download the file for inspection."
119 ),
120 )
122 course = models.ForeignKey(
123 Course,
124 verbose_name=_("course"),
125 on_delete=models.CASCADE,
126 )
128 language = models.CharField(
129 max_length=2,
130 choices=[("en", "English"), ("nl", "Dutch")],
131 blank=False,
132 null=True,
133 )
135 download_count = models.IntegerField(
136 verbose_name=_("amount of downloads"),
137 default=0,
138 blank=False,
139 )
141 def __str__(self):
142 return f"{self.name.capitalize()} {self.type.capitalize()} ({self.course.name}, {self.course.course_code}, {self.exam_date})"
144 def get_absolute_url(self):
145 return reverse("education:exam", args=[str(self.pk)])
147 @property
148 def year(self):
149 return datetime_to_lectureyear(self.exam_date)
151 class Meta:
152 verbose_name = _("exam")
153 verbose_name_plural = _("exams")
156class Summary(models.Model):
157 """Describes a summary."""
159 name = models.CharField(
160 max_length=255,
161 verbose_name=_("summary name"),
162 )
164 uploader = models.ForeignKey(
165 Member,
166 verbose_name=_("uploader"),
167 on_delete=models.SET_NULL,
168 null=True,
169 )
171 uploader_date = models.DateField(default=datetime.date.today)
173 year = models.IntegerField()
175 author = models.CharField(
176 max_length=64,
177 verbose_name=_("author"),
178 )
180 course = models.ForeignKey(
181 Course,
182 verbose_name=_("course"),
183 on_delete=models.CASCADE,
184 )
186 accepted = models.BooleanField(
187 verbose_name=_("accepted"),
188 default=False,
189 )
191 file = models.FileField(
192 upload_to="education/files/summary/",
193 help_text=_(
194 "Use the 'View on site' button to download the file for inspection."
195 ),
196 )
198 language = models.CharField(
199 max_length=2,
200 choices=[("en", "English"), ("nl", "Dutch")],
201 blank=False,
202 null=True,
203 )
205 download_count = models.IntegerField(
206 verbose_name=_("amount of downloads"),
207 default=0,
208 blank=False,
209 )
211 def __str__(self):
212 return (
213 f"{self.name} ({self.course.name}, {self.course.course_code}, {self.year})"
214 )
216 def get_absolute_url(self):
217 return reverse("education:summary", args=[str(self.pk)])
219 class Meta:
220 verbose_name = _("summary")
221 verbose_name_plural = _("summaries")