joeni

Minor model changes and new templates

I've made some slight changes to the models, more specifically; a name change and a slight change in how the foreign keys work. Additionally, the first view has been made, and a couple of new templates have been added to the tracker. These are not complete yet, but they're already pretty functional for what they're supposed to do. The stylesheets are still in their infancy, because they're not very important. Currently, it's more important to make the templates convey a meaning on their own.

Author
Maarten 'Vngngdn' Vangeneugden
Date
Nov. 22, 2017, 9:45 p.m.
Hash
4c969f3a0dea77b4409e9cf91891ddf1c75181db
Parent
c35535e1d10591e4d7f7ddd9102e58355df3ae81
Modified files
administration/models.py
courses/models.py
courses/templates/courses/index.djhtml
courses/views.py
joeni/templates/joeni/base.djhtml
joeni/templates/joeni/footer.djhtml

administration/models.py

16 additions and 4 deletions.

View changes Hide changes
1
1
from django.core.exceptions import ValidationError
2
2
from django.core.validators import MaxValueValidator
3
3
from django.utils.translation import ugettext_lazy as _
4
4
from django.contrib.auth.models import AbstractUser
5
5
import datetime
6
6
import os
7
7
import uuid
8
8
9
9
def validate_IBAN(value):
10
10
    """ Validates if the given value qualifies as a valid IBAN number.
11
11
    This validator checks if the structure is valid, and calculates the control
12
12
    number if the structure is correct. If the control number fails, or the
13
13
    structure is invalid, a ValidationError will be raised. In that case,
14
14
    the Error will specify whether the structure is incorrect, or the control
15
15
    number is not valid.
16
16
    """
17
17
    # FIXME: This function is not complete. When there's time, implement
18
18
    # as specified at https://nl.wikipedia.org/wiki/International_Bank_Account_Number#Structuur
19
19
    if False:
20
20
        raise ValidationError(
21
21
            _('%(value)s is not a valid IBAN number.'),
22
22
            params={'value': value},)
23
23
def validate_BIC(value):
24
24
    """ Same functionality as validate_IBAN, but for BIC-codes. """
25
25
    # FIXME: This function is not complete. When there's time, implement
26
26
    # as specified at https://nl.wikipedia.org/wiki/Business_Identifier_Code
27
27
    pass
28
28
29
29
class User(AbstractUser):
30
30
    """ Replacement for the standard Django User model. """
31
31
    number = models.AutoField(
32
32
        primary_key=True,
33
33
        help_text=_("The number assigned to this user."),
34
34
        )
35
35
    created = models.DateField(auto_now_add=True)
36
36
    first_name = models.CharField(max_length=64, blank=False)
37
37
    last_name = models.CharField(max_length=64, blank=False)
38
38
    title = models.CharField(
39
39
        max_length=64,
40
40
        blank=True,
41
41
        help_text=_("The academic title of this user, if applicable."),
42
42
        )
43
43
    DOB = models.DateField(
44
44
        blank=False,
45
45
        #editable=False,
46
46
        help_text=_("The date of birth of this user."),
47
47
        )
48
48
    POB = models.CharField(
49
49
        max_length=64,
50
50
        blank=False,
51
51
        #editable=False,
52
52
        help_text=_("The place of birth of this user."),
53
53
        )
54
54
    nationality = models.CharField(
55
55
        max_length=64,
56
56
        blank=False,
57
57
        help_text=_("The current nationality of this user."),
58
58
        )
59
59
    # XXX: What if this starts with zeros?
60
60
    national_registry_number = models.BigIntegerField(
61
61
        unique=True,
62
62
        #editable=False,
63
63
        help_text=_("The assigned national registry number of this user."),
64
64
        )
65
65
    civil_status = models.CharField(
66
66
        max_length=32,
67
67
        choices = (
68
68
            ("Single", _("Single")),
69
69
            ("Married", _("Married")),
70
70
            ("Divorced", _("Divorced")),
71
71
            ("Widowed", _("Widowed")),
72
72
            ("Partnership", _("Partnership")),
73
73
            ),
74
74
        blank=False,
75
75
        # There may be more; consult http://www.aantrekkingskracht.com/trefwoord/burgerlijke-staat
76
76
        # for more information.
77
77
        help_text=_("The civil/marital status of the user."),
78
78
        )
79
79
80
80
    is_staff = models.BooleanField(
81
81
        default=False,
82
82
        help_text=_("Determines if this user is part of the university's staff."),
83
83
        )
