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