Add new migrations and implement roster models
The database system should be done by now. I've added a couple of new model classes so that work can begin on a roster system. I've also added a new Group class to the courses app, because certain studies may have different groups. This was told to me by the professor.
Additionally, Some old migrations have been added that I forgot to implement.
- Author
- Maarten 'Vngngdn' Vangeneugden
- Date
- Jan. 24, 2018, 1:15 a.m.
- Hash
- 389f9833d742c0fd04de07f7f999cccb62e5dda4
- Parent
- 8e88f95f28f20568e78174ef284f1ed22ff5c11c
- Modified files
- administration/migrations/0008_auto_20180124_0049.py
- administration/migrations/0009_auto_20180124_0049.py
- administration/models.py
- administration/views.py
- courses/migrations/0003_auto_20180124_0049.py
- courses/migrations/0004_auto_20180124_0049.py
- courses/models.py
administration/migrations/0008_auto_20180124_0049.py ¶
28 additions and 0 deletions.
View changes Hide changes
+ |
1 |
|
+ |
2 |
from django.db import migrations, models |
+ |
3 |
|
+ |
4 |
|
+ |
5 |
class Migration(migrations.Migration): |
+ |
6 |
|
+ |
7 |
dependencies = [ |
+ |
8 |
('administration', '0007_auto_20171121_2018'), |
+ |
9 |
] |
+ |
10 |
|
+ |
11 |
operations = [ |
+ |
12 |
migrations.CreateModel( |
+ |
13 |
name='Event', |
+ |
14 |
fields=[ |
+ |
15 |
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
+ |
16 |
('begin_time', models.DateTimeField(help_text='The begin date and time that this event takes place.', verbose_name='begin time')), |
+ |
17 |
('end_time', models.DateTimeField(help_text='The end date and time that this event takes place.', verbose_name='end time')), |
+ |
18 |
('note', models.TextField(blank=True, help_text='Optional. If necessary, this field allows for additional information that can be shown to the people for whom this event is.')), |
+ |
19 |
('created', models.DateTimeField(auto_now_add=True)), |
+ |
20 |
('last_update', models.DateTimeField(auto_now=True)), |
+ |
21 |
], |
+ |
22 |
), |
+ |
23 |
migrations.RemoveField( |
+ |
24 |
model_name='curriculum', |
+ |
25 |
name='courses', |
+ |
26 |
), |
+ |
27 |
] |
+ |
28 |
administration/migrations/0009_auto_20180124_0049.py ¶
57 additions and 0 deletions.
View changes Hide changes
+ |
1 |
|
+ |
2 |
from django.conf import settings |
+ |
3 |
from django.db import migrations, models |
+ |
4 |
import django.db.models.deletion |
+ |
5 |
|
+ |
6 |
|
+ |
7 |
class Migration(migrations.Migration): |
+ |
8 |
|
+ |
9 |
dependencies = [ |
+ |
10 |
('courses', '0003_auto_20180124_0049'), |
+ |
11 |
('administration', '0008_auto_20180124_0049'), |
+ |
12 |
] |
+ |
13 |
|
+ |
14 |
operations = [ |
+ |
15 |
migrations.AddField( |
+ |
16 |
model_name='curriculum', |
+ |
17 |
name='course_programmes', |
+ |
18 |
field=models.ManyToManyField(help_text='All the course programmes included in this curriculum.', to='courses.CourseProgramme'), |
+ |
19 |
), |
+ |
20 |
migrations.AlterField( |
+ |
21 |
model_name='courseresult', |
+ |
22 |
name='course_programme', |
+ |
23 |
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='courses.CourseProgramme'), |
+ |
24 |
), |
+ |
25 |
migrations.AlterField( |
+ |
26 |
model_name='curriculum', |
+ |
27 |
name='year', |
+ |
28 |
field=models.DateField(auto_now_add=True, db_index=True, help_text='The academic year for which this curriculum is. If this field is equal to 2008, then that means this curriculum is for the academic year 2008-2009.'), |
+ |
29 |
), |
+ |
30 |
migrations.CreateModel( |
+ |
31 |
name='CourseEvent', |
+ |
32 |
fields=[ |
+ |
33 |
('event_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='administration.Event')), |
+ |
34 |
('subject', models.CharField(help_text="The subject of this event. Examples are 'Hoorcollege', 'Zelfstudie', ...", max_length=32)), |
+ |
35 |
('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='courses.CourseProgramme')), |
+ |
36 |
('docent', models.ForeignKey(help_text='The person who will be the main overseer of this event.', limit_choices_to={'is_staff': True}, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), |
+ |
37 |
('group', models.ForeignKey(help_text="Some courses have multiple groups. If that's the case, and this event is only for a specific group, then that group must be referenced here.", null=True, on_delete=django.db.models.deletion.CASCADE, to='courses.Group')), |
+ |
38 |
('room', models.ForeignKey(help_text='The room in which this event will be held.', on_delete=django.db.models.deletion.PROTECT, to='administration.Room')), |
+ |
39 |
], |
+ |
40 |
bases=('administration.event',), |
+ |
41 |
), |
+ |
42 |
migrations.CreateModel( |
+ |
43 |
name='StudyEvent', |
+ |
44 |
fields=[ |
+ |
45 |
('event_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='administration.Event')), |
+ |
46 |
], |
+ |
47 |
bases=('administration.event',), |
+ |
48 |
), |
+ |
49 |
migrations.CreateModel( |
+ |
50 |
name='UniversityEvent', |
+ |
51 |
fields=[ |
+ |
52 |
('event_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='administration.Event')), |
+ |
53 |
], |
+ |
54 |
bases=('administration.event',), |
+ |
55 |
), |
+ |
56 |
] |
+ |
57 |
administration/models.py ¶
77 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 |
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 |
164 |
"this curriculum is for the academic year " |
165 |
165 |
"2008-2009."), |
166 |
166 |
) |
167 |
167 |
last_modified = models.DateTimeField( |
168 |
168 |
auto_now=True, |
169 |
169 |
help_text=_("The last timestamp that this was updated."), |
170 |
170 |
) |
171 |
171 |
course_programmes = models.ManyToManyField( |
172 |
172 |
"courses.CourseProgramme", |
173 |
173 |
null=False, |
174 |
174 |
help_text=_("All the course programmes included in this curriculum."), |
175 |
175 |
) |
176 |
176 |
approved = models.NullBooleanField( |
177 |
177 |
default=None, |
178 |
178 |
help_text=_("Indicates if this curriculum has been approved. If true, " |
179 |
179 |
"that means the responsible committee has reviewed and " |
180 |
180 |
"approved the student for this curriculum. False otherwise. " |
181 |
181 |
"If review is still pending, the value is NULL. Modifying " |
182 |
182 |
"the curriculum implies this setting is set to NULL again."), |
183 |
183 |
) |
184 |
184 |
note = models.TextField( |
185 |
185 |
blank=True, |
186 |
186 |
help_text=_("Additional notes regarding this curriculum. This has " |
187 |
187 |
"multiple uses. For the student, it is used to clarify " |
188 |
188 |
"any questions, or to motivate why (s)he wants to take a " |
189 |
189 |
"course for which the requirements were not met. " |
190 |
190 |
"The reviewing committee can use this field to argument " |
191 |
191 |
"their decision, especially for when the curriculum is " |
192 |
192 |
"denied."), |
193 |
193 |
) |
194 |
194 |
|
195 |
195 |
def courses(self): |
196 |
196 |
""" Returns a set of all the courses that are in this curriculum. |
197 |
197 |
This is not the same as CourseProgrammes, as these can differ depending |
198 |
198 |
on which study one follows. """ |
199 |
199 |
course_set = set() |
200 |
200 |
for course_programme in self.course_programmes: |
201 |
201 |
course_set.add(course_programme.course) |
202 |
202 |
return course_set |
203 |
203 |
|
204 |
204 |
def curriculum_type(self): |
205 |
205 |
""" Returns the type of this curriculum. At the moment, this is |
206 |
206 |
either a standard programme, or an individualized programme. """ |
207 |
207 |
# Currently: A standard programme means: All courses are from the |
208 |
208 |
# same study, ánd from the same year. Additionally, all courses |
209 |
209 |
# from that year must've been taken. |
210 |
210 |
# FIXME: Need a way to determine what is the standard programme. |
211 |
211 |
# If not possible, make this a charfield with options or something |
212 |
212 |
pass |
213 |
213 |
|
214 |
214 |
def __str__(self): |
215 |
215 |
year = self.year.year |
216 |
216 |
if self.year.month < 7: |
217 |
217 |
return str(self.student) +" | "+ str(year-1) +"-"+ str(year) |
218 |
218 |
else: |
219 |
219 |
return str(self.student) +" | "+ str(year) +"-"+ str(year+1) |
220 |
220 |
|
221 |
221 |
|
222 |
222 |
class CourseResult(models.Model): |
223 |
223 |
""" A student has to obtain a certain course result. These are stored here, |
224 |
224 |
together with all the appropriate information. """ |
225 |
225 |
# TODO: Validate that a course programme for a student can only be made once per year for each course, if possible. |
226 |
226 |
CRED = _("Credit acquired") |
227 |
227 |
FAIL = _("Credit not acquired") |
228 |
228 |
TLRD = _("Tolerated") |
229 |
229 |
ITLD = _("Tolerance used") |
230 |
230 |
# Possible to add more in the future |
231 |
231 |
|
232 |
232 |
student = models.ForeignKey( |
233 |
233 |
"User", |
234 |
234 |
on_delete=models.CASCADE, |
235 |
235 |
limit_choices_to={'is_student': True}, |
236 |
236 |
null=False, |
237 |
237 |
) |
238 |
238 |
course_programme = models.ForeignKey( |
239 |
239 |
"courses.CourseProgramme", |
240 |
240 |
on_delete=models.PROTECT, |
241 |
241 |
null=False, |
242 |
242 |
) |
243 |
243 |
released = models.DateField( |
244 |
244 |
auto_now=True, |
245 |
245 |
help_text=_("The date that this result was last updated."), |
246 |
246 |
) |
247 |
247 |
first_score = models.PositiveSmallIntegerField( |
248 |
248 |
null=True, # It's possible a score does not exist. |
249 |
249 |
validators=[MaxValueValidator( |
250 |
250 |
20, |
251 |
251 |
_("The score mustn't be higher than 20."), |
252 |
252 |
)], |
253 |
253 |
) |
254 |
254 |
second_score = models.PositiveSmallIntegerField( |
255 |
255 |
null=True, |
256 |
256 |
validators=[MaxValueValidator( |
257 |
257 |
20, |
258 |
258 |
_("The score mustn't be higher than 20."), |
259 |
259 |
)], |
260 |
260 |
) |
261 |
261 |
result = models.CharField( |
262 |
262 |
max_length=10, |
263 |
263 |
choices = ( |
264 |
264 |
("CRED", CRED), |
265 |
265 |
("FAIL", FAIL), |
266 |
266 |
("TLRD", TLRD), |
267 |
267 |
("ITLD", ITLD), |
268 |
268 |
), |
269 |
269 |
blank=False, |
270 |
270 |
help_text=_("The final result this record constitutes."), |
271 |
271 |
) |
272 |
272 |
|
273 |
273 |
def __str__(self): |
274 |
274 |
stdnum = str(self.student.number) |
275 |
275 |
result = self.result |
276 |
276 |
if result == "CRED": |
277 |
277 |
if self.first_score < 10: |
278 |
278 |
result = "C" + self.first_score + "1" |
279 |
279 |
else: |
280 |
280 |
result = "C" + self.second_score + "2" |
281 |
281 |
course = str(self.course_programme.course) |
282 |
282 |
return stdnum +" ("+ result +") | "+ course |
283 |
283 |
|
284 |
284 |
class PreRegistration(models.Model): |
285 |
285 |
""" At the beginning of the new academic year, students can register |
286 |
286 |
themselves at the university. Online, they can do a preregistration already. |
287 |
287 |
These records are stored here and can later be retrieved for the actual |
288 |
288 |
registration process. |
289 |
289 |
Note: The current system in use at Hasselt University provides a password system. |
290 |
290 |
That will be eliminated here. Just make sure that the entered details are correct. |
291 |
291 |
Should there be an error, and the same email address is used to update something, |
292 |
292 |
a mail will be sent to that address to verify this was a genuine update.""" |
293 |
293 |
created = models.DateField(auto_now_add=True) |
294 |
294 |
first_name = models.CharField(max_length=64, blank=False, help_text=_("Your first name.")) |
295 |
295 |
last_name = models.CharField(max_length=64, blank=False, help_text=_("Your last name.")) |
296 |
296 |
additional_names = models.CharField(max_length=64, blank=True, help_text=_("Any additional names.")) |
297 |
297 |
title = models.CharField( |
298 |
298 |
max_length=64, |
299 |
299 |
blank=True, |
300 |
300 |
help_text=_("Any additional titles, prefixes, ..."), |
301 |
301 |
) |
302 |
302 |
DOB = models.DateField( |
303 |
303 |
blank=False, |
304 |
304 |
#editable=False, |
305 |
305 |
help_text=_("Your date of birth."), |
306 |
306 |
) |
307 |
307 |
POB = models.CharField( |
308 |
308 |
max_length=64, |
309 |
309 |
blank=False, |
310 |
310 |
#editable=False, |
311 |
311 |
help_text=_("The place you were born."), |
312 |
312 |
) |
313 |
313 |
nationality = models.CharField( |
314 |
314 |
max_length=64, |
315 |
315 |
blank=False, |
316 |
316 |
help_text=_("Your current nationality."), |
317 |
317 |
) |
318 |
318 |
national_registry_number = models.BigIntegerField( |
319 |
319 |
null=True, |
320 |
320 |
help_text=_("If you have one, your national registry number."), |
321 |
321 |
) |
322 |
322 |
civil_status = models.CharField( |
323 |
323 |
max_length=32, |
324 |
324 |
choices = ( |
325 |
325 |
("Single", _("Single")), |
326 |
326 |
("Married", _("Married")), |
327 |
327 |
("Divorced", _("Divorced")), |
328 |
328 |
("Widowed", _("Widowed")), |
329 |
329 |
("Partnership", _("Partnership")), |
330 |
330 |
), |
331 |
331 |
blank=False, |
332 |
332 |
# There may be more; consult http://www.aantrekkingskracht.com/trefwoord/burgerlijke-staat |
333 |
333 |
# for more information. |
334 |
334 |
help_text=_("Your civil/marital status."), |
335 |
335 |
) |
336 |
336 |
email = models.EmailField( |
337 |
337 |
blank=False, |
338 |
338 |
unique=True, |
339 |
339 |
help_text=_("The e-mail address we will use to communicate until your actual registration."), |
340 |
340 |
) |
341 |
341 |
study = models.ForeignKey( |
342 |
342 |
"courses.Study", |
343 |
343 |
on_delete=models.PROTECT, |
344 |
344 |
null=False, |
345 |
345 |
help_text=_("The study you wish to follow. Be sure to provide all legal" |
346 |
346 |
"documents that are required for this study with this " |
347 |
347 |
"application, or bring them with you to the final registration."), |
348 |
348 |
) |
349 |
349 |
study_type = models.CharField( |
350 |
350 |
max_length=32, |
351 |
351 |
choices = ( |
352 |
352 |
("Diplom contract", _("Diplom contract")), |
353 |
353 |
("Exam contract", _("Exam contract")), |
354 |
354 |
("Credit contract", _("Credit contract")), |
355 |
355 |
), |
356 |
356 |
blank=False, |
357 |
357 |
help_text=_("The type of study contract you wish to follow."), |
358 |
358 |
) |
359 |
359 |
document = models.FileField( |
360 |
360 |
upload_to="pre-enrollment/%Y", |
361 |
361 |
help_text=_("Any legal documents regarding your enrollment."), |
362 |
362 |
) |
363 |
363 |
# XXX: If the database in production is PostgreSQL, comment document, and |
364 |
364 |
# uncomment the next column. |
365 |
365 |
"""documents = models.ArrayField( |
366 |
366 |
models.FileField(upload_to="pre-enrollment/%Y"), |
367 |
367 |
help_text=_("Any legal documents regarding your enrollment."), |
368 |
368 |
)""" |
369 |
369 |
|
370 |
370 |
def __str__(self): |
371 |
371 |
name = self.last_name +" "+ self.first_name |
372 |
372 |
dob = self.DOB.strftime("%d/%m/%Y") |
373 |
373 |
return name +" | "+ dob |
374 |
374 |
|
375 |
375 |
|
376 |
376 |
# Planning and organization related tables |
377 |
377 |
class Room(models.Model): |
378 |
378 |
""" Represents a room in the university. |
379 |
379 |
Rooms can have a number of properties, which are stored in the database. |
380 |
380 |
""" |
381 |
381 |
# Types of rooms |
382 |
382 |
LABORATORY = _("Laboratory") # Chemistry/Physics equipped rooms |
383 |
383 |
CLASS_ROOM = _("Class room") # Simple class rooms |
384 |
384 |
AUDITORIUM = _("Auditorium") # Large rooms with ample seating and equipment for lectures |
385 |
385 |
PC_ROOM = _("PC room" ) # Rooms equipped for executing PC related tasks |
386 |
386 |
PUBLIC_ROOM= _("Public room") # Restaurants, restrooms, ... general public spaces |
387 |
387 |
OFFICE = _("Office" ) # Private offices for staff |
388 |
388 |
PRIVATE_ROOM = _("Private room") # Rooms accessible for a limited public; cleaning cupboards, kitchens, ... |
389 |
389 |
WORKSHOP = _("Workshop" ) # Rooms with hardware equipment to build and work on materials |
390 |
390 |
OTHER = _("Other" ) # Rooms that do not fit in any other category |
391 |
391 |
|
392 |
392 |
|
393 |
393 |
name = models.CharField( |
394 |
394 |
max_length=20, |
395 |
395 |
primary_key=True, |
396 |
396 |
blank=False, |
397 |
397 |
help_text=_("The name of this room. If more appropriate, this can be the colloquial name."), |
398 |
398 |
) |
399 |
399 |
seats = models.PositiveSmallIntegerField( |
400 |
400 |
help_text=_("The amount of available seats in this room. This can be handy for exams for example."), |
401 |
401 |
) |
402 |
402 |
wheelchair_accessible = models.BooleanField(default=True) |
403 |
403 |
exams_equipped = models.BooleanField( |
404 |
404 |
default=True, |
405 |
405 |
help_text=_("Indicates if exams can reasonably be held in this room."), |
406 |
406 |
) |
407 |
407 |
computers_available = models.PositiveSmallIntegerField( |
408 |
408 |
default=False, |
409 |
409 |
help_text=_("Indicates how many computers are available in this room."), |
410 |
410 |
) |
411 |
411 |
projector_available = models.BooleanField( |
412 |
412 |
default=False, |
413 |
413 |
help_text=_("Indicates if a projector is available at this room."), |
414 |
414 |
) |
415 |
415 |
blackboards_available = models.PositiveSmallIntegerField( |
416 |
416 |
help_text=_("The amount of blackboards available in this room."), |
417 |
417 |
) |
418 |
418 |
whiteboards_available = models.PositiveSmallIntegerField( |
419 |
419 |
help_text=_("The amount of whiteboards available in this room."), |
420 |
420 |
) |
421 |
421 |
category = models.CharField( |
422 |
422 |
max_length=16, |
423 |
423 |
blank=False, |
424 |
424 |
choices = ( |
425 |
425 |
("LABORATORY", LABORATORY), |
426 |
426 |
("CLASS_ROOM", CLASS_ROOM), |
427 |
427 |
("AUDITORIUM", AUDITORIUM), |
428 |
428 |
("PC_ROOM", PC_ROOM), |
429 |
429 |
("PUBLIC_ROOM", PUBLIC_ROOM), |
430 |
430 |
("OFFICE", OFFICE), |
431 |
431 |
("PRIVATE_ROOM", PRIVATE_ROOM), |
432 |
432 |
("WORKSHOP", WORKSHOP), |
433 |
433 |
("OTHER", OTHER), |
434 |
434 |
), |
435 |
435 |
help_text=_("The category that best suits the character of this room."), |
436 |
436 |
) |
437 |
437 |
reservable = models.BooleanField( |
438 |
438 |
default=True, |
439 |
439 |
help_text=_("Indicates if this room can be reserved for something."), |
440 |
440 |
) |
441 |
441 |
note = models.TextField( |
442 |
442 |
blank=True, |
443 |
443 |
help_text=_("If some additional info is required for this room, like a " |
444 |
444 |
"characteristic property (e.g. 'Usually occupied by 2BACH " |
445 |
445 |
"informatics'), state it here."), |
446 |
446 |
) |
447 |
447 |
# TODO: Add a campus/building field or not? |
448 |
448 |
|
449 |
449 |
def reservation_possible(self, begin, end, seats=None): |
450 |
450 |
""" Returns a boolean indicating if reservating during the given time |
451 |
451 |
is possible. If the begin overlaps with a reservation's end or vice versa, |
452 |
452 |
this is regarded as possible. |
453 |
453 |
Takes seats as optional argument. If not specified, it is assumed the entire |
454 |
454 |
room has to be reserved. """ |
455 |
455 |
if self.reservable is False: |
456 |
456 |
return False |
457 |
457 |
if seats is not None and seats < 0: raise ValueError(_("seats ∈ ℕ")) |
458 |
458 |
|
459 |
459 |
reservations = RoomReservation.objects.filter(room=self) |
460 |
460 |
for reservation in reservations: |
461 |
461 |
if reservation.end <= begin or reservation.begin >= end: |
462 |
462 |
continue # Can be trivially skipped, no overlap here |
463 |
463 |
elif seats is None or reservation.seats is None: |
464 |
464 |
return False # The whole room cannot be reserved -> False |
465 |
465 |
elif seats + reservation.seats > self.seats: |
466 |
466 |
return False # Total amount of seats exceeds the available amount -> False |
467 |
467 |
return True # No overlappings found -> True |
468 |
468 |
|
469 |
469 |
def __str__(self): |
470 |
470 |
return self.name |
471 |
471 |
|
472 |
472 |
class RoomReservation(models.Model): |
473 |
473 |
""" Rooms are to be reserved from time to time. They can be reserved |
474 |
474 |
by externals, for something else, and whatnot. That is stored in this table. |
475 |
475 |
""" |
476 |
476 |
room = models.ForeignKey( |
477 |
477 |
"Room", |
478 |
478 |
on_delete=models.CASCADE, |
479 |
479 |
null=False, |
480 |
480 |
#editable=False, |
481 |
481 |
db_index=True, |
482 |
482 |
limit_choices_to={"reservable": True}, |
483 |
483 |
help_text=_("The room that is being reserved at this point."), |
484 |
484 |
) |
485 |
485 |
reservator = models.ForeignKey( |
486 |
486 |
"User", |
487 |
487 |
on_delete=models.CASCADE, |
488 |
488 |
null=False, |
489 |
489 |
#editable=False, |
490 |
490 |
help_text=_("The person that made the reservation (and thus responsible)."), |
491 |
491 |
) |
492 |
492 |
timestamp = models.DateTimeField(auto_now_add=True) |
493 |
493 |
start_time = models.DateTimeField( |
494 |
494 |
null=False, |
495 |
495 |
help_text=_("The time that this reservation starts."), |
496 |
496 |
) |
497 |
497 |
end_time = models.DateTimeField( |
498 |
498 |
null=False, |
499 |
499 |
help_text=_("The time that this reservation ends."), |
500 |
500 |
) |
501 |
501 |
seats = models.PositiveSmallIntegerField( |
502 |
502 |
null=True, |
503 |
503 |
help_text=_("Indicates how many seats are required. If this is left null, " |
504 |
504 |
"it is assumed the entire room has to be reserved."), |
505 |
505 |
) |
506 |
506 |
reason = models.CharField( |
507 |
507 |
max_length=64, |
508 |
508 |
blank=True, |
509 |
509 |
help_text=_("The reason for this reservation, if useful."), |
510 |
510 |
) |
511 |
511 |
note = models.TextField( |
512 |
512 |
blank=True, |
513 |
513 |
help_text=_("If some additional info is required for this reservation, " |
514 |
514 |
"state it here."), |
515 |
515 |
) |
516 |
516 |
|
517 |
517 |
def __str__(self): |
518 |
518 |
start = self.start_time.strftime("%H:%M") |
519 |
519 |
end = self.end_time.strftime("%H:%M") |
520 |
520 |
return str(self.room) +" | "+ start +"-"+ end |
521 |
521 |
|
522 |
522 |
class Degree(models.Model): |
523 |
523 |
""" Contains all degrees that were achieved at this university. |
524 |
524 |
There are no foreign keys in this field. This allows system |
525 |
525 |
administrators to safely remove accounts from alumni, without |
526 |
526 |
the risk of breaking referential integrity or accidentally removing |
527 |
527 |
degrees. |
528 |
528 |
While keeping some fields editable that look like they shouldn't be |
529 |
529 |
(e.g. first_name), this makes it possible for alumni to have a name change |
530 |
530 |
later in their life, and still being able to get a copy of their degree. """ |
531 |
531 |
""" Reason for an ID field for every degree: |
532 |
532 |
This system allows for employers to verify that a certain applicant has indeed, |
533 |
533 |
achieved the degrees (s)he proclaims to have. Because of privacy concerns, |
534 |
534 |
a university cannot disclose information about alumni. |
535 |
535 |
That's where the degree ID comes in. This ID can be printed on all future |
536 |
536 |
degrees. The employer can then visit the university's website, and simply |
537 |
537 |
enter the ID. The website will then simply print what study is attached to |
538 |
538 |
this degree, but not disclose names or anything identifiable. This strikes |
539 |
539 |
thé perfect balance between (easy and digital) degree verification for employers, and maintaining |
540 |
540 |
alumni privacy to the highest extent possible. """ |
541 |
541 |
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) |
542 |
542 |
first_name = models.CharField( |
543 |
543 |
max_length=64, |
544 |
544 |
blank=False, |
545 |
545 |
) |
546 |
546 |
last_name = models.CharField( |
547 |
547 |
max_length=64, |
548 |
548 |
blank=False, |
549 |
549 |
) |
550 |
550 |
additional_names = models.CharField( |
551 |
551 |
max_length=64, |
552 |
552 |
blank=True, |
553 |
553 |
) |
554 |
554 |
DOB = models.DateField(null=False)#editable=False, null=False) # This can't be changed, of course |
555 |
555 |
POB = models.CharField( |
556 |
556 |
max_length=64, |
557 |
557 |
blank=False, |
558 |
558 |
#editable=False, |
559 |
559 |
) |
560 |
560 |
# The study also has to be a charfield, because if a study is removed, |
561 |
561 |
# The information will be lost. |
562 |
562 |
study = models.CharField( |
563 |
563 |
max_length=64, |
564 |
564 |
blank=False, |
565 |
565 |
#editable=False, |
566 |
566 |
) |
567 |
567 |
achieved = models.DateField(null=False)#editable=False, null=False) |
568 |
568 |
user = models.ForeignKey( |
569 |
569 |
"User", |
570 |
570 |
on_delete=models.SET_NULL, |
571 |
571 |
null=True, |
572 |
572 |
help_text=_("The person that achieved this degree, if (s)he still has " |
573 |
573 |
"an account at this university. If the account is deleted " |
574 |
574 |
"at a later date, this field will be set to NULL, but the " |
575 |
575 |
"other fields will be retained."), |
576 |
576 |
) |
577 |
577 |
|
578 |
578 |
def __str__(self): |
579 |
579 |
return self.first_name +" "+ self.last_name +" | "+ self.study |
580 |
580 |
|
+ |
581 |
|
+ |
582 |
# Classes regarding roster items |
+ |
583 |
|
+ |
584 |
class Event(models.Model): |
+ |
585 |
"""An event that will show up in the roster of accounts that need to be |
+ |
586 |
aware of this event. This can be a multitude of things, like colleges |
+ |
587 |
for certain courses, meetings like blood donations, and so on. There are |
+ |
588 |
specialized classes for certain types of events that take place.""" |
+ |
589 |
begin_time = models.DateTimeField( |
+ |
590 |
null=False, |
+ |
591 |
help_text=_("The begin date and time that this event takes place."), |
+ |
592 |
verbose_name=_("begin time"), |
+ |
593 |
) |
+ |
594 |
end_time = models.DateTimeField( |
+ |
595 |
null=False, |
+ |
596 |
help_text=_("The end date and time that this event takes place."), |
+ |
597 |
verbose_name=_("end time"), |
+ |
598 |
) |
+ |
599 |
note = models.TextField( |
+ |
600 |
blank=True, |
+ |
601 |
help_text=_("Optional. If necessary, this field allows for additional " |
+ |
602 |
"information that can be shown to the people for whom this " |
+ |
603 |
"event is."), |
+ |
604 |
) |
+ |
605 |
created = models.DateTimeField( |
+ |
606 |
auto_now_add=True, |
+ |
607 |
) |
+ |
608 |
last_update = models.DateTimeField( |
+ |
609 |
auto_now=True, |
+ |
610 |
) |
+ |
611 |
|
+ |
612 |
class CourseEvent(Event): |
+ |
613 |
"""An event related to a particular course. This includes a location, |
+ |
614 |
a group (if applicable), and other data.""" |
+ |
615 |
course = models.ForeignKey( |
+ |
616 |
"courses.CourseProgramme", |
+ |
617 |
on_delete=models.CASCADE, |
+ |
618 |
null=False, |
+ |
619 |
) |
+ |
620 |
docent = models.ForeignKey( |
+ |
621 |
"User", |
+ |
622 |
on_delete=models.PROTECT, |
+ |
623 |
null=False, |
+ |
624 |
limit_choices_to={'is_staff': True}, |
+ |
625 |
help_text=_("The person who will be the main overseer of this event."), |
+ |
626 |
) |
+ |
627 |
room = models.ForeignKey( |
+ |
628 |
"Room", |
+ |
629 |
on_delete=models.PROTECT, |
+ |
630 |
null=False, |
+ |
631 |
help_text=_("The room in which this event will be held."), |
+ |
632 |
) |
+ |
633 |
subject = models.CharField( |
+ |
634 |
max_length=32, |
+ |
635 |
blank=False, |
+ |
636 |
help_text=_("The subject of this event. Examples are 'Hoorcollege', " |
+ |
637 |
"'Zelfstudie', ..."), |
+ |
638 |
) |
+ |
639 |
group = models.ForeignKey( |
+ |
640 |
"courses.Group", |
+ |
641 |
on_delete = models.CASCADE, |
+ |
642 |
null=True, |
+ |
643 |
help_text=_("Some courses have multiple groups. If that's the case, " |
+ |
644 |
"and this event is only for a specific group, then that " |
+ |
645 |
"group must be referenced here."), |
+ |
646 |
) |
+ |
647 |
|
+ |
648 |
class UniversityEvent(Event): |
+ |
649 |
"""University wide events. These include events like blood donations for the |
+ |
650 |
Red Cross, for example.""" |
+ |
651 |
pass |
+ |
652 |
|
+ |
653 |
class StudyEvent(Event): |
+ |
654 |
"""An event that is linked to a particular study, like lectures from guest |
+ |
655 |
speakers about a certain subject.""" |
+ |
656 |
pass |
+ |
657 |
administration/views.py ¶
26 additions and 1 deletion.
View changes Hide changes
1 |
1 |
|
+ |
2 |
from django.urls import reverse # Why? |
+ |
3 |
from django.utils.translation import gettext as _ |
+ |
4 |
from .models import * |
+ |
5 |
import administration |
+ |
6 |
from django.contrib.auth.decorators import login_required |
+ |
7 |
|
2 |
8 |
# Create your views here. |
3 |
- | |
+ |
9 |
def roster(begin=None, end=None): |
+ |
10 |
"""Collects and renders the data that has to be displayed in the roster. |
+ |
11 |
|
+ |
12 |
The begin and end date can be specified. Only roster points in that range |
+ |
13 |
will be included in the response. If no begin and end are specified, it will |
+ |
14 |
take the current week as begin and end point. If it's |
+ |
15 |
weekend, it will take next week.""" |
+ |
16 |
if begin is None or end is None: |
+ |
17 |
today = date.today() |
+ |
18 |
if today.isoweekday() in {6,7}: # Weekend |
+ |
19 |
begin = today + date.timedelta(days=8-today.isoweekday()) |
+ |
20 |
end = today + date.timedelta(days=13-today.isoweekday()) |
+ |
21 |
else: # Same week |
+ |
22 |
begin = today - date.timedelta(days=today.weekday()) |
+ |
23 |
end = today + date.timedelta(days=5-today.isoweekday()) |
+ |
24 |
|
+ |
25 |
|
+ |
26 |
|
+ |
27 |
return roster |
+ |
28 |
courses/migrations/0003_auto_20180124_0049.py ¶
87 additions and 0 deletions.
View changes Hide changes
+ |
1 |
|
+ |
2 |
import courses.models |
+ |
3 |
from django.conf import settings |
+ |
4 |
from django.db import migrations, models |
+ |
5 |
|
+ |
6 |
|
+ |
7 |
class Migration(migrations.Migration): |
+ |
8 |
|
+ |
9 |
dependencies = [ |
+ |
10 |
('courses', '0002_auto_20171121_2018'), |
+ |
11 |
] |
+ |
12 |
|
+ |
13 |
operations = [ |
+ |
14 |
migrations.CreateModel( |
+ |
15 |
name='CourseItem', |
+ |
16 |
fields=[ |
+ |
17 |
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
+ |
18 |
('file', models.FileField(help_text='The file you wish to upload.', upload_to=courses.models.item_upload_directory)), |
+ |
19 |
('timestamp', models.DateTimeField(auto_now_add=True)), |
+ |
20 |
('note', models.TextField(blank=True, help_text='If you want to state some additional information about this upload, state it here.')), |
+ |
21 |
], |
+ |
22 |
), |
+ |
23 |
migrations.CreateModel( |
+ |
24 |
name='CourseProgramme', |
+ |
25 |
fields=[ |
+ |
26 |
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
+ |
27 |
('programme_type', models.CharField(choices=[('C', 'Compulsory'), ('O', 'Optional')], help_text='Type of this course for this study.', max_length=1)), |
+ |
28 |
('study_hours', models.PositiveSmallIntegerField(help_text='The required amount of hours to study this course.')), |
+ |
29 |
('ECTS', models.PositiveSmallIntegerField(help_text='The amount of ECTS points attached to this course.')), |
+ |
30 |
('semester', models.PositiveSmallIntegerField(choices=[(1, 'First semester'), (2, 'Second semester'), (3, 'Full year course'), (4, 'Taught in first quarter'), (5, 'Taught in second quarter'), (6, 'Taught in third quarter'), (7, 'Taught in fourth quarter')], help_text='The period in which this course is being taught in this study.')), |
+ |
31 |
('year', models.PositiveSmallIntegerField(help_text='The year in which this course is taught for this study.')), |
+ |
32 |
('second_chance', models.BooleanField(default=True, help_text='Defines if a second chance exam is planned for this course.')), |
+ |
33 |
('tolerable', models.BooleanField(default=True, help_text='Defines if a failed result can be tolerated.')), |
+ |
34 |
('scoring', models.CharField(choices=[('N', 'Numerical'), ('FP', 'Fail/Pass')], default='N', help_text='How the obtained score for this course is given.', max_length=2)), |
+ |
35 |
], |
+ |
36 |
), |
+ |
37 |
migrations.CreateModel( |
+ |
38 |
name='Group', |
+ |
39 |
fields=[ |
+ |
40 |
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
+ |
41 |
], |
+ |
42 |
), |
+ |
43 |
migrations.RenameModel( |
+ |
44 |
old_name='Prerequisites', |
+ |
45 |
new_name='Prerequisite', |
+ |
46 |
), |
+ |
47 |
migrations.RemoveField( |
+ |
48 |
model_name='programmeinformation', |
+ |
49 |
name='course', |
+ |
50 |
), |
+ |
51 |
migrations.RemoveField( |
+ |
52 |
model_name='programmeinformation', |
+ |
53 |
name='study', |
+ |
54 |
), |
+ |
55 |
migrations.RemoveField( |
+ |
56 |
model_name='programmeinformation', |
+ |
57 |
name='study_programme', |
+ |
58 |
), |
+ |
59 |
migrations.AddField( |
+ |
60 |
model_name='assignment', |
+ |
61 |
name='title', |
+ |
62 |
field=models.CharField(default='null', help_text='The title of this assignment.', max_length=32), |
+ |
63 |
preserve_default=False, |
+ |
64 |
), |
+ |
65 |
migrations.AddField( |
+ |
66 |
model_name='course', |
+ |
67 |
name='color', |
+ |
68 |
field=models.CharField(default='E73B2B', help_text='The color for this course. Must be an hexadecimal code.', max_length=6), |
+ |
69 |
), |
+ |
70 |
migrations.AddField( |
+ |
71 |
model_name='course', |
+ |
72 |
name='slug_name', |
+ |
73 |
field=models.SlugField(allow_unicode=True, default='empty_slug', help_text="A so-called 'slug name' for this course.", unique=True), |
+ |
74 |
preserve_default=False, |
+ |
75 |
), |
+ |
76 |
migrations.AlterField( |
+ |
77 |
model_name='course', |
+ |
78 |
name='educating_team', |
+ |
79 |
field=models.ManyToManyField(help_text='The remaining team members of this course.', limit_choices_to={'is_staff': True}, related_name='educating_team', to=settings.AUTH_USER_MODEL), |
+ |
80 |
), |
+ |
81 |
migrations.AlterField( |
+ |
82 |
model_name='study', |
+ |
83 |
name='degree_type', |
+ |
84 |
field=models.CharField(choices=[('BSc', 'Bachelor of Science'), ('MSc', 'Master of Science'), ('LL.B', 'Bachelor of Laws'), ('LL.M', 'Master of Laws'), ('ir.', 'Engineer'), ('ing.', 'Technological Engineer'), ('BA', 'Bachelor of Arts'), ('MA', 'Master of Arts')], help_text='The type of degree one obtains upon passing this study.', max_length=64), |
+ |
85 |
), |
+ |
86 |
] |
+ |
87 |
courses/migrations/0004_auto_20180124_0049.py ¶
43 additions and 0 deletions.
View changes Hide changes
+ |
1 |
|
+ |
2 |
from django.db import migrations, models |
+ |
3 |
import django.db.models.deletion |
+ |
4 |
|
+ |
5 |
|
+ |
6 |
class Migration(migrations.Migration): |
+ |
7 |
|
+ |
8 |
dependencies = [ |
+ |
9 |
('courses', '0003_auto_20180124_0049'), |
+ |
10 |
('administration', '0009_auto_20180124_0049'), |
+ |
11 |
] |
+ |
12 |
|
+ |
13 |
operations = [ |
+ |
14 |
migrations.DeleteModel( |
+ |
15 |
name='ProgrammeInformation', |
+ |
16 |
), |
+ |
17 |
migrations.AddField( |
+ |
18 |
model_name='group', |
+ |
19 |
name='study', |
+ |
20 |
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='courses.Study'), |
+ |
21 |
), |
+ |
22 |
migrations.AddField( |
+ |
23 |
model_name='courseprogramme', |
+ |
24 |
name='course', |
+ |
25 |
field=models.ForeignKey(help_text='The course that this programme is for.', on_delete=django.db.models.deletion.CASCADE, to='courses.Course'), |
+ |
26 |
), |
+ |
27 |
migrations.AddField( |
+ |
28 |
model_name='courseprogramme', |
+ |
29 |
name='study', |
+ |
30 |
field=models.ForeignKey(help_text='The study in which the course is taught.', on_delete=django.db.models.deletion.CASCADE, to='courses.Study'), |
+ |
31 |
), |
+ |
32 |
migrations.AddField( |
+ |
33 |
model_name='courseprogramme', |
+ |
34 |
name='study_programme', |
+ |
35 |
field=models.ForeignKey(help_text='The study programme that this course belongs to.', on_delete=django.db.models.deletion.CASCADE, to='courses.StudyProgramme'), |
+ |
36 |
), |
+ |
37 |
migrations.AddField( |
+ |
38 |
model_name='courseitem', |
+ |
39 |
name='course', |
+ |
40 |
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='courses.Course'), |
+ |
41 |
), |
+ |
42 |
] |
+ |
43 |
courses/models.py ¶
16 additions and 0 deletions.
View changes Hide changes
1 |
1 |
from joeni import constants |
2 |
2 |
from django.utils.translation import ugettext_lazy as _ |
3 |
3 |
|
4 |
4 |
def validate_hex_color(value): |
5 |
5 |
pass |
6 |
6 |
|
7 |
7 |
class Course(models.Model): |
8 |
8 |
""" Represents a course that is taught at the university. """ |
9 |
9 |
number = models.PositiveSmallIntegerField( |
10 |
10 |
primary_key=True, |
11 |
11 |
blank=False, |
12 |
12 |
help_text=_("The number associated with this course. A leading '0' will be added if the number is smaller than 1000."), |
13 |
13 |
) |
14 |
14 |
name = models.CharField( |
15 |
15 |
max_length=64, |
16 |
16 |
blank=False, |
17 |
17 |
help_text=_("The name of this course, in the language that it is taught. Translations are for the appropriate template."), |
18 |
18 |
) |
19 |
19 |
color = models.CharField( |
20 |
20 |
max_length=6, |
21 |
21 |
blank=False, |
22 |
22 |
default=constants.COLORS['UHasselt default'], |
23 |
23 |
help_text=_("The color for this course. Must be an hexadecimal code."), |
24 |
24 |
#validators=['validate_hex_color'], |
25 |
25 |
) |
26 |
26 |
slug_name = models.SlugField( |
27 |
27 |
blank=False, |
28 |
28 |
allow_unicode=True, |
29 |
29 |
unique=True, |
30 |
30 |
help_text=_("A so-called 'slug name' for this course."), |
31 |
31 |
) |
32 |
32 |
# TODO: Add a potential thingy magicky to auto fill the slug name on the course name |
33 |
33 |
contact_person = models.ForeignKey( |
34 |
34 |
"administration.User", |
35 |
35 |
on_delete=models.PROTECT, # A course must have a contact person |
36 |
36 |
limit_choices_to={'is_staff': True}, |
37 |
37 |
null=False, |
38 |
38 |
help_text=_("The person to contact regarding this course."), |
39 |
39 |
related_name="contact_person", |
40 |
40 |
) |
41 |
41 |
coordinator = models.ForeignKey( |
42 |
42 |
"administration.User", |
43 |
43 |
on_delete=models.PROTECT, # A course must have a coordinator |
44 |
44 |
limit_choices_to={'is_staff': True}, |
45 |
45 |
null=False, |
46 |
46 |
help_text=_("The person whom's the coordinator of this course."), |
47 |
47 |
related_name="coordinator", |
48 |
48 |
) |
49 |
49 |
educating_team = models.ManyToManyField( |
50 |
50 |
"administration.User", |
51 |
51 |
# No on_delete, since M->M cannot be required at database level |
52 |
52 |
limit_choices_to={'is_staff': True}, |
53 |
53 |
#null=False, # Useless on a M->M |
54 |
54 |
help_text=_("The remaining team members of this course."), |
55 |
55 |
related_name="educating_team", |
56 |
56 |
) |
57 |
57 |
language = models.CharField( |
58 |
58 |
max_length=64, |
59 |
59 |
choices = ( |
60 |
60 |
('NL', _("Dutch")), |
61 |
61 |
('EN', _("English")), |
62 |
62 |
('FR', _("French")), |
63 |
63 |
), |
64 |
64 |
null=False, |
65 |
65 |
help_text=_("The language in which this course is given."), |
66 |
66 |
) |
67 |
67 |
|
68 |
68 |
def course_team(self): |
69 |
69 |
""" Returns a set of all Users that are part of the team of this course. """ |
70 |
70 |
return set( |
71 |
71 |
self.contact_person, |
72 |
72 |
self.coordinator, |
73 |
73 |
self.educating_team, |
74 |
74 |
) |
75 |
75 |
|
76 |
76 |
def __str__(self): |
77 |
77 |
number = str(self.number) |
78 |
78 |
for i in [10,100,1000]: |
79 |
79 |
if self.number < i: |
80 |
80 |
number = "0" + number |
81 |
81 |
return "(" + number + ") " + self.name |
82 |
82 |
|
83 |
83 |
|
84 |
84 |
class Prerequisite(models.Model): |
85 |
85 |
""" Represents a collection of prerequisites a student must have obtained |
86 |
86 |
before being allowed to partake in this course. |
87 |
87 |
It's possible that, if a student has obtained credits in a certain set of |
88 |
88 |
courses, a certain part of the prerequisites do not have to be obtained. |
89 |
89 |
Because of this, make a different record for each different set. In other |
90 |
90 |
words: If one set of prerequisites is obtained, and another one isn't, BUT |
91 |
91 |
they point to the same course, the student is allowed to partake. """ |
92 |
92 |
course = models.ForeignKey( |
93 |
93 |
"Course", |
94 |
94 |
on_delete=models.CASCADE, |
95 |
95 |
null=False, |
96 |
96 |
help_text=_("The course that these prerequisites are for."), |
97 |
97 |
related_name="prerequisite_course", |
98 |
98 |
) |
99 |
99 |
name = models.CharField( |
100 |
100 |
max_length=64, |
101 |
101 |
blank=True, |
102 |
102 |
help_text=_("To specify a name for this set, if necessary."), |
103 |
103 |
) |
104 |
104 |
sequentialities = models.ManyToManyField( |
105 |
105 |
"Course", |
106 |
106 |
help_text=_("All courses for which a credit must've been received in order to follow the course."), |
107 |
107 |
related_name="sequentialities", |
108 |
108 |
) |
109 |
109 |
in_curriculum = models.ManyToManyField( |
110 |
110 |
"Course", |
111 |
111 |
help_text=_("All courses that have to be in the curriculum to follow this. If a credit was achieved, that course can be omitted."), |
112 |
112 |
related_name="in_curriculum", |
113 |
113 |
) |
114 |
114 |
required_study = models.ForeignKey( |
115 |
115 |
"Study", |
116 |
116 |
on_delete=models.CASCADE, |
117 |
117 |
null=True, |
118 |
118 |
help_text=_("If one must have a certain amount of obtained ECTS points for a particular course, state that course here."), |
119 |
119 |
) |
120 |
120 |
ECTS_for_required_study = models.PositiveSmallIntegerField( |
121 |
121 |
null=True, |
122 |
122 |
help_text=_("The amount of obtained ECTS points for the required course, if any."), |
123 |
123 |
) |
124 |
124 |
|
125 |
125 |
def __str__(self): |
126 |
126 |
if self.name == "": |
127 |
127 |
return _("Prerequisites for %(course)s") % {'course': str(self.course)} |
128 |
128 |
else: |
129 |
129 |
return self.name + " | " + str(self.course) |
130 |
130 |
|
131 |
131 |
|
132 |
132 |
class CourseProgramme(models.Model): |
133 |
133 |
""" It's possible that a course is taught in multiple degree programmes; For |
134 |
134 |
example: Calculus can easily be taught to physics and mathematics students |
135 |
135 |
alike. In this table, these relations are set up, and the related properties |
136 |
136 |
are defined as well. """ |
137 |
137 |
study = models.ForeignKey( |
138 |
138 |
"Study", |
139 |
139 |
on_delete=models.CASCADE, |
140 |
140 |
null=False, |
141 |
141 |
help_text=_("The study in which the course is taught."), |
142 |
142 |
) |
143 |
143 |
course = models.ForeignKey( |
144 |
144 |
"Course", |
145 |
145 |
on_delete=models.CASCADE, |
146 |
146 |
null=False, |
147 |
147 |
help_text=_("The course that this programme is for."), |
148 |
148 |
) |
149 |
149 |
study_programme = models.ForeignKey( |
150 |
150 |
"StudyProgramme", |
151 |
151 |
on_delete=models.CASCADE, |
152 |
152 |
null=False, |
153 |
153 |
help_text=_("The study programme that this course belongs to."), |
154 |
154 |
) |
155 |
155 |
programme_type = models.CharField( |
156 |
156 |
max_length=1, |
157 |
157 |
blank=False, |
158 |
158 |
choices = ( |
159 |
159 |
('C', _("Compulsory")), |
160 |
160 |
('O', _("Optional")), |
161 |
161 |
), |
162 |
162 |
help_text=_("Type of this course for this study."), |
163 |
163 |
) |
164 |
164 |
study_hours = models.PositiveSmallIntegerField( |
165 |
165 |
blank=False, |
166 |
166 |
help_text=_("The required amount of hours to study this course."), |
167 |
167 |
) |
168 |
168 |
ECTS = models.PositiveSmallIntegerField( |
169 |
169 |
blank=False, |
170 |
170 |
help_text=_("The amount of ECTS points attached to this course."), |
171 |
171 |
) |
172 |
172 |
semester = models.PositiveSmallIntegerField( |
173 |
173 |
blank=False, |
174 |
174 |
choices = ( |
175 |
175 |
(1, _("First semester")), |
176 |
176 |
(2, _("Second semester")), |
177 |
177 |
(3, _("Full year course")), |
178 |
178 |
(4, _("Taught in first quarter")), |
179 |
179 |
(5, _("Taught in second quarter")), |
180 |
180 |
(6, _("Taught in third quarter")), |
181 |
181 |
(7, _("Taught in fourth quarter")), |
182 |
182 |
), |
183 |
183 |
help_text=_("The period in which this course is being taught in this study."), |
184 |
184 |
) |
185 |
185 |
year = models.PositiveSmallIntegerField( |
186 |
186 |
blank=False, |
187 |
187 |
help_text=_("The year in which this course is taught for this study."), |
188 |
188 |
) |
189 |
189 |
second_chance = models.BooleanField( |
190 |
190 |
default=True, |
191 |
191 |
help_text=_("Defines if a second chance exam is planned for this course."), |
192 |
192 |
) |
193 |
193 |
tolerable = models.BooleanField( |
194 |
194 |
default=True, |
195 |
195 |
help_text=_("Defines if a failed result can be tolerated."), |
196 |
196 |
) |
197 |
197 |
scoring = models.CharField( |
198 |
198 |
max_length=2, |
199 |
199 |
choices = ( |
200 |
200 |
('N', _("Numerical")), |
201 |
201 |
('FP', _("Fail/Pass")), |
202 |
202 |
), |
203 |
203 |
default='N', |
204 |
204 |
blank=False, |
205 |
205 |
help_text=_("How the obtained score for this course is given."), |
206 |
206 |
) |
207 |
207 |
|
208 |
208 |
def __str__(self): |
209 |
209 |
return str(self.study) + " - " + str(self.course) |
210 |
210 |
|
211 |
211 |
class Study(models.Model): |
212 |
212 |
""" Defines a certain study that can be followed at the university. |
213 |
213 |
This also includes abridged study programmes, like transition programmes. |
214 |
214 |
Other information, such as descriptions, are kept in the template file |
215 |
215 |
of this study, which can be manually edited. Joeni searches for a file |
216 |
216 |
with the exact name as the study + ".html". So if the study is called |
217 |
217 |
"Bachelor of Informatics", it will search for "Bachelor of Informatics.html". |
218 |
218 |
""" |
219 |
219 |
# Degree types |
220 |
220 |
BSc = _("Bachelor of Science") |
221 |
221 |
MSc = _("Master of Science") |
222 |
222 |
LLB = _("Bachelor of Laws") |
223 |
223 |
LLM = _("Master of Laws") |
224 |
224 |
ir = _("Engineer") |
+ |
225 |
MA = _("Master of Arts") |
+ |
226 |
ir = _("Engineer") |
225 |
227 |
ing = _("Technological Engineer") |
226 |
228 |
# Faculties |
227 |
229 |
FoMaLS = _("Faculty of Medicine and Life Sciences") |
228 |
230 |
FoS = _("Faculty of Sciences") |
229 |
231 |
FoTS = _("Faculty of Transportation Sciences") |
230 |
232 |
FoAaA = _("Faculty of Architecture and Arts") |
231 |
233 |
FoBE = _("Faculty of Business Economics") |
232 |
234 |
FoET = _("Faculty of Engineering Technology") |
233 |
235 |
FoL = _("Faculty of Law") |
234 |
236 |
|
235 |
237 |
name = models.CharField( |
236 |
238 |
max_length=128, |
237 |
239 |
blank=False, |
238 |
240 |
unique=True, |
239 |
241 |
help_text=_("The full name of this study, in the language it's taught in."), |
240 |
242 |
) |
241 |
243 |
degree_type = models.CharField( |
242 |
244 |
max_length=64, |
243 |
245 |
choices = ( |
244 |
246 |
('BSc', BSc), |
245 |
247 |
('MSc', MSc), |
246 |
248 |
('LL.B', LLB), |
247 |
249 |
('LL.M', LLM), |
248 |
250 |
('ir.', ir ), |
249 |
251 |
('ing.',ing), |
250 |
252 |
), |
+ |
253 |
('MA', MA), |
+ |
254 |
), |
251 |
255 |
blank=False, |
252 |
256 |
help_text=_("The type of degree one obtains upon passing this study."), |
253 |
257 |
) |
254 |
258 |
language = models.CharField( |
255 |
259 |
max_length=64, |
256 |
260 |
choices = ( |
257 |
261 |
('NL', _("Dutch")), |
258 |
262 |
('EN', _("English")), |
259 |
263 |
('FR', _("French")), |
260 |
264 |
), |
261 |
265 |
null=False, |
262 |
266 |
help_text=_("The language in which this study is given."), |
263 |
267 |
) |
264 |
268 |
# Information about exam committee |
265 |
269 |
chairman = models.ForeignKey( |
266 |
270 |
"administration.User", |
267 |
271 |
on_delete=models.PROTECT, |
268 |
272 |
null=False, |
269 |
273 |
limit_choices_to={'is_staff': True}, |
270 |
274 |
help_text=_("The chairman of this study."), |
271 |
275 |
related_name="chairman", |
272 |
276 |
) |
273 |
277 |
vice_chairman = models.ForeignKey( |
274 |
278 |
"administration.User", |
275 |
279 |
on_delete=models.PROTECT, |
276 |
280 |
null=False, |
277 |
281 |
help_text=_("The vice-chairman of this study."), |
278 |
282 |
limit_choices_to={'is_staff': True}, |
279 |
283 |
related_name="vice_chairman", |
280 |
284 |
) |
281 |
285 |
secretary = models.ForeignKey( |
282 |
286 |
"administration.User", |
283 |
287 |
on_delete=models.PROTECT, |
284 |
288 |
null=False, |
285 |
289 |
help_text=_("The secretary of this study."), |
286 |
290 |
limit_choices_to={'is_staff': True}, |
287 |
291 |
related_name="secretary", |
288 |
292 |
) |
289 |
293 |
ombuds = models.ForeignKey( |
290 |
294 |
"administration.User", |
291 |
295 |
on_delete=models.PROTECT, |
292 |
296 |
null=False, |
293 |
297 |
help_text=_("The ombuds person of this study."), |
294 |
298 |
limit_choices_to={'is_staff': True}, |
295 |
299 |
related_name="ombuds", |
296 |
300 |
) |
297 |
301 |
vice_ombuds = models.ForeignKey( |
298 |
302 |
"administration.User", |
299 |
303 |
on_delete=models.PROTECT, |
300 |
304 |
null=False, |
301 |
305 |
help_text=_("The (replacing) ombuds person of this study."), |
302 |
306 |
limit_choices_to={'is_staff': True}, |
303 |
307 |
related_name="vice_ombuds", |
304 |
308 |
) |
305 |
309 |
additional_members = models.ManyToManyField( |
306 |
310 |
"administration.User", |
307 |
311 |
help_text=_("All the other members of the exam committee."), |
308 |
312 |
limit_choices_to={'is_staff': True}, |
309 |
313 |
related_name="additional_members", |
310 |
314 |
) |
311 |
315 |
faculty = models.CharField( |
312 |
316 |
max_length=6, |
313 |
317 |
choices = ( |
314 |
318 |
('FoS', FoS), |
315 |
319 |
('FoTS', FoTS), |
316 |
320 |
('FoAaA', FoAaA), |
317 |
321 |
('FoBE', FoBE), |
318 |
322 |
('FoMaLS', FoMaLS), |
319 |
323 |
('FoET', FoET), |
320 |
324 |
('FoL', FoL), |
321 |
325 |
), |
322 |
326 |
blank=False, |
323 |
327 |
help_text=_("The faculty where this study belongs to."), |
324 |
328 |
) |
325 |
329 |
|
326 |
330 |
#def study_points(self): |
327 |
331 |
""" Returns the amount of study points for this year. |
328 |
332 |
This value is inferred based on the study programme information |
329 |
333 |
records that lists this study as their foreign key. """ |
330 |
334 |
#total_ECTS = 0 |
331 |
335 |
#for course in CourseProgramme.objects.filter(study=self): |
332 |
336 |
#total_ECTS += course.ECTS |
333 |
337 |
#return total_ECTS |
334 |
338 |
# XXX: Commented because this is actually something for the StudyProgramme |
335 |
339 |
def years(self): |
336 |
340 |
""" Returns the amount of years this study takes. |
337 |
341 |
This value is inferred based on the study programme information |
338 |
342 |
records that lists this study as their foreign key. """ |
339 |
343 |
highest_year = 0 |
340 |
344 |
for course in CourseProgramme.objects.filter(study=self): |
341 |
345 |
highest_year = max(highest_year, course.year) |
342 |
346 |
return highest_year |
343 |
347 |
|
344 |
348 |
def students(self): |
345 |
349 |
""" Cross references the information stored in the database, and |
346 |
350 |
returns all the students that are following this study in this |
347 |
351 |
academic year. """ |
348 |
352 |
return 0 # TODO |
349 |
353 |
|
350 |
354 |
|
351 |
355 |
def __str__(self): |
352 |
356 |
return self.name |
353 |
357 |
|
354 |
358 |
class StudyProgramme(models.Model): |
355 |
359 |
""" Represents a programme within a certain study. |
356 |
360 |
A good example for this is the different specializations, minors, majors, ... |
357 |
361 |
one can follow within the same study. Nevertheless, they're all made of |
358 |
362 |
a certain set of courses. This table collects all these, and allows one to name |
359 |
363 |
them, so they're distinct from one another. """ |
360 |
364 |
name = models.CharField( |
361 |
365 |
max_length=64, |
362 |
366 |
blank=False, |
363 |
367 |
help_text=_("The name of this programme."), |
364 |
368 |
) |
365 |
369 |
|
366 |
370 |
def courses(self): |
367 |
371 |
""" All courses that are part of this study programme. """ |
368 |
372 |
programmes = CourseProgramme.objects.filter(study_programme=self) |
369 |
373 |
courses = {} |
370 |
374 |
for program in programmes: |
371 |
375 |
courses.add(program.course) |
372 |
376 |
return courses |
373 |
377 |
|
374 |
378 |
def study_points(self, year=None): |
375 |
379 |
""" Returns the amount of study points this programme contains. |
376 |
380 |
Accepts year as an optional argument. If not given, the study points |
377 |
381 |
of all years are returned. """ |
378 |
382 |
programmes = CourseProgramme.objects.filter(study_programme=self) |
379 |
383 |
ECTS = 0 |
380 |
384 |
for program in programmes: |
381 |
385 |
if year is None or program.year == year: |
382 |
386 |
# XXX: This only works if the used implementation does lazy |
383 |
387 |
# evaluation, otherwise this is a type error! |
384 |
388 |
ECTS += program.ECTS |
385 |
389 |
return ECTS |
386 |
390 |
|
387 |
391 |
def __str__(self): |
388 |
392 |
return self.name |
389 |
393 |
|
390 |
394 |
# Tables about things related to the courses: |
391 |
395 |
|
392 |
396 |
class Assignment(models.Model): |
393 |
397 |
""" For courses, it's possible to set up tasks. These tasks are recorded |
394 |
398 |
here. """ |
395 |
399 |
# TODO: Require that only the course team can create assignments for a team. |
396 |
400 |
course = models.ForeignKey( |
397 |
401 |
"Course", |
398 |
402 |
on_delete=models.CASCADE, |
399 |
403 |
null=False, |
400 |
404 |
#editable=False, |
401 |
405 |
db_index=True, |
402 |
406 |
help_text=_("The course for which this task is assigned."), |
403 |
407 |
) |
404 |
408 |
title = models.CharField( |
405 |
409 |
max_length=32, |
406 |
410 |
blank=False, |
407 |
411 |
help_text=_("The title of this assignment."), |
408 |
412 |
) |
409 |
413 |
information = models.TextField( |
410 |
414 |
help_text=_("Any additional information regarding the assignment. Orgmode syntax available."), |
411 |
415 |
) |
412 |
416 |
deadline = models.DateTimeField( |
413 |
417 |
null=False, |
414 |
418 |
help_text=_("The date and time this task is due."), |
415 |
419 |
) |
416 |
420 |
posted = models.DateField(auto_now_add=True) |
417 |
421 |
digital_task = models.BooleanField( |
418 |
422 |
default=True, |
419 |
423 |
help_text=_("This determines whether this assignment requires handing " |
420 |
424 |
"in a digital file."), |
421 |
425 |
) |
422 |
426 |
|
423 |
427 |
def __str__(self): |
424 |
428 |
return str(self.course) +" | "+ str(self.posted) |
425 |
429 |
|
426 |
430 |
class Announcement(models.Model): |
427 |
431 |
""" Courses sometimes have to make announcements for the students. """ |
428 |
432 |
course = models.ForeignKey( |
429 |
433 |
"Course", |
430 |
434 |
on_delete=models.CASCADE, |
431 |
435 |
null=False, |
432 |
436 |
#editable=False, |
433 |
437 |
db_index=True, |
434 |
438 |
help_text=_("The course for which this announcement is made."), |
435 |
439 |
) |
436 |
440 |
title = models.CharField( |
437 |
441 |
max_length=20, # Keep It Short & Simple® |
438 |
442 |
help_text=_("A quick title for what this is about."), |
439 |
443 |
) |
440 |
444 |
text = models.TextField( |
441 |
445 |
blank=False, |
442 |
446 |
help_text=_("The announcement itself. Orgmode syntax available."), |
443 |
447 |
) |
444 |
448 |
posted = models.DateTimeField(auto_now_add=True) |
445 |
449 |
|
446 |
450 |
def __str__(self): |
447 |
451 |
return str(self.course) +" | "+ self.posted.strftime("%m/%d") |
448 |
452 |
|
449 |
453 |
class Upload(models.Model): |
450 |
454 |
""" For certain assignments, digital hand-ins may be required. These hand |
451 |
455 |
ins are recorded per student in this table. """ |
452 |
456 |
assignment = models.ForeignKey( |
453 |
457 |
"Assignment", |
454 |
458 |
on_delete=models.CASCADE, |
455 |
459 |
null=False, |
456 |
460 |
#editable=False, |
457 |
461 |
db_index=True, |
458 |
462 |
limit_choices_to={"digital_task": True}, |
459 |
463 |
help_text=_("For which assignment this upload is."), |
460 |
464 |
) |
461 |
465 |
# TODO: Try to find a way to require that, if the upload is made, |
462 |
466 |
# only students that have this course in their curriculum can upload. |
463 |
467 |
student = models.ForeignKey( |
464 |
468 |
"administration.User", |
465 |
469 |
on_delete=models.CASCADE, |
466 |
470 |
null=False, |
467 |
471 |
#editable=False, |
468 |
472 |
limit_choices_to={"is_student": True}, |
469 |
473 |
help_text=_("The student who handed this in."), |
470 |
474 |
) |
471 |
475 |
upload_time = models.DateTimeField(auto_now_add=True) |
472 |
476 |
comment = models.TextField( |
473 |
477 |
blank=True, |
474 |
478 |
help_text=_("If you wish to add an additional comment, state it here."), |
475 |
479 |
) |
476 |
480 |
file = models.FileField( |
477 |
481 |
upload_to="assignments/uploads/%Y/%m/", |
478 |
482 |
null=False, |
479 |
483 |
#editable=False, |
480 |
484 |
help_text=_("The file you want to upload for this assignment."), |
481 |
485 |
) |
482 |
486 |
|
483 |
487 |
|
484 |
488 |
def __str__(self): |
485 |
489 |
deadline = self.assignment.deadline |
486 |
490 |
if deadline < self.upload_time: |
487 |
491 |
return str(self.assignment.course) +" | "+ str(self.student.number) + _("(OVERDUE)") |
488 |
492 |
else: |
489 |
493 |
return str(self.assignment.course) +" | "+ str(self.student.number) |
490 |
494 |
|
491 |
495 |
def item_upload_directory(instance, filename): |
492 |
496 |
return "courses/" + instance.course.slug_name + "/" |
493 |
497 |
class CourseItem(models.Model): |
494 |
498 |
""" Reprensents study material for a course that is being shared by the |
495 |
499 |
course's education team. """ |
496 |
500 |
course = models.ForeignKey( |
497 |
501 |
Course, |
498 |
502 |
on_delete=models.CASCADE, |
499 |
503 |
null=False, |
500 |
504 |
#editable=False, |
501 |
505 |
) |
502 |
506 |
file = models.FileField( |
503 |
507 |
upload_to=item_upload_directory, |
504 |
508 |
null=False, |
505 |
509 |
#editable=False, |
506 |
510 |
help_text=_("The file you wish to upload."), |
507 |
511 |
) |
508 |
512 |
timestamp = models.DateTimeField(auto_now_add=True) |
509 |
513 |
note = models.TextField( |
510 |
514 |
blank=True, |
511 |
515 |
help_text=_("If you want to state some additional information about " |
512 |
516 |
"this upload, state it here."), |
513 |
517 |
) |
514 |
518 |
|
515 |
519 |
class StudyGroup(models.Model): |
516 |
520 |
""" It may be necessary to make study groups regarding a course. These |
517 |
521 |
are recorded here, and blend in seamlessly with the Groups from Agora. |
518 |
522 |
Groups that are recorded as a StudyGroup, are given official course status, |
519 |
523 |
and thus, cannot be removed until the status of StudyGroup is lifted. """ |
520 |
524 |
course = models.ForeignKey( |
521 |
525 |
"Course", |
522 |
526 |
on_delete=models.CASCADE, |
523 |
527 |
null=False, |
524 |
528 |
#editable=False, |
525 |
529 |
db_index=True, |
526 |
530 |
help_text=_("The course for which this group is."), |
527 |
531 |
) |
528 |
532 |
group = models.ForeignKey( |
529 |
533 |
"agora.Group", |
530 |
534 |
on_delete=models.PROTECT, # See class documentation |
531 |
535 |
null=False, |
532 |
536 |
#editable=False, # Keep the same group |
533 |
537 |
help_text=_("The group that will be seen as the study group."), |
534 |
538 |
) |
535 |
539 |
|
536 |
540 |
def __str__(self): |
537 |
541 |
return str(self.course) +" | "+ str(self.group) |
538 |
542 |
|
+ |
543 |
class Group(models.Model): |
+ |
544 |
"""Because of size, some studies may use multiple groups for the different |
+ |
545 |
students, so it's possible to facilitate all of them. These groups must be |
+ |
546 |
registered here.""" |
+ |
547 |
study = models.ForeignKey( |
+ |
548 |
"Study", |
+ |
549 |
on_delete=models.CASCADE, |
+ |
550 |
null=False, |
+ |
551 |
) |
+ |
552 |
# TODO: How to attach students to certain groups? The curriculum or what? |
+ |
553 |
|
+ |
554 |