84
84
    is_student = models.BooleanField(
85
85
        default=True,
86
86
        help_text=_("Indicates if this user is a student at the university."),
87
87
        )
88
88
89
89
    # Home address
90
90
    home_street = models.CharField(max_length=64, blank=False)
91
91
    home_number = models.PositiveSmallIntegerField(blank=False)
92
92
    home_bus = models.PositiveSmallIntegerField(null=True)
93
93
    home_postal_code = models.PositiveSmallIntegerField(blank=False)
94
94
    home_country = models.CharField(max_length=64, blank=False)
95
95
    home_telephone = models.CharField(
96
96
        max_length=64,
97
97
        help_text=_("The telephone number for the house address. Prefix 0 can be presented with the national call code in the system."),
98
98
        )
99
99
    # Study address
100
100
    study_street = models.CharField(max_length=64, blank=False)
101
101
    study_number = models.PositiveSmallIntegerField(blank=False)
102
102
    study_bus = models.PositiveSmallIntegerField(null=True)
103
103
    study_postal_code = models.PositiveSmallIntegerField(blank=False)
104
104
    study_country = models.CharField(max_length=64, blank=False)
105
105
    study_telephone = models.CharField(
106
106
        max_length=64,
107
107
        help_text=_("The telephone number for the study address. Prefix 0 can be presented with the national call code in the system."),
108
108
        )
109
109
    study_cellphone = models.CharField(
110
110
        max_length=64,
111
111
        help_text=_("The cellphone number of the person. Prefix 0 can be presented with then national call code in the system."),
112
112
        )
113
113
    # Titularis address
114
114
    # XXX: These fields are only required if this differs from the user itself.
115
115
    titularis_street = models.CharField(max_length=64, null=True)
116
116
    titularis_number = models.PositiveSmallIntegerField(null=True)
117
117
    titularis_bus = models.PositiveSmallIntegerField(null=True)
118
118
    titularis_postal_code = models.PositiveSmallIntegerField(null=True)
119
119
    titularis_country = models.CharField(max_length=64, null=True)
120
120
    titularis_telephone = models.CharField(
121
121
        max_length=64,
122
122
        help_text=_("The telephone number of the titularis. Prefix 0 can be presented with the national call code in the system."),
123
123
        null=True,
124
124
        )
125
125
126
126
    # Financial details
127
127
    bank_account_number = models.CharField(
128
128
        max_length=34,  # Max length of all IBAN account numbers
129
129
        validators=[validate_IBAN],
130
130
        help_text=_("The IBAN of this user. No spaces!"),
131
131
        )
132
132
    BIC = models.CharField(
133
133
        max_length=11,
134
134
        validators=[validate_BIC],
135
135
        help_text=_("The BIC of this user's bank."),
136
136
        )
137
137
138
138
""" NOTE: What about all the other features that should be in the administration?
139
139
While there are a lot of things to cover, as of now, I have no way to know which
140
140
ones are still valid, which are deprecated, and so on...
141
141
Additionally, every feature may have a different set of requirements, data,
142
142
and it's very likely making an abstract class won't do any good. Thus I have
143
143
decided to postpone making additional tables and forms for these features until
144
144
I have clearance about certain aspects. """
145
145
146
146
class Curriculum(models.Model):
147
147
    """ The curriculum of a particular student.
148
148
    Every academic year, a student has to hand in a curriculum (s)he wishes to
149
149
    follow. This is then reviewed by a committee. A curriculum exists of all the
150
150
    courses one wants to partake in in a certain year. """
151
151
    student = models.ForeignKey(
152
152
        "User",
153
153
        on_delete=models.CASCADE,
154
154
        limit_choices_to={'is_student': True},
155
155
        null=False,
156
156
        #editable=False,
157
157
        unique_for_year="year",  # Only 1 curriculum per year
158
158
        )
159
159
    year = models.DateField(
160
160
        auto_now_add=True,
161
161
        db_index=True,
162
162
        help_text=_("The academic year for which this curriculum is."),
163
-
        )
+
163
                    "If this field is equal to 2008, then that means "
+
164
                    "this curriculum is for the academic year "
+
165
                    "2008-2009."),
+
166
        )
164
167
    last_modified = models.DateTimeField(
165
168
        auto_now=True,
166
169
        help_text=_("The last timestamp that this was updated."),
167
170
        )
