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

1import datetime 

2 

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 _ 

7 

8from queryable_properties.managers import QueryablePropertiesManager 

9from queryable_properties.properties import AggregateProperty 

10 

11from members.models import Member 

12from utils.snippets import datetime_to_lectureyear 

13 

14 

15class Category(models.Model): 

16 """Describes a course category.""" 

17 

18 name = models.CharField( 

19 max_length=64, 

20 ) 

21 

22 def __str__(self): 

23 return str(self.name) 

24 

25 def get_absolute_url(self): 

26 return reverse("education:category", args=[str(self.pk)]) 

27 

28 class Meta: 

29 verbose_name = _("category") 

30 verbose_name_plural = _("categories") 

31 

32 

33class Course(models.Model): 

34 """Describes a course.""" 

35 

36 objects = QueryablePropertiesManager() 

37 

38 name = models.CharField(max_length=255) 

39 

40 categories = models.ManyToManyField( 

41 Category, verbose_name=_("categories"), blank=True 

42 ) 

43 

44 old_courses = models.ManyToManyField( 

45 "self", symmetrical=False, verbose_name=_("old courses"), blank=True 

46 ) 

47 

48 course_code = models.CharField(max_length=16) 

49 

50 ec = models.IntegerField(verbose_name=_("EC")) 

51 

52 since = models.IntegerField() 

53 until = models.IntegerField(blank=True, null=True) 

54 

55 def __str__(self): 

56 return f"{self.name} ({self.course_code})" 

57 

58 def get_absolute_url(self): 

59 return reverse("education:course", args=[str(self.pk)]) 

60 

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 ) 

67 

68 class Meta: 

69 ordering = ["-pk"] 

70 verbose_name = _("course") 

71 verbose_name_plural = _("courses") 

72 

73 

74class Exam(models.Model): 

75 """Describes an exam.""" 

76 

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 ) 

88 

89 type = models.CharField( 

90 max_length=40, 

91 choices=EXAM_TYPES, 

92 verbose_name=_("exam type"), 

93 ) 

94 

95 name = models.CharField(max_length=255, verbose_name=_("exam name"), blank=True) 

96 

97 uploader = models.ForeignKey( 

98 Member, 

99 verbose_name=_("uploader"), 

100 on_delete=models.SET_NULL, 

101 null=True, 

102 ) 

103 

104 uploader_date = models.DateField(default=datetime.date.today) 

105 

106 accepted = models.BooleanField( 

107 verbose_name=_("accepted"), 

108 default=False, 

109 ) 

110 

111 exam_date = models.DateField( 

112 verbose_name=_("exam date"), 

113 ) 

114 

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 ) 

121 

122 course = models.ForeignKey( 

123 Course, 

124 verbose_name=_("course"), 

125 on_delete=models.CASCADE, 

126 ) 

127 

128 language = models.CharField( 

129 max_length=2, 

130 choices=[("en", "English"), ("nl", "Dutch")], 

131 blank=False, 

132 null=True, 

133 ) 

134 

135 download_count = models.IntegerField( 

136 verbose_name=_("amount of downloads"), 

137 default=0, 

138 blank=False, 

139 ) 

140 

141 def __str__(self): 

142 return f"{self.name.capitalize()} {self.type.capitalize()} ({self.course.name}, {self.course.course_code}, {self.exam_date})" 

143 

144 def get_absolute_url(self): 

145 return reverse("education:exam", args=[str(self.pk)]) 

146 

147 @property 

148 def year(self): 

149 return datetime_to_lectureyear(self.exam_date) 

150 

151 class Meta: 

152 verbose_name = _("exam") 

153 verbose_name_plural = _("exams") 

154 

155 

156class Summary(models.Model): 

157 """Describes a summary.""" 

158 

159 name = models.CharField( 

160 max_length=255, 

161 verbose_name=_("summary name"), 

162 ) 

163 

164 uploader = models.ForeignKey( 

165 Member, 

166 verbose_name=_("uploader"), 

167 on_delete=models.SET_NULL, 

168 null=True, 

169 ) 

170 

171 uploader_date = models.DateField(default=datetime.date.today) 

172 

173 year = models.IntegerField() 

174 

175 author = models.CharField( 

176 max_length=64, 

177 verbose_name=_("author"), 

178 ) 

179 

180 course = models.ForeignKey( 

181 Course, 

182 verbose_name=_("course"), 

183 on_delete=models.CASCADE, 

184 ) 

185 

186 accepted = models.BooleanField( 

187 verbose_name=_("accepted"), 

188 default=False, 

189 ) 

190 

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 ) 

197 

198 language = models.CharField( 

199 max_length=2, 

200 choices=[("en", "English"), ("nl", "Dutch")], 

201 blank=False, 

202 null=True, 

203 ) 

204 

205 download_count = models.IntegerField( 

206 verbose_name=_("amount of downloads"), 

207 default=0, 

208 blank=False, 

209 ) 

210 

211 def __str__(self): 

212 return ( 

213 f"{self.name} ({self.course.name}, {self.course.course_code}, {self.year})" 

214 ) 

215 

216 def get_absolute_url(self): 

217 return reverse("education:summary", args=[str(self.pk)]) 

218 

219 class Meta: 

220 verbose_name = _("summary") 

221 verbose_name_plural = _("summaries")