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.