168
171
    courses = models.ManyToManyField(
169
-
        "courses.Course",
170
-
        null=False,
+
172
        "courses.CourseProgramme",
+
173
        null=False,
171
174
        help_text=_("All the courses included in this curriculum."),
172
-
        )
+
175
        )
173
176
    approved = models.NullBooleanField(
174
177
        default=None,
175
178
        help_text=_("Indicates if this curriculum has been approved. If true, "
176
179
                    "that means the responsible committee has reviewed and "
177
180
                    "approved the student for this curriculum. False otherwise. "
178
181
                    "If review is still pending, the value is NULL. Modifying "
179
182
                    "the curriculum implies this setting is set to NULL again."),
180
183
        )
181
184
    note = models.TextField(
182
185
        blank=True,
183
186
        help_text=_("Additional notes regarding this curriculum. This has "
184
187
                    "multiple uses. For the student, it is used to clarify "
185
188
                    "any questions, or to motivate why (s)he wants to take a "
186
189
                    "course for which the requirements were not met. "
187
190
                    "The reviewing committee can use this field to argument "
188
191
                    "their decision, especially for when the curriculum is "
189
192
                    "denied."),
190
193
        )
191
194
192
195
    def curriculum_type(self):
+
196
        """ Returns a set of all the courses that are in this curriculum.
+
197
        This is not the same as CourseProgrammes, as these can differ depending
+
198
        on which study one follows. """
+
199
        course_set = set()
+
200
        for course_programme in self.course_programmes:
+
201
            course_set.add(course_programme.course)
+
202
        return course_set
+
203
+
204
    def curriculum_type(self):
193
205
        """ Returns the type of this curriculum. At the moment, this is
194
206
        either a standard programme, or an individualized programme. """
195
207
        # Currently: A standard programme means: All courses are from the
196
208
        # same study, ánd from the same year. Additionally, all courses
197
209
        # from that year must've been taken.
198
210
        # FIXME: Need a way to determine what is the standard programme.
199
211
        # If not possible, make this a charfield with options or something
200
212
        pass
201
213
202
214
    def __str__(self):
203
215
        year = self.year.year
204
216
        if self.year.month < 7:
205
217
            return str(self.student) +" | "+ str(year-1) +"-"+ str(year)
206
218
        else:
207
219
            return str(self.student) +" | "+ str(year) +"-"+ str(year+1)
208
220
209
221
210
222
class CourseResult(models.Model):
211
223
    """ A student has to obtain a certain course result. These are stored here,
212
224
    together with all the appropriate information. """
213
225
    # TODO: Validate that a course programme for a student can only be made once per year for each course, if possible.
214
226
    CRED = _("Credit acquired")
215
227
    FAIL = _("Credit not acquired")
216
228
    TLRD = _("Tolerated")
217
229
    ITLD = _("Tolerance used")
218
230
    # Possible to add more in the future
219
231
220
232
    student = models.ForeignKey(
221
233
        "User",
222
234
        on_delete=models.CASCADE,
223
235
        limit_choices_to={'is_student': True},
224
236
        null=False,
225
237
        )
226
238
    course_programme = models.ForeignKey(
227
239
        "courses.ProgrammeInformation",
228
240
        on_delete=models.PROTECT,
229
241
        null=False,
230
242
        )
231
243
    released = models.DateField(
232
244
        auto_now=True,
233
245
        help_text=_("The date that this result was last updated."),
234
246
        )
235
247
    first_score = models.PositiveSmallIntegerField(
236
248
        null=True,  # It's possible a score does not exist.
237
249
        validators=[MaxValueValidator(
238
250
            20,
239
251
            _("The score mustn't be higher than 20."),
240
252
            )],
241
253
        )
242
254
    second_score = models.PositiveSmallIntegerField(
243
255
        null=True,
244
256
        validators=[MaxValueValidator(
245
257
            20,
246
258
            _("The score mustn't be higher than 20."),
247
259
            )],
248
260
        )
249
261
    result = models.CharField(
250
262
        max_length=10,
251
263
        choices = (
252
264
            ("CRED", CRED),
253
265
            ("FAIL", FAIL),
254
266
            ("TLRD", TLRD),
255
267
            ("ITLD", ITLD),
256
268
            ),
257
269
        blank=False,
258
270
        help_text=_("The final result this record constitutes."),
259
271
        )
260
272
261
273
    def __str__(self):
262
274
        stdnum = str(self.student.number)
263
275
        result = self.result
264
276
        if result == "CRED":
