Minor changes to courses
- Author
- Maarten 'Vngngdn' Vangeneugden
- Date
- Nov. 26, 2017, 2:02 a.m.
- Hash
- 1f2fa3814594fb45c0f1e13535e977ccbcf3b38d
- Parent
- 07e85b805e87d9e192d097a329645ca554d9fd37
- Modified files
- courses/models.py
- courses/urls.py
- courses/views.py
courses/models.py ¶
9 additions and 1 deletion.
View changes Hide changes
1 |
1 |
from django.utils.translation import ugettext_lazy as _ |
2 |
2 |
|
3 |
3 |
class Course(models.Model): |
4 |
4 |
""" Represents a course that is taught at the university. """ |
5 |
5 |
number = models.PositiveSmallIntegerField( |
6 |
6 |
primary_key=True, |
7 |
7 |
blank=False, |
8 |
8 |
help_text=_("The number associated with this course. A leading '0' will be added if the number is smaller than 1000."), |
9 |
9 |
) |
10 |
10 |
name = models.CharField( |
11 |
11 |
max_length=64, |
12 |
12 |
blank=False, |
13 |
13 |
help_text=_("The name of this course, in the language that it is taught. Translations are for the appropriate template."), |
14 |
14 |
) |
15 |
15 |
color = models.CharField( |
16 |
16 |
max_length=6, |
17 |
17 |
blank=False, |
18 |
18 |
default=constants.COLORS['uhasselt-default'], |
19 |
19 |
help_text=_("The color for this course. Must be an hexadecimal code."), |
20 |
20 |
validators=['validate_hex_color'], |
21 |
21 |
) |
22 |
22 |
slug_name = models.SlugField( |
23 |
23 |
blank=False, |
24 |
24 |
allow_unicode=True, |
25 |
25 |
unique=True, |
26 |
26 |
help_text=_("A so-called 'slug name' for this course."), |
27 |
27 |
) |
28 |
28 |
# TODO: Add a potential thingy magicky to auto fill the slug name on the course name |
29 |
29 |
contact_person = models.ForeignKey( |
30 |
30 |
"administration.User", |
31 |
31 |
on_delete=models.PROTECT, # A course must have a contact person |
32 |
32 |
limit_choices_to={'is_staff': True}, |
33 |
33 |
null=False, |
34 |
34 |
help_text=_("The person to contact regarding this course."), |
35 |
35 |
related_name="contact_person", |
36 |
36 |
) |
37 |
37 |
coordinator = models.ForeignKey( |
38 |
38 |
"administration.User", |
39 |
39 |
on_delete=models.PROTECT, # A course must have a coordinator |
40 |
40 |
limit_choices_to={'is_staff': True}, |
41 |
41 |
null=False, |
42 |
42 |
help_text=_("The person whom's the coordinator of this course."), |
43 |
43 |
related_name="coordinator", |
44 |
44 |
) |
45 |
45 |
educating_team = models.ManyToManyField( |
46 |
46 |
"administration.User", |
47 |
47 |
# No on_delete, since M->M cannot be required at database level |
48 |
48 |
limit_choices_to={'is_staff': True}, |
49 |
49 |
#null=False, # Useless on a M->M |
50 |
50 |
help_text=_("The team members of this course."), |
51 |
- | related_name="educating_team", |
+ |
51 |
related_name="educating_team", |
52 |
52 |
) |
53 |
53 |
language = models.CharField( |
54 |
54 |
max_length=64, |
55 |
55 |
choices = ( |
56 |
56 |
('NL', _("Dutch")), |
57 |
57 |
('EN', _("English")), |
58 |
58 |
('FR', _("French")), |
59 |
59 |
), |
60 |
60 |
null=False, |
61 |
61 |
help_text=_("The language in which this course is given."), |
62 |
62 |
) |
63 |
63 |
|
64 |
64 |
def __str__(self): |
+ |
65 |
""" Returns a set of all Users that are part of the team of this course. """ |
+ |
66 |
return set( |
+ |
67 |
self.contact_person, |
+ |
68 |
self.coordinator, |
+ |
69 |
self.educating_team, |
+ |
70 |
) |
+ |
71 |
|
+ |
72 |
def __str__(self): |
65 |
73 |
number = str(self.number) |
66 |
74 |
for i in [10,100,1000]: |
67 |
75 |
if self.number < i: |
68 |
76 |
number = "0" + number |
69 |
77 |
return "(" + number + ") " + self.name |
70 |
78 |
|
71 |
79 |
|
72 |
80 |
class Prerequisite(models.Model): |
73 |
81 |
""" Represents a collection of prerequisites a student must have obtained |
74 |
82 |
before being allowed to partake in this course. |
75 |
83 |
It's possible that, if a student has obtained credits in a certain set of |
76 |
84 |
courses, a certain part of the prerequisites do not have to be obtained. |
77 |
85 |
Because of this, make a different record for each different set. In other |
78 |
86 |
words: If one set of prerequisites is obtained, and another one isn't, BUT |
79 |
87 |
they point to the same course, the student is allowed to partake. """ |
80 |
88 |
course = models.ForeignKey( |
81 |
89 |
"Course", |
82 |
90 |
on_delete=models.CASCADE, |
83 |
91 |
null=False, |
84 |
92 |
help_text=_("The course that these prerequisites are for."), |
85 |
93 |
related_name="prerequisite_course", |
86 |
94 |
) |
87 |
95 |
name = models.CharField( |
88 |
96 |
max_length=64, |
89 |
97 |
blank=True, |
90 |
98 |
help_text=_("To specify a name for this set, if necessary."), |
91 |
99 |
) |
92 |
100 |
sequentialities = models.ManyToManyField( |
93 |
101 |
"Course", |
94 |
102 |
help_text=_("All courses for which a credit must've been received in order to follow the course."), |
95 |
103 |
related_name="sequentialities", |
96 |
104 |
) |
97 |
105 |
in_curriculum = models.ManyToManyField( |
98 |
106 |
"Course", |
99 |
107 |
help_text=_("All courses that have to be in the curriculum to follow this. If a credit was achieved, that course can be omitted."), |
100 |
108 |
related_name="in_curriculum", |
101 |
109 |
) |
102 |
110 |
required_study = models.ForeignKey( |
103 |
111 |
"Study", |
104 |
112 |
on_delete=models.CASCADE, |
105 |
113 |
null=True, |
106 |
114 |
help_text=_("If one must have a certain amount of obtained ECTS points for a particular course, state that course here."), |
107 |
115 |
) |
108 |
116 |
ECTS_for_required_study = models.PositiveSmallIntegerField( |
109 |
117 |
null=True, |
110 |
118 |
help_text=_("The amount of obtained ECTS points for the required course, if any."), |
111 |
119 |
) |
112 |
120 |
|
113 |
121 |
def __str__(self): |
114 |
122 |
if self.name == "": |
115 |
123 |
return _("Prerequisites for %(course)s") % {'course': str(self.course)} |
116 |
124 |
else: |
117 |
125 |
return self.name + " | " + str(self.course) |
118 |
126 |
|
119 |
127 |
|
120 |
128 |
class CourseProgramme(models.Model): |
121 |
129 |
""" It's possible that a course is taught in multiple degree programmes; For |
122 |
130 |
example: Calculus can easily be taught to physics and mathematics students |
123 |
131 |
alike. In this table, these relations are set up, and the related properties |
124 |
132 |
are defined as well. """ |
125 |
133 |
study = models.ForeignKey( |
126 |
134 |
"Study", |
127 |
135 |
on_delete=models.CASCADE, |
128 |
136 |
null=False, |
129 |
137 |
help_text=_("The study in which the course is taught."), |
130 |
138 |
) |
131 |
139 |
course = models.ForeignKey( |
132 |
140 |
"Course", |
133 |
141 |
on_delete=models.CASCADE, |
134 |
142 |
null=False, |
135 |
143 |
help_text=_("The course that this programme is for."), |
136 |
144 |
) |
137 |
145 |
study_programme = models.ForeignKey( |
138 |
146 |
"StudyProgramme", |
139 |
147 |
on_delete=models.CASCADE, |
140 |
148 |
null=False, |
141 |
149 |
help_text=_("The study programme that this course belongs to."), |
142 |
150 |
) |
143 |
151 |
programme_type = models.CharField( |
144 |
152 |
max_length=1, |
145 |
153 |
blank=False, |
146 |
154 |
choices = ( |
147 |
155 |
('C', _("Compulsory")), |
148 |
156 |
('O', _("Optional")), |
149 |
157 |
), |
150 |
158 |
help_text=_("Type of this course for this study."), |
151 |
159 |
) |
152 |
160 |
study_hours = models.PositiveSmallIntegerField( |
153 |
161 |
blank=False, |
154 |
162 |
help_text=_("The required amount of hours to study this course."), |
155 |
163 |
) |
156 |
164 |
ECTS = models.PositiveSmallIntegerField( |
157 |
165 |
blank=False, |
158 |
166 |
help_text=_("The amount of ECTS points attached to this course."), |
159 |
167 |
) |
160 |
168 |
semester = models.PositiveSmallIntegerField( |
161 |
169 |
blank=False, |
162 |
170 |
choices = ( |
163 |
171 |
(1, _("First semester")), |
164 |
172 |
(2, _("Second semester")), |
165 |
173 |
(3, _("Full year course")), |
166 |
174 |
(4, _("Taught in first quarter")), |
167 |
175 |
(5, _("Taught in second quarter")), |
168 |
176 |
(6, _("Taught in third quarter")), |
169 |
177 |
(7, _("Taught in fourth quarter")), |
170 |
178 |
), |
171 |
179 |
help_text=_("The period in which this course is being taught in this study."), |
172 |
180 |
) |
173 |
181 |
year = models.PositiveSmallIntegerField( |
174 |
182 |
blank=False, |
175 |
183 |
help_text=_("The year in which this course is taught for this study."), |
176 |
184 |
) |
177 |
185 |
second_chance = models.BooleanField( |
178 |
186 |
default=True, |
179 |
187 |
help_text=_("Defines if a second chance exam is planned for this course."), |
180 |
188 |
) |
181 |
189 |
tolerable = models.BooleanField( |
182 |
190 |
default=True, |
183 |
191 |
help_text=_("Defines if a failed result can be tolerated."), |
184 |
192 |
) |
185 |
193 |
scoring = models.CharField( |
186 |
194 |
max_length=2, |
187 |
195 |
choices = ( |
188 |
196 |
('N', _("Numerical")), |
189 |
197 |
('FP', _("Fail/Pass")), |
190 |
198 |
), |
191 |
199 |
default='N', |
192 |
200 |
blank=False, |
193 |
201 |
help_text=_("How the obtained score for this course is given."), |
194 |
202 |
) |
195 |
203 |
|
196 |
204 |
def __str__(self): |
197 |
205 |
return str(self.study) + " - " + str(self.course) |
198 |
206 |
|
199 |
207 |
class Study(models.Model): |
200 |
208 |
""" Defines a certain study that can be followed at the university. |
201 |
209 |
This also includes abridged study programmes, like transition programmes. |
202 |
210 |
Other information, such as descriptions, are kept in the template file |
203 |
211 |
of this study, which can be manually edited. Joeni searches for a file |
204 |
212 |
with the exact name as the study + ".html". So if the study is called |
205 |
213 |
"Bachelor of Informatics", it will search for "Bachelor of Informatics.html". |
206 |
214 |
""" |
207 |
215 |
# Degree types |
208 |
216 |
BSc = _("Bachelor of Science") |
209 |
217 |
MSc = _("Master of Science") |
210 |
218 |
LLB = _("Bachelor of Laws") |
211 |
219 |
LLM = _("Master of Laws") |
212 |
220 |
ir = _("Engineer") |
213 |
221 |
ing = _("Technological Engineer") |
214 |
222 |
# Faculties |
215 |
223 |
FoMaLS = _("Faculty of Medicine and Life Sciences") |
216 |
224 |
FoS = _("Faculty of Sciences") |
217 |
225 |
FoTS = _("Faculty of Transportation Sciences") |
218 |
226 |
FoAaA = _("Faculty of Architecture and Arts") |
219 |
227 |
FoBE = _("Faculty of Business Economics") |
220 |
228 |
FoET = _("Faculty of Engineering Technology") |
221 |
229 |
FoL = _("Faculty of Law") |
222 |
230 |
|
223 |
231 |
name = models.CharField( |
224 |
232 |
max_length=128, |
225 |
233 |
blank=False, |
226 |
234 |
unique=True, |
227 |
235 |
help_text=_("The full name of this study, in the language it's taught in."), |
228 |
236 |
) |
229 |
237 |
degree_type = models.CharField( |
230 |
238 |
max_length=64, |
231 |
239 |
choices = ( |
232 |
240 |
('BSc', BSc), |
233 |
241 |
('MSc', MSc), |
234 |
242 |
('LL.B', LLB), |
235 |
243 |
('LL.M', LLM), |
236 |
244 |
('ir.', ir ), |
237 |
245 |
('ing.',ing), |
238 |
246 |
), |
239 |
247 |
blank=False, |
240 |
248 |
help_text=_("The type of degree one obtains upon passing this study."), |
241 |
249 |
) |
242 |
250 |
language = models.CharField( |
243 |
251 |
max_length=64, |
244 |
252 |
choices = ( |
245 |
253 |
('NL', _("Dutch")), |
246 |
254 |
('EN', _("English")), |
247 |
255 |
('FR', _("French")), |
248 |
256 |
), |
249 |
257 |
null=False, |
250 |
258 |
help_text=_("The language in which this study is given."), |
251 |
259 |
) |
252 |
260 |
# Information about exam committee |
253 |
261 |
chairman = models.ForeignKey( |
254 |
262 |
"administration.User", |
255 |
263 |
on_delete=models.PROTECT, |
256 |
264 |
null=False, |
257 |
265 |
limit_choices_to={'is_staff': True}, |
258 |
266 |
help_text=_("The chairman of this study."), |
259 |
267 |
related_name="chairman", |
260 |
268 |
) |
261 |
269 |
vice_chairman = models.ForeignKey( |
262 |
270 |
"administration.User", |
263 |
271 |
on_delete=models.PROTECT, |
264 |
272 |
null=False, |
265 |
273 |
help_text=_("The vice-chairman of this study."), |
266 |
274 |
limit_choices_to={'is_staff': True}, |
267 |
275 |
related_name="vice_chairman", |
268 |
276 |
) |
269 |
277 |
secretary = models.ForeignKey( |
270 |
278 |
"administration.User", |
271 |
279 |
on_delete=models.PROTECT, |
272 |
280 |
null=False, |
273 |
281 |
help_text=_("The secretary of this study."), |
274 |
282 |
limit_choices_to={'is_staff': True}, |
275 |
283 |
related_name="secretary", |
276 |
284 |
) |
277 |
285 |
ombuds = models.ForeignKey( |
278 |
286 |
"administration.User", |
279 |
287 |
on_delete=models.PROTECT, |
280 |
288 |
null=False, |
281 |
289 |
help_text=_("The ombuds person of this study."), |
282 |
290 |
limit_choices_to={'is_staff': True}, |
283 |
291 |
related_name="ombuds", |
284 |
292 |
) |
285 |
293 |
vice_ombuds = models.ForeignKey( |
286 |
294 |
"administration.User", |
287 |
295 |
on_delete=models.PROTECT, |
288 |
296 |
null=False, |
289 |
297 |
help_text=_("The (replacing) ombuds person of this study."), |
290 |
298 |
limit_choices_to={'is_staff': True}, |
291 |
299 |
related_name="vice_ombuds", |
292 |
300 |
) |
293 |
301 |
additional_members = models.ManyToManyField( |
294 |
302 |
"administration.User", |
295 |
303 |
help_text=_("All the other members of the exam committee."), |
296 |
304 |
limit_choices_to={'is_staff': True}, |
297 |
305 |
related_name="additional_members", |
298 |
306 |
) |
299 |
307 |
faculty = models.CharField( |
300 |
308 |
max_length=6, |
301 |
309 |
choices = ( |
302 |
310 |
('FoS', FoS), |
303 |
311 |
('FoTS', FoTS), |
304 |
312 |
('FoAaA', FoAaA), |
305 |
313 |
('FoBE', FoBE), |
306 |
314 |
('FoMaLS', FoMaLS), |
307 |
315 |
('FoET', FoET), |
308 |
316 |
('FoL', FoL), |
309 |
317 |
), |
310 |
318 |
blank=False, |
311 |
319 |
help_text=_("The faculty where this study belongs to."), |
312 |
320 |
) |
313 |
321 |
|
314 |
322 |
#def study_points(self): |
315 |
323 |
""" Returns the amount of study points for this year. |
316 |
324 |
This value is inferred based on the study programme information |
317 |
325 |
records that lists this study as their foreign key. """ |
318 |
326 |
#total_ECTS = 0 |
319 |
327 |
#for course in CourseProgramme.objects.filter(study=self): |
320 |
328 |
#total_ECTS += course.ECTS |
321 |
329 |
#return total_ECTS |
322 |
330 |
# XXX: Commented because this is actually something for the StudyProgramme |
323 |
331 |
def years(self): |
324 |
332 |
""" Returns the amount of years this study takes. |
325 |
333 |
This value is inferred based on the study programme information |
326 |
334 |
records that lists this study as their foreign key. """ |
327 |
335 |
highest_year = 0 |
328 |
336 |
for course in CourseProgramme.objects.filter(study=self): |
329 |
337 |
highest_year = max(highest_year, course.year) |
330 |
338 |
return highest_year |
331 |
339 |
|
332 |
340 |
def students(self): |
333 |
341 |
""" Cross references the information stored in the database, and |
334 |
342 |
returns all the students that are following this study in this |
335 |
343 |
academic year. """ |
336 |
344 |
return 0 # TODO |
337 |
345 |
|
338 |
346 |
|
339 |
347 |
def __str__(self): |
340 |
348 |
return self.name |
341 |
349 |
|
342 |
350 |
class StudyProgramme(models.Model): |
343 |
351 |
""" Represents a programme within a certain study. |
344 |
352 |
A good example for this is the different specializations, minors, majors, ... |
345 |
353 |
one can follow within the same study. Nevertheless, they're all made of |
346 |
354 |
a certain set of courses. This table collects all these, and allows one to name |
347 |
355 |
them, so they're distinct from one another. """ |
348 |
356 |
name = models.CharField( |
349 |
357 |
max_length=64, |
350 |
358 |
blank=False, |
351 |
359 |
help_text=_("The name of this programme."), |
352 |
360 |
) |
353 |
361 |
|
354 |
362 |
def courses(self): |
355 |
363 |
""" All courses that are part of this study programme. """ |
356 |
364 |
programmes = CourseProgramme.objects.filter(study_programme=self) |
357 |
365 |
courses = {} |
358 |
366 |
for program in programmes: |
359 |
367 |
courses.add(program.course) |
360 |
368 |
return courses |
361 |
369 |
|
362 |
370 |
def study_points(self, year=None): |
363 |
371 |
""" Returns the amount of study points this programme contains. |
364 |
372 |
Accepts year as an optional argument. If not given, the study points |
365 |
373 |
of all years are returned. """ |
366 |
374 |
programmes = CourseProgramme.objects.filter(study_programme=self) |
367 |
375 |
ECTS = 0 |
368 |
376 |
for program in programmes: |
369 |
377 |
if year is None or program.year == year: |
370 |
378 |
# XXX: This only works if the used implementation does lazy |
371 |
379 |
# evaluation, otherwise this is a type error! |
372 |
380 |
ECTS += program.ECTS |
373 |
381 |
return ECTS |
374 |
382 |
|
375 |
383 |
def __str__(self): |
376 |
384 |
return self.name |
377 |
385 |
|
378 |
386 |
# Tables about things related to the courses: |
379 |
387 |
|
380 |
388 |
class Assignment(models.Model): |
381 |
389 |
""" For courses, it's possible to set up tasks. These tasks are recorded |
382 |
390 |
here. """ |
383 |
391 |
# TODO: Require that only the course team can create assignments for a team. |
384 |
392 |
course = models.ForeignKey( |
385 |
393 |
"Course", |
386 |
394 |
on_delete=models.CASCADE, |
387 |
395 |
null=False, |
388 |
396 |
#editable=False, |
389 |
397 |
db_index=True, |
390 |
398 |
help_text=_("The course for which this task is assigned."), |
391 |
399 |
) |
392 |
400 |
title = models.CharField( |
393 |
401 |
max_length=32, |
394 |
402 |
blank=False, |
395 |
403 |
help_text=_("The title of this assignment."), |
396 |
404 |
) |
397 |
405 |
information = models.TextField( |
398 |
406 |
help_text=_("Any additional information regarding the assignment. Orgmode syntax available."), |
399 |
407 |
) |
400 |
408 |
deadline = models.DateTimeField( |
401 |
409 |
null=False, |
402 |
410 |
help_text=_("The date and time this task is due."), |
403 |
411 |
) |
404 |
412 |
posted = models.DateField(auto_now_add=True) |
405 |
413 |
digital_task = models.BooleanField( |
406 |
414 |
default=True, |
407 |
415 |
help_text=_("This determines whether this assignment requires handing " |
408 |
416 |
"in a digital file."), |
409 |
417 |
) |
410 |
418 |
|
411 |
419 |
def __str__(self): |
412 |
420 |
return str(self.course) +" | "+ str(self.posted) |
413 |
421 |
|
414 |
422 |
class Announcement(models.Model): |
415 |
423 |
""" Courses sometimes have to make announcements for the students. """ |
416 |
424 |
course = models.ForeignKey( |
417 |
425 |
"Course", |
418 |
426 |
on_delete=models.CASCADE, |
419 |
427 |
null=False, |
420 |
428 |
#editable=False, |
421 |
429 |
db_index=True, |
422 |
430 |
help_text=_("The course for which this announcement is made."), |
423 |
431 |
) |
424 |
432 |
title = models.CharField( |
425 |
433 |
max_length=20, # Keep It Short & Simple® |
426 |
434 |
help_text=_("A quick title for what this is about."), |
427 |
435 |
) |
428 |
436 |
text = models.TextField( |
429 |
437 |
blank=False, |
430 |
438 |
help_text=_("The announcement itself. Orgmode syntax available."), |
431 |
439 |
) |
432 |
440 |
posted = models.DateTimeField(auto_now_add=True) |
433 |
441 |
|
434 |
442 |
def __str__(self): |
435 |
443 |
return str(self.course) +" | "+ self.posted.strftime("%m/%d") |
436 |
444 |
|
437 |
445 |
class Upload(models.Model): |
438 |
446 |
""" For certain assignments, digital hand-ins may be required. These hand |
439 |
447 |
ins are recorded per student in this table. """ |
440 |
448 |
assignment = models.ForeignKey( |
441 |
449 |
"Assignment", |
442 |
450 |
on_delete=models.CASCADE, |
443 |
451 |
null=False, |
444 |
452 |
#editable=False, |
445 |
453 |
db_index=True, |
446 |
454 |
limit_choices_to={"digital_task": True}, |
447 |
455 |
help_text=_("For which assignment this upload is."), |
448 |
456 |
) |
449 |
457 |
# TODO: Try to find a way to require that, if the upload is made, |
450 |
458 |
# only students that have this course in their curriculum can upload. |
451 |
459 |
student = models.ForeignKey( |
452 |
460 |
"administration.User", |
453 |
461 |
on_delete=models.CASCADE, |
454 |
462 |
null=False, |
455 |
463 |
#editable=False, |
456 |
464 |
limit_choices_to={"is_student": True}, |
457 |
465 |
help_text=_("The student who handed this in."), |
458 |
466 |
) |
459 |
467 |
upload_time = models.DateTimeField(auto_now_add=True) |
460 |
468 |
comment = models.TextField( |
461 |
469 |
blank=True, |
462 |
470 |
help_text=_("If you wish to add an additional comment, state it here."), |
463 |
471 |
) |
464 |
472 |
file = models.FileField( |
465 |
473 |
upload_to="assignments/uploads/%Y/%m/", |
466 |
474 |
null=False, |
467 |
475 |
#editable=False, |
468 |
476 |
help_text=_("The file you want to upload for this assignment."), |
469 |
477 |
) |
470 |
478 |
|
471 |
479 |
|
472 |
480 |
def __str__(self): |
473 |
481 |
deadline = self.assignment.deadline |
474 |
482 |
if deadline < self.upload_time: |
475 |
483 |
return str(self.assignment.course) +" | "+ str(self.student.number) + _("(OVERDUE)") |
476 |
484 |
else: |
477 |
485 |
return str(self.assignment.course) +" | "+ str(self.student.number) |
478 |
486 |
|
479 |
487 |
def item_upload_directory(instance, filename): |
480 |
488 |
return "courses/" + instance.course.slug_name + "/" |
481 |
489 |
class CourseItem(models.Model): |
482 |
490 |
""" Reprensents study material for a course that is being shared by the |
483 |
491 |
course's education team. """ |
484 |
492 |
course = models.ForeignKey( |
485 |
493 |
Course, |
486 |
494 |
on_delete=models.CASCADE, |
487 |
495 |
null=False, |
488 |
496 |
#editable=False, |
489 |
497 |
) |
490 |
498 |
file = models.FileField( |
491 |
499 |
upload_to=item_upload_directory, |
492 |
500 |
null=False, |
493 |
501 |
#editable=False, |
494 |
502 |
help_text=_("The file you wish to upload."), |
495 |
503 |
) |
496 |
504 |
timestamp = models.DateTimeField(auto_now_add=True) |
497 |
505 |
note = models.TextField( |
498 |
506 |
blank=True, |
499 |
507 |
help_text=_("If you want to state some additional information about " |
500 |
508 |
"this upload, state it here."), |
501 |
509 |
) |
502 |
510 |
|
503 |
511 |
class StudyGroup(models.Model): |
504 |
512 |
""" It may be necessary to make study groups regarding a course. These |
505 |
513 |
are recorded here, and blend in seamlessly with the Groups from Agora. |
506 |
514 |
Groups that are recorded as a StudyGroup, are given official course status, |
507 |
515 |
and thus, cannot be removed until the status of StudyGroup is lifted. """ |
508 |
516 |
course = models.ForeignKey( |
509 |
517 |
"Course", |
510 |
518 |
on_delete=models.CASCADE, |
511 |
519 |
null=False, |
512 |
520 |
#editable=False, |
513 |
521 |
db_index=True, |
514 |
522 |
help_text=_("The course for which this group is."), |
515 |
523 |
) |
516 |
524 |
group = models.ForeignKey( |
517 |
525 |
"agora.Group", |
518 |
526 |
on_delete=models.PROTECT, # See class documentation |
519 |
527 |
null=False, |
520 |
528 |
#editable=False, # Keep the same group |
521 |
529 |
help_text=_("The group that will be seen as the study group."), |
522 |
530 |
) |
523 |
531 |
|
524 |
532 |
def __str__(self): |
525 |
533 |
return str(self.course) +" | "+ str(self.group) |
526 |
534 |
courses/urls.py ¶
0 additions and 3 deletions.
View changes Hide changes
1 |
1 |
from . import views |
2 |
2 |
from django.utils.translation import ugettext_lazy as _ |
3 |
3 |
|
4 |
4 |
urlpatterns = [] + i18n_patterns( |
5 |
5 |
path('index', views.main, name='courses-index'), |
6 |
6 |
path('<slug:course_slug>', views.course, name='courses-course-index'), |
7 |
7 |
path(_('<slug:course_slug>/announcements'), views.announcements, name='courses-announcements'), |
8 |
- | path(_('<slug:course_slug>/assignments'), views.assignments, name='courses-assignments'), |
9 |
- | path(_('<slug:course_slug>/course-material'), views.course_material, name='courses-material'), |
10 |
- | path(_('<slug:course_slug>/<int:assignment_id>/upload'), views.upload, name='courses-upload'), |
11 |
8 |
path(_('<slug:course_slug>/new-item'), views.new_item, name='courses-new-item'), |
12 |
9 |
path(_('<slug:course_slug>/groups'), views.groups, name='courses-groups'), |
13 |
10 |
) |
14 |
11 |
courses/views.py ¶
28 additions and 4 deletions.
View changes Hide changes
1 |
1 |
import datetime |
2 |
2 |
from django.core.urlresolvers import reverse # Why? |
3 |
3 |
from django.utils.translation import ugettext as _ |
4 |
- | from .models import * |
+ |
4 |
from .models import * |
5 |
5 |
import joeni.administration |
6 |
6 |
|
7 |
7 |
def current_academic_year(): |
8 |
8 |
""" Returns the current academic year. The year is determined as follows: |
9 |
9 |
- If today is before September 15 of the current year, the returned value |
10 |
10 |
is the current year - 1. |
11 |
11 |
- If today is after September 15 of the current year, but before January 1 |
12 |
12 |
of the next year, it returns the current year as is. |
13 |
13 |
""" |
14 |
14 |
today = datetime.datetime.now() |
15 |
15 |
switch = datetime.datetime.date(datetime.datetime.year, 9, 15) |
16 |
16 |
if today < switch: |
17 |
17 |
return today.year - 1 |
18 |
18 |
else: |
19 |
19 |
return today.year |
20 |
20 |
|
21 |
21 |
@login_required |
22 |
22 |
def index(request): |
23 |
23 |
""" Starting page regarding the courses. This serves two specific groups: |
24 |
24 |
- Students: Displays all courses that this student has in his/her curriculum |
25 |
25 |
for this academic year. Requires the curriculum to be accepted. |
26 |
26 |
- Staff: Displays all courses in which the staff member is part of the |
27 |
27 |
educating team, or is otherwise related to the course. |
28 |
28 |
Users who are not logged in will be sent to the login page. |
29 |
29 |
""" |
30 |
30 |
template = "courses/index.djhtml" |
31 |
31 |
courses = set() |
32 |
32 |
if request.user.is_student: |
33 |
33 |
curricula = administration.models.Curriculum.objects.filter(student=request.user) |
34 |
34 |
current_curriculum = curricula.filter(year__year=current_academic_year()) |
35 |
35 |
courses = current_curriculum.courses |
36 |
36 |
elif request.user.is_staff: |
37 |
37 |
courses += adminstration.models.Course.filter(contact_person=request.user) |
38 |
- | courses += adminstration.models.Course.filter(coordinator=request.user) |
39 |
- | courses += adminstration.models.Course.filter(educating_team__contains=request.user) |
40 |
- | else: |
+ |
38 |
else: |
41 |
39 |
raise django.exceptions.FieldError("User "+request.user.number+" is neither staff nor student") |
42 |
40 |
|
43 |
41 |
context = { |
44 |
42 |
'courses': courses, |
45 |
43 |
} |
46 |
44 |
|
47 |
45 |
return render(request, template, context) |
48 |
46 |
|
49 |
47 |
@login_required |
50 |
48 |
def course(request, course_slug): |
51 |
49 |
template = "courses/course.djhtml" |
52 |
50 |
course = Course.objects.get(slug_name=course_slug) |
53 |
51 |
|
54 |
52 |
# Check if user can see this page |
55 |
53 |
if request.user.is_student: |
56 |
54 |
curricula = administration.models.Curriculum.objects.filter(student=request.user) |
57 |
55 |
current_curriculum = curricula.filter(year__year=current_academic_year()) |
58 |
56 |
if course not in current_curriculum.courses: |
59 |
57 |
""" I'm currently just redirecting to the index page, but maybe it's |
60 |
58 |
just as good to make an announcement that this course cannot be |
61 |
59 |
used by this user. """ |
62 |
60 |
return index(request) |
63 |
61 |
|
64 |
62 |
|
65 |
63 |
|
66 |
64 |
context = { |
67 |
65 |
'course': course, |
68 |
66 |
'announcements': Announcement.objects.filter(course=course), |
69 |
67 |
'assignments': Assignment.objects.filter(course=course), |
70 |
68 |
'course-items': CourseItem.objects.filter(course=course), |
71 |
69 |
'study-groups': StudyGroup.objects.filter(course=course), |
72 |
70 |
'uploads': Upload.objects.filter(course=course).filter(student=request.user) |
73 |
71 |
} |
74 |
72 |
|
75 |
73 |
return render(request, template, context) |
76 |
74 |
|
+ |
75 |
@login_required |
+ |
76 |
def new_item(request, course_slug): |
+ |
77 |
template = "courses/new_item.djhtml" |
+ |
78 |
course = Course.objects.get(slug_name=course_slug) |
+ |
79 |
|
+ |
80 |
if request.user.is_student or request.user not in course.course_team: |
+ |
81 |
# Students can't add new items. Redirect to index |
+ |
82 |
# Also redirect people who are not part of the course team |
+ |
83 |
redirect('courses-index') |
+ |
84 |
# Now able to assume user is allowed to add items to this course |
+ |
85 |
|
+ |
86 |
context = { |
+ |
87 |
'course': course, |
+ |
88 |
'announcements': Announcement.objects.filter(course=course), |
+ |
89 |
'assignments': Assignment.objects.filter(course=course), |
+ |
90 |
'course-items': CourseItem.objects.filter(course=course), |
+ |
91 |
'study-groups': StudyGroup.objects.filter(course=course), |
+ |
92 |
'uploads': Upload.objects.filter(course=course) |
+ |
93 |
} |
+ |
94 |
|
+ |
95 |
return render(request, template, context) |
+ |
96 |
|
+ |
97 |
@login_required |
+ |
98 |
def remove(request, type, id): |
+ |
99 |
pass |
+ |
100 |