joeni

Small changes to administration app

Added a couple lines of code to the administration's view, and changed some that were due to change eventually. Edit of models: Added a year to course results, to ease sorting on year while displaying. Added the standard form template for almost all forms.

Author
Maarten 'Vngngdn' Vangeneugden
Date
Jan. 28, 2018, 10:37 p.m.
Hash
b8e9de04855e787d25bdd347f29c3217a170a0bc
Parent
4a6a29f734af5d3f0f96746f84605f38b321acc9
Modified files
administration/models.py
administration/views.py
joeni/templates/joeni/form.djhtml

administration/models.py

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

administration/views.py

16 additions and 7 deletions.

View changes Hide changes
1
1
import datetime
2
2
from django.urls import reverse # Why?
3
3
from django.utils.translation import gettext as _
4
4
from .models import *
5
5
from .forms import UserDataForm
6
6
import administration
7
7
from django.contrib.auth.decorators import login_required
8
8
9
9
@login_required
10
10
def roster(request, begin=None, end=None):
11
11
    """Collects and renders the data that has to be displayed in the roster.
12
12
13
13
    The begin and end date can be specified. Only roster points in that range
14
14
    will be included in the response. If no begin and end are specified, it will
15
15
    take the current week as begin and end point. If it's
16
16
    weekend, it will take next week."""
17
17
    if begin is None or end is None:
+
18
    template = "administration/roster.djhtml"
+
19
+
20
    if begin is None or end is None:
18
21
        today = datetime.date.today()
19
22
        if today.isoweekday() in {6,7}:  # Weekend
20
23
            begin = today + datetime.timedelta(days=8-today.isoweekday())
21
24
            end = today + datetime.timedelta(days=13-today.isoweekday())
22
25
        else:  # Same week
23
26
            begin = today - datetime.timedelta(days=today.weekday())
24
27
            end = today + datetime.timedelta(days=5-today.isoweekday())
25
28
26
29
        return roster
27
-
    # TODO Finish!
+
30
    return render(request, template, context)
+
31
    # TODO Finish!
28
32
29
33
def index(request):
30
34
    template = "administration/index.djhtml"
31
35
    context = {}
32
36
    return render(request, template, context)
33
37
34
38
    pass
35
39
36
40
def pre_registration(request):
37
41
    user_data_form = UserDataForm()
38
42
    template = "administration/pre_registration.djhtml"
39
43
    context = dict()
40
44
41
45
    if request.method == 'POST':
42
46
        user_data_form = UserDataForm(request.POST)
43
47
        context['user_data_form'] = user_data_form
44
48
        if user_data_form.is_valid():
45
49
            user_data_form.save()
46
50
            context['messsage'] = _("Your registration has been completed. You will receive an e-mail shortly.")
47
51
        else:
48
52
            context['messsage'] = _("The data you supplied had errors. Please review your submission.")
49
53
    else:
50
54
        context['user_data_form'] = UserDataForm(instance = user_data)
51
55
52
56
    return render(request, template, context)
53
57
    pass
54
58
55
59
@login_required
56
60
def settings(request):
57
61
    user_data = UserData.objects.get(user=request.user)
58
62
    user_data_form = UserDataForm(instance = user_data)
59
63
    template = "administration/settings.djhtml"
60
64
    context = dict()
61
65
62
66
    if request.method == 'POST':
63
67
        user_data_form = UserDataForm(request.POST, instance = user_data)
64
68
        context['user_data_form'] = user_data_form
65
69
        if user_data_form.is_valid():
66
70
            user_data_form.save()
67
71
            context['messsage'] = _("Your settings were successfully updated.")
68
72
        else:
69
73
            context['messsage'] = _("The data you supplied had errors. Please review your submission.")
70
74
    else:
71
75
        context['user_data_form'] = UserDataForm(instance = user_data)
72
76
73
77
    return render(request, template, context)
74
78
75
79
@login_required
76
80
def exam_commission_decisions(request):
77
81
    context = dict()
78
82
    context['decisions'] = ExamCommissionDecision.objects.filter(user=request.user)
79
83
    template = "administration/exam_commission.djhtml"
80
84
    return render(request, template, context)
81
85
82
86
83
87
def curriculum(request):
84
88
    pass
85
-
+
89
86
90
def result(request):
87
91
    pass
88
-
+
92
89
93
def results(request):
+
94
def results(request):
90
95
    pass
91
-
+
96
    template = "administration/results.djhtml"
+
97
    # TODO
+
98
    return render(request, template, context)
+
99
92
100
def forms(request):
93
101
    pass
94
-
+
102
95
103
def rooms(request):
96
104
    pass
97
-
+
105
    return render(request, template, context)
+
106
98
107
def room_reservate(request):
99
108
    pass
100
-
+
109
101
110
def login(request):
102
111
    if request.method == "POST":
103
112
        name = request.POST['name']
104
113
        passphrase = request.POST['password']
105
114
        user = authenticate(username=name, password=passphrase)
106
115
        if user is not None: # The user was successfully authenticated.
107
116
            login(request, user)
108
117
            # Because of Leen, I now track when and where is logged in:
109
118
            loginRecord = Login()
110
119
            loginRecord.ip = request.META['REMOTE_ADDR']
111
120
            loginRecord.name = name
112
121
            loginRecord.save()
113
122
            return HttpResponseRedirect(reverse('ITdays-index'))
114
123
115
124
    template = 'administration/login.djhtml'
116
125
117
126
    footer_links = [
118
127
            ["Home", "/"],
119
128
            ["Contact", "mailto:maarten.vangeneugden@student.uhasselt.be"],
120
129
            ]
121
130
122
131
    context = {
123
132
            'materialDesign_color': "deep-purple",
124
133
            'materialDesign_accentColor': "amber",
125
134
            'navbar_title': "Authentication",
126
135
            'navbar_fixed': True,
127
136
            'navbar_backArrow': True,
128
137
            'footer_title': "Quotebook",
129
138
            'footer_description': "Een lijst van citaten uit 2BACH Informatica @ UHasselt.",
130
139
            'footer_links': footer_links,
131
140
            }
132
141
    return render(request, template, context)
133
142

joeni/templates/joeni/form.djhtml

10 additions and 0 deletions.

View changes Hide changes
+
1
{% for hidden in form.hidden_fields %}
+
2
{{ hidden }}
+
3
{% endfor %}
+
4
{% for field in form.visible_fields %}
+
5
    <div class="form-field">
+
6
        {{ field.errors }}
+
7
        {{ field.label_tag }} {{ field }}
+
8
    </div>
+
9
{% endfor %}
+
10