265
277
            if self.first_score < 10:
266
278
                result = "C" + self.first_score + "1"
267
279
            else:
268
280
                result = "C" + self.second_score + "2"
269
281
        course = str(self.course_programme.course)
270
282
        return stdnum +" ("+ result +") | "+ course
271
283
272
284
class PreRegistration(models.Model):
273
285
    """ At the beginning of the new academic year, students can register
274
286
    themselves at the university. Online, they can do a preregistration already.
275
287
    These records are stored here and can later be retrieved for the actual
276
288
    registration process.
277
289
    Note: The current system in use at Hasselt University provides a password system.
278
290
    That will be eliminated here. Just make sure that the entered details are correct.
279
291
    Should there be an error, and the same email address is used to update something,
280
292
    a mail will be sent to that address to verify this was a genuine update."""
281
293
    created = models.DateField(auto_now_add=True)
282
294
    first_name = models.CharField(max_length=64, blank=False, help_text=_("Your first name."))
283
295
    last_name = models.CharField(max_length=64, blank=False, help_text=_("Your last name."))
284
296
    additional_names = models.CharField(max_length=64, blank=True, help_text=_("Any additional names."))
285
297
    title = models.CharField(
286
298
        max_length=64,
287
299
        blank=True,
288
300
        help_text=_("Any additional titles, prefixes, ..."),
289
301
        )
290
302
    DOB = models.DateField(
291
303
        blank=False,
292
304
        #editable=False,
293
305
        help_text=_("Your date of birth."),
294
306
        )
295
307
    POB = models.CharField(
296
308
        max_length=64,
297
309
        blank=False,
298
310
        #editable=False,
299
311
        help_text=_("The place you were born."),
300
312
        )
301
313
    nationality = models.CharField(
302
314
        max_length=64,
303
315
        blank=False,
304
316
        help_text=_("Your current nationality."),
305
317
        )
306
318
    national_registry_number = models.BigIntegerField(
307
319
        null=True,
308
320
        help_text=_("If you have one, your national registry number."),
309
321
        )
310
322
    civil_status = models.CharField(
311
323
        max_length=32,
312
324
        choices = (
313
325
            ("Single", _("Single")),
314
326
            ("Married", _("Married")),
315
327
            ("Divorced", _("Divorced")),
316
328
            ("Widowed", _("Widowed")),
317
329
            ("Partnership", _("Partnership")),
318
330
            ),
319
331
        blank=False,
320
332
        # There may be more; consult http://www.aantrekkingskracht.com/trefwoord/burgerlijke-staat
321
333
        # for more information.
322
334
        help_text=_("Your civil/marital status."),
323
335
        )
324
336
    email = models.EmailField(
325
337
        blank=False,
326
338
        unique=True,
327
339
        help_text=_("The e-mail address we will use to communicate until your actual registration."),
328
340
        )
329
341
    study = models.ForeignKey(
330
342
        "courses.Study",
331
343
        on_delete=models.PROTECT,
332
344
        null=False,
333
345
        help_text=_("The study you wish to follow. Be sure to provide all legal"
334
346
                    "documents that are required for this study with this "
335
347
                    "application, or bring them with you to the final registration."),
336
348
        )
337
349
    study_type = models.CharField(
338
350
        max_length=32,
339
351
        choices = (
340
352
            ("Diplom contract", _("Diplom contract")),
341
353
            ("Exam contract", _("Exam contract")),
342
354
            ("Credit contract", _("Credit contract")),
343
355
            ),
344
356
        blank=False,
345
357
        help_text=_("The type of study contract you wish to follow."),
346
358
        )
347
359
    document = models.FileField(
348
360
        upload_to="pre-enrollment/%Y",
349
361
        help_text=_("Any legal documents regarding your enrollment."),
350
362
        )
351
363
    # XXX: If the database in production is PostgreSQL, comment document, and
352
364
    # uncomment the next column.
353
365
    """documents = models.ArrayField(
354
366
        models.FileField(upload_to="pre-enrollment/%Y"),
355
367
        help_text=_("Any legal documents regarding your enrollment."),
356
368
        )"""
357
369
358
370
    def __str__(self):
359
371
        name = self.last_name +" "+ self.first_name
360
372
        dob = self.DOB.strftime("%d/%m/%Y")
361
373
        return name +" | "+ dob
362
374
363
375
364
376
# Planning and organization related tables
365
377
class Room(models.Model):
366
378
    """ Represents a room in the university.
367
379
    Rooms can have a number of properties, which are stored in the database.
368
380
    """
369
381
    # Types of rooms
370
382
    LABORATORY = _("Laboratory")  # Chemistry/Physics equipped rooms
371
383
    CLASS_ROOM = _("Class room")  # Simple class rooms
372
384
    AUDITORIUM = _("Auditorium")  # Large rooms with ample seating and equipment for lectures
373
385
    PC_ROOM    = _("PC room"   )  # Rooms equipped for executing PC related tasks
374
386
    PUBLIC_ROOM= _("Public room") # Restaurants, restrooms, ... general public spaces
375
387
    OFFICE     = _("Office"    )  # Private offices for staff
376
388
    PRIVATE_ROOM = _("Private room")  # Rooms accessible for a limited public; cleaning cupboards, kitchens, ...
377
389
    WORKSHOP   = _("Workshop"  )  # Rooms with hardware equipment to build and work on materials
378
390
    OTHER      = _("Other"     )  # Rooms that do not fit in any other category
379
391
380
392
381
393
    name = models.CharField(
382
394
        max_length=20,
383
395
        primary_key=True,
384
396
        blank=False,
385
397
        help_text=_("The name of this room. If more appropriate, this can be the colloquial name."),
386
398
        )
387
399
    seats = models.PositiveSmallIntegerField(
388
400
        help_text=_("The amount of available seats in this room. This can be handy for exams for example."),
389
401
        )
390
402
    wheelchair_accessible = models.BooleanField(default=True)
391
403
    exams_equipped = models.BooleanField(
392
404
        default=True,
393
405
        help_text=_("Indicates if exams can reasonably be held in this room."),
394
406
        )
395
407
    computers_available = models.PositiveSmallIntegerField(
396
408
        default=False,
397
409
        help_text=_("Indicates how many computers are available in this room."),
398
410
        )
399
411
    projector_available = models.BooleanField(
400
412
        default=False,
401
413
        help_text=_("Indicates if a projector is available at this room."),
402
414
        )
403
415
    blackboards_available = models.PositiveSmallIntegerField(
404
416
        help_text=_("The amount of blackboards available in this room."),
405
417
        )
406
418
    whiteboards_available = models.PositiveSmallIntegerField(
407
419
        help_text=_("The amount of whiteboards available in this room."),
408
420
        )
409
421
    category = models.CharField(
410
422
        max_length=16,
411
423
        blank=False,
412
424
        choices = (
413
425
            ("LABORATORY", LABORATORY),
414
426
            ("CLASS_ROOM", CLASS_ROOM),
415
427
            ("AUDITORIUM", AUDITORIUM),
416
428
            ("PC_ROOM", PC_ROOM),
417
429
            ("PUBLIC_ROOM", PUBLIC_ROOM),
418
430
            ("OFFICE", OFFICE),
419
431
            ("PRIVATE_ROOM", PRIVATE_ROOM),
420
432
            ("WORKSHOP", WORKSHOP),
421
433
            ("OTHER", OTHER),
422
434
            ),
423
435
        help_text=_("The category that best suits the character of this room."),
424
436
        )
425
437
    reservable = models.BooleanField(
426
438
        default=True,
427
439
        help_text=_("Indicates if this room can be reserved for something."),
428
440
        )
429
441
    note = models.TextField(
430
442
        blank=True,
431
443
        help_text=_("If some additional info is required for this room, like a "
432
444
                    "characteristic property (e.g. 'Usually occupied by 2BACH "
433
445
                    "informatics'), state it here."),
434
446
        )
435
447
    # TODO: Add a campus/building field or not?
436
448
437
449
    def reservation_possible(self, begin, end, seats=None):
438
450
        """ Returns a boolean indicating if reservating during the given time
439
451
        is possible. If the begin overlaps with a reservation's end or vice versa,
440
452
        this is regarded as possible.
441
453
        Takes seats as optional argument. If not specified, it is assumed the entire
442
454
        room has to be reserved. """
443
455
        if self.reservable is False:
444
456
            return False
445
457
        if seats is not None and seats < 0: raise ValueError(_("seats ∈ ℕ"))
446
458
447
459
        reservations = RoomReservation.objects.filter(room=self)
448
460
        for reservation in reservations:
449
461
            if reservation.end <= begin or reservation.begin >= end:
450
462
                continue  # Can be trivially skipped, no overlap here
451
463
            elif seats is None or reservation.seats is None:
452
464
                return False  # The whole room cannot be reserved -> False
453
465
            elif seats + reservation.seats > self.seats:
454
466
                    return False  # Total amount of seats exceeds the available amount -> False
455
467
        return True  # No overlappings found -> True
456
468
457
469
    def __str__(self):
458
470
        return self.name
459
471
460
472
class RoomReservation(models.Model):
461
473
    """ Rooms are to be reserved from time to time. They can be reserved
462
474
    by externals, for something else, and whatnot. That is stored in this table.
463
475
    """
464
476
    room = models.ForeignKey(
465
477
        "Room",
466
478
        on_delete=models.CASCADE,
467
479
        null=False,
468
480
        #editable=False,
469
481
        db_index=True,
470
482
        limit_choices_to={"reservable": True},
471
483
        help_text=_("The room that is being reserved at this point."),
472
484
        )
473
485
    reservator = models.ForeignKey(
474
486
        "User",
475
487
        on_delete=models.CASCADE,
476
488
        null=False,
477
489
        #editable=False,
478
490
        help_text=_("The person that made the reservation (and thus responsible)."),
479
491
        )
480
492
    timestamp = models.DateTimeField(auto_now_add=True)
481
493
    start_time = models.DateTimeField(
482
494
        null=False,
483
495
        help_text=_("The time that this reservation starts."),
484
496
        )
485
497
    end_time = models.DateTimeField(
486
498
        null=False,
487
499
        help_text=_("The time that this reservation ends."),
488
500
        )
489
501
    seats = models.PositiveSmallIntegerField(
490
502
        null=True,
491
503
        help_text=_("Indicates how many seats are required. If this is left null, "
492
504
                    "it is assumed the entire room has to be reserved."),
493
505
        )
494
506
    reason = models.CharField(
495
507
        max_length=64,
496
508
        blank=True,
497
509
        help_text=_("The reason for this reservation, if useful."),
498
510
        )
499
511
    note = models.TextField(
500
512
        blank=True,
501
513
        help_text=_("If some additional info is required for this reservation, "
502
514
                    "state it here."),
503
515
        )
504
516
505
517
    def __str__(self):
506
518
        start = self.start_time.strftime("%H:%M")
507
519
        end = self.end_time.strftime("%H:%M")
508
520
        return str(self.room) +" | "+ start +"-"+ end
509
521
510
522
class Degree(models.Model):
511
523
    """ Contains all degrees that were achieved at this university.
512
524
    There are no foreign keys in this field. This allows system
513
525
    administrators to safely remove accounts from alumni, without
514
526
    the risk of breaking referential integrity or accidentally removing
515
527
    degrees.
516
528
    While keeping some fields editable that look like they shouldn't be
517
529
    (e.g. first_name), this makes it possible for alumni to have a name change
518
530
    later in their life, and still being able to get a copy of their degree. """
519
531
    """ Reason for an ID field for every degree:
520
532
    This system allows for employers to verify that a certain applicant has indeed,
521
533
    achieved the degrees (s)he proclaims to have. Because of privacy concerns,
522
534
    a university cannot disclose information about alumni.
523
535
    That's where the degree ID comes in. This ID can be printed on all future
524
536
    degrees. The employer can then visit the university's website, and simply
525
537
    enter the ID. The website will then simply print what study is attached to
526
538
    this degree, but not disclose names or anything identifiable. This strikes
527
539
    thé perfect balance between (easy and digital) degree verification for employers, and maintaining
528
540
    alumni privacy to the highest extent possible. """
529
541
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
530
542
    first_name = models.CharField(
531
543
        max_length=64,
532
544
        blank=False,
533
545
        )
534
546
    last_name = models.CharField(
535
547
        max_length=64,
536
548
        blank=False,
537
549
        )
538
550
    additional_names = models.CharField(
539
551
        max_length=64,
540
552
        blank=True,
541
553
        )
542
554
    DOB = models.DateField(null=False)#editable=False, null=False)  # This can't be changed, of course
543
555
    POB = models.CharField(
544
556
        max_length=64,
545
557
        blank=False,
546
558
        #editable=False,
547
559
        )
548
560
    # The study also has to be a charfield, because if a study is removed,
549
561
    # The information will be lost.
550
562
    study = models.CharField(
551
563
        max_length=64,
552
564
        blank=False,
553
565
        #editable=False,
554
566
        )
555
567
    achieved = models.DateField(null=False)#editable=False, null=False)
556
568
    user = models.ForeignKey(
557
569
        "User",
558
570
        on_delete=models.SET_NULL,
559
571
        null=True,
560
572
        help_text=_("The person that achieved this degree, if (s)he still has "
561
573
                    "an account at this university. If the account is deleted "
562
574
                    "at a later date, this field will be set to NULL, but the "
563
575
                    "other fields will be retained."),
564
576
        )
565
577
566
578
    def __str__(self):
567
579
        return self.first_name +" "+ self.last_name +" | "+ self.study
568
580

courses/models.py

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

courses/templates/courses/index.djhtml

24 additions and 0 deletions.

View changes Hide changes
+
1
{% load i18n %}
+
2
+
3
{% block title %}
+
4
    {% trans "Joeni | Courses" %}
+
5
{% endblock %}
+
6
+
7
{% block main %}
+
8
    

{% trans "Your courses" %}

+
9
    
    +
    10
            {% for course in courses %}
    +
    11
                
  • +
    12
                    
    +
    13
                        {{ course.name|lower|capfirst }}
    +
    14
                        {% if course.number < 10 %}(000{{ course.number }})
    +
    15
                        {% elif course.number < 100 %}(00{{ course.number }})
    +
    16
                        {% elif course.number < 1000 %}(0{{ course.number }})
    +
    17
                        {% else %}({{ course.number }})
    +
    18
                    
    +
    19
                
    +
    20
            {% endfor %}
    +
    21
        
    +
    22
    {% endblock main %}
    +
    23
    +
    24

    courses/views.py

    47 additions and 0 deletions.

    View changes Hide changes
    1
    1
    +
    2
    from django.core.urlresolvers import reverse # Why?
    +
    3
    from django.utils.translation import ugettext as _
    +
    4
    from .models import *
    +
    5
    import joeni.administration
    +
    6
    +
    7
    def current_academic_year():
    +
    8
        """ Returns the current academic year. The year is determined as follows:
    +
    9
        - If today is before September 15 of the current year, the returned value
    +
    10
          is the current year - 1.
    +
    11
        - If today is after September 15 of the current year, but before January 1
    +
    12
          of the next year, it returns the current year as is.
    +
    13
        """
    +
    14
        today = datetime.datetime.now()
    +
    15
        switch = datetime.datetime.date(datetime.datetime.year, 9, 15)
    +
    16
        if today < switch:
    +
    17
            return today.year - 1
    +
    18
        else:
    +
    19
            return today.year
    +
    20
    +
    21
    @login_required
    +
    22
    def index(request):
    +
    23
        """ Starting page regarding the courses. This serves two specific groups:
    +
    24
        - Students: Displays all courses that this student has in his/her curriculum
    +
    25
                    for this academic year. Requires the curriculum to be accepted.
    +
    26
        - Staff: Displays all courses in which the staff member is part of the
    +
    27
                 educating team, or is otherwise related to the course.
    +
    28
        Users who are not logged in will be sent to the login page.
    +
    29
        """
    +
    30
        template = "courses/index.djhtml"
    +
    31
        courses = set()
    +
    32
        if request.user.is_student:
    +
    33
            curricula = administration.models.Curriculum.objects.filter(student=request.user)
    +
    34
            current_curriculum = curricula.filter(year__year=current_academic_year())
    +
    35
            courses = current_curriculum.courses
    +
    36
        elif request.user.is_staff:
    +
    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:
    +
    41
            raise django.exceptions.FieldError("User "+request.user.number+" is neither staff nor student")
    +
    42
    +
    43
        context = {
    +
    44
            'courses': courses,
    +
    45
            }
    +
    46
    +
    47
        return render(request, template, context)
    +
    48

    joeni/templates/joeni/base.djhtml

    72 additions and 0 deletions.

    View changes Hide changes
    +
    1
    {% load i18n %}
    +
    2
    {% get_current_language as LANGUAGE_CODE %}
    +
    3
    {% get_language_info for LANGUAGE_CODE as lang %}
    +
    4
    {% load static %}
    +
    5
    {% get_media_prefix as media %}
    +
    6
    +
    7
    +
    8
    +
    9
        
    +
    10
            </pre></td>
            </tr>
            
            
            <tr class="addition">
                <td class="line-number symbol">+</td>
                <td id="joeni/templates/joeni/base.djhtml-B11" class="line-number">
                    <a href="#joeni/templates/joeni/base.djhtml-B11" class="accent-on-hover-only"><pre>11</pre>
                </a></td>
            
                <td style="padding-left: 1em;"><pre>            {% block title %}</pre></td>
            </tr>
            
            
            <tr class="addition">
                <td class="line-number symbol">+</td>
                <td id="joeni/templates/joeni/base.djhtml-B12" class="line-number">
                    <a href="#joeni/templates/joeni/base.djhtml-B12" class="accent-on-hover-only"><pre>12</pre>
                </a></td>
            
                <td style="padding-left: 1em;"><pre>                ◀ Joeni /▶ | ▶▶ UHasselt</pre></td>
            </tr>
            
            
            <tr class="addition">
                <td class="line-number symbol">+</td>
                <td id="joeni/templates/joeni/base.djhtml-B13" class="line-number">
                    <a href="#joeni/templates/joeni/base.djhtml-B13" class="accent-on-hover-only"><pre>13</pre>
                </a></td>
            
                <td style="padding-left: 1em;"><pre>            {% endblock title %}</pre></td>
            </tr>
            
            
            <tr class="addition">
                <td class="line-number symbol">+</td>
                <td id="joeni/templates/joeni/base.djhtml-B14" class="line-number">
                    <a href="#joeni/templates/joeni/base.djhtml-B14" class="accent-on-hover-only"><pre>14</pre>
                </a></td>
            
                <td style="padding-left: 1em;"><pre>        
    +
    15
    +
    16
            {% block stylesheets %}
    +
    17
                
    +
    18
                
    +
    23
            {% endblock stylesheets %}
    +
    24
    +
    25
            {% block metaflags %}
    +
    26
                {# This is standard for all web pages and doesn't require changing. #}
    +
    27
                {# UTF-8, always #}
    +
    28
                
    +
    29
                {##}
    +
    30
                {# Indicates this page is suited for mobile devices #}
    +
    31
                
    +
    32
                
    +
    33
                    name="description"
    +
    34
                    content="{% block description %}
    +
    35
                        {% trans "The digital platform of Hasselt University" %}
    +
    36
                    {% endblock description %}" />
    +
    37
            {% endblock metaflags %}
    +
    38
        
    +
    39
    +
    40
        
    +
    41
            
    +
    42
                    {% block header %}
    +
    43
                        {% include "joeni/navbar.djhtml" %}
    +
    44
                    {% endblock header %}
    +
    45
            
    +
    46
    +
    47
            
    +
    48
                {% block main %}
    +
    49
                {% endblock main %}
    +
    50
            
    +
    51
    +
    52
            
    +
    53
                {% block footer %}
    +
    54
                    {% include "joeni/footer.djhtml" %}
    +
    55
                {% endblock footer %}
    +
    56
            
    +
    57
    +
    58
        
    +
    59
        {% block JavaScript %}
    +
    60
        {% comment JavaScript %}
    +
    61
        My website does not require JavaScript for basic actions. However, it may be
    +
    62
        used to make certain things look better, or to spice up some actions for the
    +
    63
        user. Should it be necessary, add the appropriate JavaScript code in this
    +
    64
        block.
    +
    65
        The reason this block is at the bottom?
    +
    66
    	Remember to always put JavaScript on the bottom to reduce load time.
    +
    67
        Otherwise it just errors like a fucktard, which is really the only thing
    +
    68
        you can always expect from JavaScript.
    +
    69
        {% endcomment %}
    +
    70
        {% endblock JavaScript %}
    +
    71
    +
    72

    joeni/templates/joeni/footer.djhtml

    26 additions and 0 deletions.

    View changes Hide changes
    +
    1
    {% load static %}
    +
    2
    {% get_media_prefix as media %}
    +
    3
    ◀ Joeni /▶
    +
    4
    ▶▶ UHasselt
    +
    5
    {% blocktrans %}
    +
    6
        

    +
    7
            Joeni is the digital platform of Hasselt University, designed with an
    +
    8
            emphasis on the day to day needs of its students and staff.
    +
    9
        

    +
    10
    {% endblocktrans %}
    +
    11
    +
    12
      +
      13
          
    • +
      14
              Agora
      +
      15
          
      +
      16
          
    • +
      17
              {% trans "Administration" %}
      +
      18
          
      +
      19
          
    • +
      20
              {% trans "Courses" %}
      +
      21
          
      +
      22
      +
      23
      +
      24
      +
      25
      +
      26