Add a lot of files and changes
A lot of the new files are the recent migrations, caused by a variety of bugs in the database.
In addition, I've also added a lot of new files for the program, including some static files like the stylesheets, and some templates.
Also some changes in other files, but there are too much, I can't begin to list them here. Please check the diffs.
- Author
- Maarten 'Vngngdn' Vangeneugden
- Date
- Nov. 22, 2017, 12:17 a.m.
- Hash
- 53066247d90ebba534b0a4da2b14ac8189bc2eec
- Parent
- f1175e5c018fb47d6b6dd76045df6de897b8ed39
- Modified files
- administration/migrations/0004_auto_20171121_1906.py
- administration/migrations/0005_auto_20171121_1921.py
- administration/migrations/0006_auto_20171121_1924.py
- administration/migrations/0007_auto_20171121_2018.py
- administration/models.py
- agora/forms.py
- agora/migrations/0003_auto_20171121_1906.py
- agora/migrations/0004_auto_20171121_2018.py
- agora/models.py
- agora/templates/agora/post.djhtml
- courses/migrations/0002_auto_20171121_2018.py
- courses/models.py
- courses/templates/courses/courses/course.djhtml
- courses/templates/courses/studies/study.djhtml
- courses/urls.py
- courses/views.py
- joeni/settings.py
- joeni/templates/flat/about.html
- joeni/urls.py
- static/_colors.scss
- static/_fonts.scss
- static/css/post.scss
- static/main.scss
administration/migrations/0004_auto_20171121_1906.py ¶
148 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', '0003_auto_20171118_2204'), |
+ |
9 |
] |
+ |
10 |
|
+ |
11 |
operations = [ |
+ |
12 |
migrations.RemoveField( |
+ |
13 |
model_name='user', |
+ |
14 |
name='BIC', |
+ |
15 |
), |
+ |
16 |
migrations.RemoveField( |
+ |
17 |
model_name='user', |
+ |
18 |
name='DOB', |
+ |
19 |
), |
+ |
20 |
migrations.RemoveField( |
+ |
21 |
model_name='user', |
+ |
22 |
name='POB', |
+ |
23 |
), |
+ |
24 |
migrations.RemoveField( |
+ |
25 |
model_name='user', |
+ |
26 |
name='bank_account_number', |
+ |
27 |
), |
+ |
28 |
migrations.RemoveField( |
+ |
29 |
model_name='user', |
+ |
30 |
name='civil_status', |
+ |
31 |
), |
+ |
32 |
migrations.RemoveField( |
+ |
33 |
model_name='user', |
+ |
34 |
name='created', |
+ |
35 |
), |
+ |
36 |
migrations.RemoveField( |
+ |
37 |
model_name='user', |
+ |
38 |
name='home_bus', |
+ |
39 |
), |
+ |
40 |
migrations.RemoveField( |
+ |
41 |
model_name='user', |
+ |
42 |
name='home_country', |
+ |
43 |
), |
+ |
44 |
migrations.RemoveField( |
+ |
45 |
model_name='user', |
+ |
46 |
name='home_number', |
+ |
47 |
), |
+ |
48 |
migrations.RemoveField( |
+ |
49 |
model_name='user', |
+ |
50 |
name='home_postal_code', |
+ |
51 |
), |
+ |
52 |
migrations.RemoveField( |
+ |
53 |
model_name='user', |
+ |
54 |
name='home_street', |
+ |
55 |
), |
+ |
56 |
migrations.RemoveField( |
+ |
57 |
model_name='user', |
+ |
58 |
name='home_telephone', |
+ |
59 |
), |
+ |
60 |
migrations.RemoveField( |
+ |
61 |
model_name='user', |
+ |
62 |
name='is_student', |
+ |
63 |
), |
+ |
64 |
migrations.RemoveField( |
+ |
65 |
model_name='user', |
+ |
66 |
name='national_registry_number', |
+ |
67 |
), |
+ |
68 |
migrations.RemoveField( |
+ |
69 |
model_name='user', |
+ |
70 |
name='nationality', |
+ |
71 |
), |
+ |
72 |
migrations.RemoveField( |
+ |
73 |
model_name='user', |
+ |
74 |
name='passphrase', |
+ |
75 |
), |
+ |
76 |
migrations.RemoveField( |
+ |
77 |
model_name='user', |
+ |
78 |
name='study_bus', |
+ |
79 |
), |
+ |
80 |
migrations.RemoveField( |
+ |
81 |
model_name='user', |
+ |
82 |
name='study_cellphone', |
+ |
83 |
), |
+ |
84 |
migrations.RemoveField( |
+ |
85 |
model_name='user', |
+ |
86 |
name='study_country', |
+ |
87 |
), |
+ |
88 |
migrations.RemoveField( |
+ |
89 |
model_name='user', |
+ |
90 |
name='study_number', |
+ |
91 |
), |
+ |
92 |
migrations.RemoveField( |
+ |
93 |
model_name='user', |
+ |
94 |
name='study_postal_code', |
+ |
95 |
), |
+ |
96 |
migrations.RemoveField( |
+ |
97 |
model_name='user', |
+ |
98 |
name='study_street', |
+ |
99 |
), |
+ |
100 |
migrations.RemoveField( |
+ |
101 |
model_name='user', |
+ |
102 |
name='study_telephone', |
+ |
103 |
), |
+ |
104 |
migrations.RemoveField( |
+ |
105 |
model_name='user', |
+ |
106 |
name='title', |
+ |
107 |
), |
+ |
108 |
migrations.RemoveField( |
+ |
109 |
model_name='user', |
+ |
110 |
name='titularis_bus', |
+ |
111 |
), |
+ |
112 |
migrations.RemoveField( |
+ |
113 |
model_name='user', |
+ |
114 |
name='titularis_country', |
+ |
115 |
), |
+ |
116 |
migrations.RemoveField( |
+ |
117 |
model_name='user', |
+ |
118 |
name='titularis_number', |
+ |
119 |
), |
+ |
120 |
migrations.RemoveField( |
+ |
121 |
model_name='user', |
+ |
122 |
name='titularis_postal_code', |
+ |
123 |
), |
+ |
124 |
migrations.RemoveField( |
+ |
125 |
model_name='user', |
+ |
126 |
name='titularis_street', |
+ |
127 |
), |
+ |
128 |
migrations.RemoveField( |
+ |
129 |
model_name='user', |
+ |
130 |
name='titularis_telephone', |
+ |
131 |
), |
+ |
132 |
migrations.AlterField( |
+ |
133 |
model_name='user', |
+ |
134 |
name='first_name', |
+ |
135 |
field=models.CharField(blank=True, max_length=30, verbose_name='first name'), |
+ |
136 |
), |
+ |
137 |
migrations.AlterField( |
+ |
138 |
model_name='user', |
+ |
139 |
name='is_staff', |
+ |
140 |
field=models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status'), |
+ |
141 |
), |
+ |
142 |
migrations.AlterField( |
+ |
143 |
model_name='user', |
+ |
144 |
name='last_name', |
+ |
145 |
field=models.CharField(blank=True, max_length=150, verbose_name='last name'), |
+ |
146 |
), |
+ |
147 |
] |
+ |
148 |
administration/migrations/0005_auto_20171121_1921.py ¶
203 additions and 0 deletions.
View changes Hide changes
+ |
1 |
|
+ |
2 |
import administration.models |
+ |
3 |
import datetime |
+ |
4 |
from django.db import migrations, models |
+ |
5 |
import django.utils.timezone |
+ |
6 |
|
+ |
7 |
|
+ |
8 |
class Migration(migrations.Migration): |
+ |
9 |
|
+ |
10 |
dependencies = [ |
+ |
11 |
('administration', '0004_auto_20171121_1906'), |
+ |
12 |
] |
+ |
13 |
|
+ |
14 |
operations = [ |
+ |
15 |
migrations.AddField( |
+ |
16 |
model_name='user', |
+ |
17 |
name='BIC', |
+ |
18 |
field=models.CharField(default=535, help_text="The BIC of this user's bank.", max_length=11, validators=[administration.models.validate_BIC]), |
+ |
19 |
preserve_default=False, |
+ |
20 |
), |
+ |
21 |
migrations.AddField( |
+ |
22 |
model_name='user', |
+ |
23 |
name='DOB', |
+ |
24 |
field=models.DateField(default=datetime.datetime(2017, 11, 21, 19, 19, 30, 347992), editable=False, help_text='The date of birth of this user.'), |
+ |
25 |
preserve_default=False, |
+ |
26 |
), |
+ |
27 |
migrations.AddField( |
+ |
28 |
model_name='user', |
+ |
29 |
name='POB', |
+ |
30 |
field=models.CharField(default='Genk', editable=False, help_text='The place of birth of this user.', max_length=64), |
+ |
31 |
preserve_default=False, |
+ |
32 |
), |
+ |
33 |
migrations.AddField( |
+ |
34 |
model_name='user', |
+ |
35 |
name='bank_account_number', |
+ |
36 |
field=models.CharField(default=4353, help_text='The IBAN of this user. No spaces!', max_length=34, validators=[administration.models.validate_IBAN]), |
+ |
37 |
preserve_default=False, |
+ |
38 |
), |
+ |
39 |
migrations.AddField( |
+ |
40 |
model_name='user', |
+ |
41 |
name='civil_status', |
+ |
42 |
field=models.CharField(choices=[('Single', 'Single'), ('Married', 'Married'), ('Divorced', 'Divorced'), ('Widowed', 'Widowed'), ('Partnership', 'Partnership')], default='Single', help_text='The civil/marital status of the user.', max_length=32), |
+ |
43 |
preserve_default=False, |
+ |
44 |
), |
+ |
45 |
migrations.AddField( |
+ |
46 |
model_name='user', |
+ |
47 |
name='created', |
+ |
48 |
field=models.DateField(auto_now_add=True, default=django.utils.timezone.now), |
+ |
49 |
preserve_default=False, |
+ |
50 |
), |
+ |
51 |
migrations.AddField( |
+ |
52 |
model_name='user', |
+ |
53 |
name='home_bus', |
+ |
54 |
field=models.PositiveSmallIntegerField(default=3), |
+ |
55 |
preserve_default=False, |
+ |
56 |
), |
+ |
57 |
migrations.AddField( |
+ |
58 |
model_name='user', |
+ |
59 |
name='home_country', |
+ |
60 |
field=models.CharField(default='Belgium', max_length=64), |
+ |
61 |
preserve_default=False, |
+ |
62 |
), |
+ |
63 |
migrations.AddField( |
+ |
64 |
model_name='user', |
+ |
65 |
name='home_number', |
+ |
66 |
field=models.PositiveSmallIntegerField(default=22), |
+ |
67 |
preserve_default=False, |
+ |
68 |
), |
+ |
69 |
migrations.AddField( |
+ |
70 |
model_name='user', |
+ |
71 |
name='home_postal_code', |
+ |
72 |
field=models.PositiveSmallIntegerField(default=3740), |
+ |
73 |
preserve_default=False, |
+ |
74 |
), |
+ |
75 |
migrations.AddField( |
+ |
76 |
model_name='user', |
+ |
77 |
name='home_street', |
+ |
78 |
field=models.CharField(default='Kuil', max_length=64), |
+ |
79 |
preserve_default=False, |
+ |
80 |
), |
+ |
81 |
migrations.AddField( |
+ |
82 |
model_name='user', |
+ |
83 |
name='home_telephone', |
+ |
84 |
field=models.CharField(default=4223, help_text='The telephone number for the house address. Prefix 0 can be presented with the national call code in the system.', max_length=64), |
+ |
85 |
preserve_default=False, |
+ |
86 |
), |
+ |
87 |
migrations.AddField( |
+ |
88 |
model_name='user', |
+ |
89 |
name='is_student', |
+ |
90 |
field=models.BooleanField(default=True, help_text='Indicates if this user is a student at the university.'), |
+ |
91 |
), |
+ |
92 |
migrations.AddField( |
+ |
93 |
model_name='user', |
+ |
94 |
name='national_registry_number', |
+ |
95 |
field=models.BigIntegerField(default=33333, editable=False, help_text='The assigned national registry number of this user.', unique=True), |
+ |
96 |
preserve_default=False, |
+ |
97 |
), |
+ |
98 |
migrations.AddField( |
+ |
99 |
model_name='user', |
+ |
100 |
name='nationality', |
+ |
101 |
field=models.CharField(default='Belgian', help_text='The current nationality of this user.', max_length=64), |
+ |
102 |
preserve_default=False, |
+ |
103 |
), |
+ |
104 |
migrations.AddField( |
+ |
105 |
model_name='user', |
+ |
106 |
name='study_bus', |
+ |
107 |
field=models.PositiveSmallIntegerField(default=4), |
+ |
108 |
preserve_default=False, |
+ |
109 |
), |
+ |
110 |
migrations.AddField( |
+ |
111 |
model_name='user', |
+ |
112 |
name='study_cellphone', |
+ |
113 |
field=models.CharField(default=33442, help_text='The cellphone number of the person. Prefix 0 can be presented with then national call code in the system.', max_length=64), |
+ |
114 |
preserve_default=False, |
+ |
115 |
), |
+ |
116 |
migrations.AddField( |
+ |
117 |
model_name='user', |
+ |
118 |
name='study_country', |
+ |
119 |
field=models.CharField(default='Belgium', max_length=64), |
+ |
120 |
preserve_default=False, |
+ |
121 |
), |
+ |
122 |
migrations.AddField( |
+ |
123 |
model_name='user', |
+ |
124 |
name='study_number', |
+ |
125 |
field=models.PositiveSmallIntegerField(default=4), |
+ |
126 |
preserve_default=False, |
+ |
127 |
), |
+ |
128 |
migrations.AddField( |
+ |
129 |
model_name='user', |
+ |
130 |
name='study_postal_code', |
+ |
131 |
field=models.PositiveSmallIntegerField(default=234), |
+ |
132 |
preserve_default=False, |
+ |
133 |
), |
+ |
134 |
migrations.AddField( |
+ |
135 |
model_name='user', |
+ |
136 |
name='study_street', |
+ |
137 |
field=models.CharField(default='Streeeeeet', max_length=64), |
+ |
138 |
preserve_default=False, |
+ |
139 |
), |
+ |
140 |
migrations.AddField( |
+ |
141 |
model_name='user', |
+ |
142 |
name='study_telephone', |
+ |
143 |
field=models.CharField(default=2442222, help_text='The telephone number for the study address. Prefix 0 can be presented with the national call code in the system.', max_length=64), |
+ |
144 |
preserve_default=False, |
+ |
145 |
), |
+ |
146 |
migrations.AddField( |
+ |
147 |
model_name='user', |
+ |
148 |
name='title', |
+ |
149 |
field=models.CharField(blank=True, help_text='The academic title of this user, if applicable.', max_length=64), |
+ |
150 |
), |
+ |
151 |
migrations.AddField( |
+ |
152 |
model_name='user', |
+ |
153 |
name='titularis_bus', |
+ |
154 |
field=models.PositiveSmallIntegerField(default=2), |
+ |
155 |
preserve_default=False, |
+ |
156 |
), |
+ |
157 |
migrations.AddField( |
+ |
158 |
model_name='user', |
+ |
159 |
name='titularis_country', |
+ |
160 |
field=models.CharField(default='Beligum', max_length=64), |
+ |
161 |
preserve_default=False, |
+ |
162 |
), |
+ |
163 |
migrations.AddField( |
+ |
164 |
model_name='user', |
+ |
165 |
name='titularis_number', |
+ |
166 |
field=models.PositiveSmallIntegerField(default=2), |
+ |
167 |
preserve_default=False, |
+ |
168 |
), |
+ |
169 |
migrations.AddField( |
+ |
170 |
model_name='user', |
+ |
171 |
name='titularis_postal_code', |
+ |
172 |
field=models.PositiveSmallIntegerField(default=1), |
+ |
173 |
preserve_default=False, |
+ |
174 |
), |
+ |
175 |
migrations.AddField( |
+ |
176 |
model_name='user', |
+ |
177 |
name='titularis_street', |
+ |
178 |
field=models.CharField(default='IEP', max_length=64), |
+ |
179 |
preserve_default=False, |
+ |
180 |
), |
+ |
181 |
migrations.AddField( |
+ |
182 |
model_name='user', |
+ |
183 |
name='titularis_telephone', |
+ |
184 |
field=models.CharField(default=9, help_text='The telephone number of the titularis. Prefix 0 can be presented with the national call code in the system.', max_length=64), |
+ |
185 |
preserve_default=False, |
+ |
186 |
), |
+ |
187 |
migrations.AlterField( |
+ |
188 |
model_name='user', |
+ |
189 |
name='first_name', |
+ |
190 |
field=models.CharField(max_length=64), |
+ |
191 |
), |
+ |
192 |
migrations.AlterField( |
+ |
193 |
model_name='user', |
+ |
194 |
name='is_staff', |
+ |
195 |
field=models.BooleanField(default=False, help_text="Determines if this user is part of the university's staff."), |
+ |
196 |
), |
+ |
197 |
migrations.AlterField( |
+ |
198 |
model_name='user', |
+ |
199 |
name='last_name', |
+ |
200 |
field=models.CharField(max_length=64), |
+ |
201 |
), |
+ |
202 |
] |
+ |
203 |
administration/migrations/0006_auto_20171121_1924.py ¶
53 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', '0005_auto_20171121_1921'), |
+ |
9 |
] |
+ |
10 |
|
+ |
11 |
operations = [ |
+ |
12 |
migrations.AlterField( |
+ |
13 |
model_name='user', |
+ |
14 |
name='home_bus', |
+ |
15 |
field=models.PositiveSmallIntegerField(null=True), |
+ |
16 |
), |
+ |
17 |
migrations.AlterField( |
+ |
18 |
model_name='user', |
+ |
19 |
name='study_bus', |
+ |
20 |
field=models.PositiveSmallIntegerField(null=True), |
+ |
21 |
), |
+ |
22 |
migrations.AlterField( |
+ |
23 |
model_name='user', |
+ |
24 |
name='titularis_bus', |
+ |
25 |
field=models.PositiveSmallIntegerField(null=True), |
+ |
26 |
), |
+ |
27 |
migrations.AlterField( |
+ |
28 |
model_name='user', |
+ |
29 |
name='titularis_country', |
+ |
30 |
field=models.CharField(max_length=64, null=True), |
+ |
31 |
), |
+ |
32 |
migrations.AlterField( |
+ |
33 |
model_name='user', |
+ |
34 |
name='titularis_number', |
+ |
35 |
field=models.PositiveSmallIntegerField(null=True), |
+ |
36 |
), |
+ |
37 |
migrations.AlterField( |
+ |
38 |
model_name='user', |
+ |
39 |
name='titularis_postal_code', |
+ |
40 |
field=models.PositiveSmallIntegerField(null=True), |
+ |
41 |
), |
+ |
42 |
migrations.AlterField( |
+ |
43 |
model_name='user', |
+ |
44 |
name='titularis_street', |
+ |
45 |
field=models.CharField(max_length=64, null=True), |
+ |
46 |
), |
+ |
47 |
migrations.AlterField( |
+ |
48 |
model_name='user', |
+ |
49 |
name='titularis_telephone', |
+ |
50 |
field=models.CharField(help_text='The telephone number of the titularis. Prefix 0 can be presented with the national call code in the system.', max_length=64, null=True), |
+ |
51 |
), |
+ |
52 |
] |
+ |
53 |
administration/migrations/0007_auto_20171121_2018.py ¶
80 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 |
('administration', '0006_auto_20171121_1924'), |
+ |
11 |
] |
+ |
12 |
|
+ |
13 |
operations = [ |
+ |
14 |
migrations.AlterField( |
+ |
15 |
model_name='curriculum', |
+ |
16 |
name='student', |
+ |
17 |
field=models.ForeignKey(limit_choices_to={'is_student': True}, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, unique_for_year='year'), |
+ |
18 |
), |
+ |
19 |
migrations.AlterField( |
+ |
20 |
model_name='degree', |
+ |
21 |
name='DOB', |
+ |
22 |
field=models.DateField(), |
+ |
23 |
), |
+ |
24 |
migrations.AlterField( |
+ |
25 |
model_name='degree', |
+ |
26 |
name='POB', |
+ |
27 |
field=models.CharField(max_length=64), |
+ |
28 |
), |
+ |
29 |
migrations.AlterField( |
+ |
30 |
model_name='degree', |
+ |
31 |
name='achieved', |
+ |
32 |
field=models.DateField(), |
+ |
33 |
), |
+ |
34 |
migrations.AlterField( |
+ |
35 |
model_name='degree', |
+ |
36 |
name='additional_names', |
+ |
37 |
field=models.CharField(blank=True, max_length=64), |
+ |
38 |
), |
+ |
39 |
migrations.AlterField( |
+ |
40 |
model_name='degree', |
+ |
41 |
name='study', |
+ |
42 |
field=models.CharField(max_length=64), |
+ |
43 |
), |
+ |
44 |
migrations.AlterField( |
+ |
45 |
model_name='preregistration', |
+ |
46 |
name='DOB', |
+ |
47 |
field=models.DateField(help_text='Your date of birth.'), |
+ |
48 |
), |
+ |
49 |
migrations.AlterField( |
+ |
50 |
model_name='preregistration', |
+ |
51 |
name='POB', |
+ |
52 |
field=models.CharField(help_text='The place you were born.', max_length=64), |
+ |
53 |
), |
+ |
54 |
migrations.AlterField( |
+ |
55 |
model_name='roomreservation', |
+ |
56 |
name='reservator', |
+ |
57 |
field=models.ForeignKey(help_text='The person that made the reservation (and thus responsible).', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), |
+ |
58 |
), |
+ |
59 |
migrations.AlterField( |
+ |
60 |
model_name='roomreservation', |
+ |
61 |
name='room', |
+ |
62 |
field=models.ForeignKey(help_text='The room that is being reserved at this point.', limit_choices_to={'reservable': True}, on_delete=django.db.models.deletion.CASCADE, to='administration.Room'), |
+ |
63 |
), |
+ |
64 |
migrations.AlterField( |
+ |
65 |
model_name='user', |
+ |
66 |
name='DOB', |
+ |
67 |
field=models.DateField(help_text='The date of birth of this user.'), |
+ |
68 |
), |
+ |
69 |
migrations.AlterField( |
+ |
70 |
model_name='user', |
+ |
71 |
name='POB', |
+ |
72 |
field=models.CharField(help_text='The place of birth of this user.', max_length=64), |
+ |
73 |
), |
+ |
74 |
migrations.AlterField( |
+ |
75 |
model_name='user', |
+ |
76 |
name='national_registry_number', |
+ |
77 |
field=models.BigIntegerField(help_text='The assigned national registry number of this user.', unique=True), |
+ |
78 |
), |
+ |
79 |
] |
+ |
80 |
administration/models.py ¶
21 additions and 24 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 |
passphrase = models.CharField( |
37 |
- | max_length=512, |
38 |
- | blank=False, |
39 |
- | help_text=_("The passphrase used for this account. This field must only contain hashed information."), |
40 |
- | ) |
41 |
- | first_name = models.CharField(max_length=64, blank=False) |
42 |
37 |
last_name = models.CharField(max_length=64, blank=False) |
43 |
38 |
title = models.CharField( |
44 |
39 |
max_length=64, |
45 |
40 |
blank=True, |
46 |
41 |
help_text=_("The academic title of this user, if applicable."), |
47 |
42 |
) |
48 |
43 |
DOB = models.DateField( |
49 |
44 |
blank=False, |
50 |
45 |
editable=False, |
51 |
- | help_text=_("The date of birth of this user."), |
+ |
46 |
help_text=_("The date of birth of this user."), |
52 |
47 |
) |
53 |
48 |
POB = models.CharField( |
54 |
49 |
max_length=64, |
55 |
50 |
blank=False, |
56 |
51 |
editable=False, |
57 |
- | help_text=_("The place of birth of this user."), |
+ |
52 |
help_text=_("The place of birth of this user."), |
58 |
53 |
) |
59 |
54 |
nationality = models.CharField( |
60 |
55 |
max_length=64, |
61 |
56 |
blank=False, |
62 |
57 |
help_text=_("The current nationality of this user."), |
63 |
58 |
) |
64 |
59 |
# XXX: What if this starts with zeros? |
65 |
60 |
national_registry_number = models.BigIntegerField( |
66 |
61 |
unique=True, |
67 |
62 |
editable=False, |
68 |
- | help_text=_("The assigned national registry number of this user."), |
+ |
63 |
help_text=_("The assigned national registry number of this user."), |
69 |
64 |
) |
70 |
65 |
civil_status = models.CharField( |
71 |
66 |
max_length=32, |
72 |
67 |
choices = ( |
73 |
68 |
("Single", _("Single")), |
74 |
69 |
("Married", _("Married")), |
75 |
70 |
("Divorced", _("Divorced")), |
76 |
71 |
("Widowed", _("Widowed")), |
77 |
72 |
("Partnership", _("Partnership")), |
78 |
73 |
), |
79 |
74 |
blank=False, |
80 |
75 |
# There may be more; consult http://www.aantrekkingskracht.com/trefwoord/burgerlijke-staat |
81 |
76 |
# for more information. |
82 |
77 |
help_text=_("The civil/marital status of the user."), |
83 |
78 |
) |
84 |
79 |
|
85 |
80 |
is_staff = models.BooleanField( |
86 |
81 |
default=False, |
87 |
82 |
help_text=_("Determines if this user is part of the university's staff."), |
88 |
83 |
) |
89 |
84 |
is_student = models.BooleanField( |
90 |
85 |
default=True, |
91 |
86 |
help_text=_("Indicates if this user is a student at the university."), |
92 |
87 |
) |
93 |
88 |
|
94 |
89 |
# Home address |
95 |
90 |
home_street = models.CharField(max_length=64, blank=False) |
96 |
91 |
home_number = models.PositiveSmallIntegerField(blank=False) |
97 |
92 |
home_bus = models.PositiveSmallIntegerField() |
98 |
- | home_postal_code = models.PositiveSmallIntegerField(blank=False) |
+ |
93 |
home_postal_code = models.PositiveSmallIntegerField(blank=False) |
99 |
94 |
home_country = models.CharField(max_length=64, blank=False) |
100 |
95 |
home_telephone = models.CharField( |
101 |
96 |
max_length=64, |
102 |
97 |
help_text=_("The telephone number for the house address. Prefix 0 can be presented with the national call code in the system."), |
103 |
98 |
) |
104 |
99 |
# Study address |
105 |
100 |
study_street = models.CharField(max_length=64, blank=False) |
106 |
101 |
study_number = models.PositiveSmallIntegerField(blank=False) |
107 |
102 |
study_bus = models.PositiveSmallIntegerField() |
108 |
- | study_postal_code = models.PositiveSmallIntegerField(blank=False) |
+ |
103 |
study_postal_code = models.PositiveSmallIntegerField(blank=False) |
109 |
104 |
study_country = models.CharField(max_length=64, blank=False) |
110 |
105 |
study_telephone = models.CharField( |
111 |
106 |
max_length=64, |
112 |
107 |
help_text=_("The telephone number for the study address. Prefix 0 can be presented with the national call code in the system."), |
113 |
108 |
) |
114 |
109 |
study_cellphone = models.CharField( |
115 |
110 |
max_length=64, |
116 |
111 |
help_text=_("The cellphone number of the person. Prefix 0 can be presented with then national call code in the system."), |
117 |
112 |
) |
118 |
113 |
# Titularis address |
119 |
114 |
# XXX: These fields are only required if this differs from the user itself. |
120 |
115 |
titularis_street = models.CharField(max_length=64) |
121 |
- | titularis_number = models.PositiveSmallIntegerField() |
122 |
- | titularis_bus = models.PositiveSmallIntegerField() |
123 |
- | titularis_postal_code = models.PositiveSmallIntegerField() |
124 |
- | titularis_country = models.CharField(max_length=64) |
125 |
- | titularis_telephone = models.CharField( |
+ |
116 |
titularis_number = models.PositiveSmallIntegerField(null=True) |
+ |
117 |
titularis_bus = models.PositiveSmallIntegerField(null=True) |
+ |
118 |
titularis_postal_code = models.PositiveSmallIntegerField(null=True) |
+ |
119 |
titularis_country = models.CharField(max_length=64, null=True) |
+ |
120 |
titularis_telephone = models.CharField( |
126 |
121 |
max_length=64, |
127 |
122 |
help_text=_("The telephone number of the titularis. Prefix 0 can be presented with the national call code in the system."), |
128 |
123 |
) |
+ |
124 |
) |
129 |
125 |
|
130 |
126 |
# Financial details |
131 |
127 |
bank_account_number = models.CharField( |
132 |
128 |
max_length=34, # Max length of all IBAN account numbers |
133 |
129 |
validators=[validate_IBAN], |
134 |
130 |
help_text=_("The IBAN of this user. No spaces!"), |
135 |
131 |
) |
136 |
132 |
BIC = models.CharField( |
137 |
133 |
max_length=11, |
138 |
134 |
validators=[validate_BIC], |
139 |
135 |
help_text=_("The BIC of this user's bank."), |
140 |
136 |
) |
141 |
137 |
|
142 |
138 |
""" NOTE: What about all the other features that should be in the administration? |
143 |
139 |
While there are a lot of things to cover, as of now, I have no way to know which |
144 |
140 |
ones are still valid, which are deprecated, and so on... |
145 |
141 |
Additionally, every feature may have a different set of requirements, data, |
146 |
142 |
and it's very likely making an abstract class won't do any good. Thus I have |
147 |
143 |
decided to postpone making additional tables and forms for these features until |
148 |
144 |
I have clearance about certain aspects. """ |
149 |
145 |
|
150 |
146 |
class Curriculum(models.Model): |
151 |
147 |
""" The curriculum of a particular student. |
152 |
148 |
Every academic year, a student has to hand in a curriculum (s)he wishes to |
153 |
149 |
follow. This is then reviewed by a committee. A curriculum exists of all the |
154 |
150 |
courses one wants to partake in in a certain year. """ |
155 |
151 |
student = models.ForeignKey( |
156 |
152 |
"User", |
157 |
153 |
on_delete=models.CASCADE, |
158 |
154 |
limit_choices_to={'is_student': True}, |
159 |
155 |
null=False, |
160 |
156 |
editable=False, |
161 |
- | unique_for_year="year", # Only 1 curriculum per year |
+ |
157 |
unique_for_year="year", # Only 1 curriculum per year |
162 |
158 |
) |
163 |
159 |
year = models.DateField( |
164 |
160 |
auto_now_add=True, |
165 |
161 |
db_index=True, |
166 |
162 |
help_text=_("The academic year for which this curriculum is."), |
167 |
163 |
) |
168 |
164 |
last_modified = models.DateTimeField( |
169 |
165 |
auto_now=True, |
170 |
166 |
help_text=_("The last timestamp that this was updated."), |
171 |
167 |
) |
172 |
168 |
courses = models.ManyToManyField( |
173 |
169 |
"courses.Course", |
174 |
170 |
null=False, |
175 |
171 |
help_text=_("All the courses included in this curriculum."), |
176 |
172 |
) |
177 |
173 |
approved = models.NullBooleanField( |
178 |
174 |
default=None, |
179 |
175 |
help_text=_("Indicates if this curriculum has been approved. If true, " |
180 |
176 |
"that means the responsible committee has reviewed and " |
181 |
177 |
"approved the student for this curriculum. False otherwise. " |
182 |
178 |
"If review is still pending, the value is NULL. Modifying " |
183 |
179 |
"the curriculum implies this setting is set to NULL again."), |
184 |
180 |
) |
185 |
181 |
note = models.TextField( |
186 |
182 |
blank=True, |
187 |
183 |
help_text=_("Additional notes regarding this curriculum. This has " |
188 |
184 |
"multiple uses. For the student, it is used to clarify " |
189 |
185 |
"any questions, or to motivate why (s)he wants to take a " |
190 |
186 |
"course for which the requirements were not met. " |
191 |
187 |
"The reviewing committee can use this field to argument " |
192 |
188 |
"their decision, especially for when the curriculum is " |
193 |
189 |
"denied."), |
194 |
190 |
) |
195 |
191 |
|
196 |
192 |
def curriculum_type(self): |
197 |
193 |
""" Returns the type of this curriculum. At the moment, this is |
198 |
194 |
either a standard programme, or an individualized programme. """ |
199 |
195 |
# Currently: A standard programme means: All courses are from the |
200 |
196 |
# same study, ánd from the same year. Additionally, all courses |
201 |
197 |
# from that year must've been taken. |
202 |
198 |
# FIXME: Need a way to determine what is the standard programme. |
203 |
199 |
# If not possible, make this a charfield with options or something |
204 |
200 |
pass |
205 |
201 |
|
206 |
202 |
def __str__(self): |
207 |
203 |
year = self.year.year |
208 |
204 |
if self.year.month < 7: |
209 |
205 |
return str(self.student) +" | "+ str(year-1) +"-"+ str(year) |
210 |
206 |
else: |
211 |
207 |
return str(self.student) +" | "+ str(year) +"-"+ str(year+1) |
212 |
208 |
|
213 |
209 |
|
214 |
210 |
class CourseResult(models.Model): |
215 |
211 |
""" A student has to obtain a certain course result. These are stored here, |
216 |
212 |
together with all the appropriate information. """ |
217 |
213 |
# TODO: Validate that a course programme for a student can only be made once per year for each course, if possible. |
218 |
214 |
CRED = _("Credit acquired") |
219 |
215 |
FAIL = _("Credit not acquired") |
220 |
216 |
TLRD = _("Tolerated") |
221 |
217 |
ITLD = _("Tolerance used") |
222 |
218 |
# Possible to add more in the future |
223 |
219 |
|
224 |
220 |
student = models.ForeignKey( |
225 |
221 |
"User", |
226 |
222 |
on_delete=models.CASCADE, |
227 |
223 |
limit_choices_to={'is_student': True}, |
228 |
224 |
null=False, |
229 |
225 |
) |
230 |
226 |
course_programme = models.ForeignKey( |
231 |
227 |
"courses.ProgrammeInformation", |
232 |
228 |
on_delete=models.PROTECT, |
233 |
229 |
null=False, |
234 |
230 |
) |
235 |
231 |
released = models.DateField( |
236 |
232 |
auto_now=True, |
237 |
233 |
help_text=_("The date that this result was last updated."), |
238 |
234 |
) |
239 |
235 |
first_score = models.PositiveSmallIntegerField( |
240 |
236 |
null=True, # It's possible a score does not exist. |
241 |
237 |
validators=[MaxValueValidator( |
242 |
238 |
20, |
243 |
239 |
_("The score mustn't be higher than 20."), |
244 |
240 |
)], |
245 |
241 |
) |
246 |
242 |
second_score = models.PositiveSmallIntegerField( |
247 |
243 |
null=True, |
248 |
244 |
validators=[MaxValueValidator( |
249 |
245 |
20, |
250 |
246 |
_("The score mustn't be higher than 20."), |
251 |
247 |
)], |
252 |
248 |
) |
253 |
249 |
result = models.CharField( |
254 |
250 |
max_length=10, |
255 |
251 |
choices = ( |
256 |
252 |
("CRED", CRED), |
257 |
253 |
("FAIL", FAIL), |
258 |
254 |
("TLRD", TLRD), |
259 |
255 |
("ITLD", ITLD), |
260 |
256 |
), |
261 |
257 |
blank=False, |
262 |
258 |
help_text=_("The final result this record constitutes."), |
263 |
259 |
) |
264 |
260 |
|
265 |
261 |
def __str__(self): |
266 |
262 |
stdnum = str(self.student.number) |
267 |
263 |
result = self.result |
268 |
264 |
if result == "CRED": |
269 |
265 |
if self.first_score < 10: |
270 |
266 |
result = "C" + self.first_score + "1" |
271 |
267 |
else: |
272 |
268 |
result = "C" + self.second_score + "2" |
273 |
269 |
course = str(self.course_programme.course) |
274 |
270 |
return stdnum +" ("+ result +") | "+ course |
275 |
271 |
|
276 |
272 |
class PreRegistration(models.Model): |
277 |
273 |
""" At the beginning of the new academic year, students can register |
278 |
274 |
themselves at the university. Online, they can do a preregistration already. |
279 |
275 |
These records are stored here and can later be retrieved for the actual |
280 |
276 |
registration process. |
281 |
277 |
Note: The current system in use at Hasselt University provides a password system. |
282 |
278 |
That will be eliminated here. Just make sure that the entered details are correct. |
283 |
279 |
Should there be an error, and the same email address is used to update something, |
284 |
280 |
a mail will be sent to that address to verify this was a genuine update.""" |
285 |
281 |
created = models.DateField(auto_now_add=True) |
286 |
282 |
first_name = models.CharField(max_length=64, blank=False, help_text=_("Your first name.")) |
287 |
283 |
last_name = models.CharField(max_length=64, blank=False, help_text=_("Your last name.")) |
288 |
284 |
additional_names = models.CharField(max_length=64, blank=True, help_text=_("Any additional names.")) |
289 |
285 |
title = models.CharField( |
290 |
286 |
max_length=64, |
291 |
287 |
blank=True, |
292 |
288 |
help_text=_("Any additional titles, prefixes, ..."), |
293 |
289 |
) |
294 |
290 |
DOB = models.DateField( |
295 |
291 |
blank=False, |
296 |
292 |
editable=False, |
297 |
- | help_text=_("Your date of birth."), |
+ |
293 |
help_text=_("Your date of birth."), |
298 |
294 |
) |
299 |
295 |
POB = models.CharField( |
300 |
296 |
max_length=64, |
301 |
297 |
blank=False, |
302 |
298 |
editable=False, |
303 |
- | help_text=_("The place you were born."), |
+ |
299 |
help_text=_("The place you were born."), |
304 |
300 |
) |
305 |
301 |
nationality = models.CharField( |
306 |
302 |
max_length=64, |
307 |
303 |
blank=False, |
308 |
304 |
help_text=_("Your current nationality."), |
309 |
305 |
) |
310 |
306 |
national_registry_number = models.BigIntegerField( |
311 |
307 |
null=True, |
312 |
308 |
help_text=_("If you have one, your national registry number."), |
313 |
309 |
) |
314 |
310 |
civil_status = models.CharField( |
315 |
311 |
max_length=32, |
316 |
312 |
choices = ( |
317 |
313 |
("Single", _("Single")), |
318 |
314 |
("Married", _("Married")), |
319 |
315 |
("Divorced", _("Divorced")), |
320 |
316 |
("Widowed", _("Widowed")), |
321 |
317 |
("Partnership", _("Partnership")), |
322 |
318 |
), |
323 |
319 |
blank=False, |
324 |
320 |
# There may be more; consult http://www.aantrekkingskracht.com/trefwoord/burgerlijke-staat |
325 |
321 |
# for more information. |
326 |
322 |
help_text=_("Your civil/marital status."), |
327 |
323 |
) |
328 |
324 |
email = models.EmailField( |
329 |
325 |
blank=False, |
330 |
326 |
unique=True, |
331 |
327 |
help_text=_("The e-mail address we will use to communicate until your actual registration."), |
332 |
328 |
) |
333 |
329 |
study = models.ForeignKey( |
334 |
330 |
"courses.Study", |
335 |
331 |
on_delete=models.PROTECT, |
336 |
332 |
null=False, |
337 |
333 |
help_text=_("The study you wish to follow. Be sure to provide all legal" |
338 |
334 |
"documents that are required for this study with this " |
339 |
335 |
"application, or bring them with you to the final registration."), |
340 |
336 |
) |
341 |
337 |
study_type = models.CharField( |
342 |
338 |
max_length=32, |
343 |
339 |
choices = ( |
344 |
340 |
("Diplom contract", _("Diplom contract")), |
345 |
341 |
("Exam contract", _("Exam contract")), |
346 |
342 |
("Credit contract", _("Credit contract")), |
347 |
343 |
), |
348 |
344 |
blank=False, |
349 |
345 |
help_text=_("The type of study contract you wish to follow."), |
350 |
346 |
) |
351 |
347 |
document = models.FileField( |
352 |
348 |
upload_to="pre-enrollment/%Y", |
353 |
349 |
help_text=_("Any legal documents regarding your enrollment."), |
354 |
350 |
) |
355 |
351 |
# XXX: If the database in production is PostgreSQL, comment document, and |
356 |
352 |
# uncomment the next column. |
357 |
353 |
"""documents = models.ArrayField( |
358 |
354 |
models.FileField(upload_to="pre-enrollment/%Y"), |
359 |
355 |
help_text=_("Any legal documents regarding your enrollment."), |
360 |
356 |
)""" |
361 |
357 |
|
362 |
358 |
def __str__(self): |
363 |
359 |
name = self.last_name +" "+ self.first_name |
364 |
360 |
dob = self.DOB.strftime("%d/%m/%Y") |
365 |
361 |
return name +" | "+ dob |
366 |
362 |
|
367 |
363 |
|
368 |
364 |
# Planning and organization related tables |
369 |
365 |
class Room(models.Model): |
370 |
366 |
""" Represents a room in the university. |
371 |
367 |
Rooms can have a number of properties, which are stored in the database. |
372 |
368 |
""" |
373 |
369 |
# Types of rooms |
374 |
370 |
LABORATORY = _("Laboratory") # Chemistry/Physics equipped rooms |
375 |
371 |
CLASS_ROOM = _("Class room") # Simple class rooms |
376 |
372 |
AUDITORIUM = _("Auditorium") # Large rooms with ample seating and equipment for lectures |
377 |
373 |
PC_ROOM = _("PC room" ) # Rooms equipped for executing PC related tasks |
378 |
374 |
PUBLIC_ROOM= _("Public room") # Restaurants, restrooms, ... general public spaces |
379 |
375 |
OFFICE = _("Office" ) # Private offices for staff |
380 |
376 |
PRIVATE_ROOM = _("Private room") # Rooms accessible for a limited public; cleaning cupboards, kitchens, ... |
381 |
377 |
WORKSHOP = _("Workshop" ) # Rooms with hardware equipment to build and work on materials |
382 |
378 |
OTHER = _("Other" ) # Rooms that do not fit in any other category |
383 |
379 |
|
384 |
380 |
|
385 |
381 |
name = models.CharField( |
386 |
382 |
max_length=20, |
387 |
383 |
primary_key=True, |
388 |
384 |
blank=False, |
389 |
385 |
help_text=_("The name of this room. If more appropriate, this can be the colloquial name."), |
390 |
386 |
) |
391 |
387 |
seats = models.PositiveSmallIntegerField( |
392 |
388 |
help_text=_("The amount of available seats in this room. This can be handy for exams for example."), |
393 |
389 |
) |
394 |
390 |
wheelchair_accessible = models.BooleanField(default=True) |
395 |
391 |
exams_equipped = models.BooleanField( |
396 |
392 |
default=True, |
397 |
393 |
help_text=_("Indicates if exams can reasonably be held in this room."), |
398 |
394 |
) |
399 |
395 |
computers_available = models.PositiveSmallIntegerField( |
400 |
396 |
default=False, |
401 |
397 |
help_text=_("Indicates how many computers are available in this room."), |
402 |
398 |
) |
403 |
399 |
projector_available = models.BooleanField( |
404 |
400 |
default=False, |
405 |
401 |
help_text=_("Indicates if a projector is available at this room."), |
406 |
402 |
) |
407 |
403 |
blackboards_available = models.PositiveSmallIntegerField( |
408 |
404 |
help_text=_("The amount of blackboards available in this room."), |
409 |
405 |
) |
410 |
406 |
whiteboards_available = models.PositiveSmallIntegerField( |
411 |
407 |
help_text=_("The amount of whiteboards available in this room."), |
412 |
408 |
) |
413 |
409 |
category = models.CharField( |
414 |
410 |
max_length=16, |
415 |
411 |
blank=False, |
416 |
412 |
choices = ( |
417 |
413 |
("LABORATORY", LABORATORY), |
418 |
414 |
("CLASS_ROOM", CLASS_ROOM), |
419 |
415 |
("AUDITORIUM", AUDITORIUM), |
420 |
416 |
("PC_ROOM", PC_ROOM), |
421 |
417 |
("PUBLIC_ROOM", PUBLIC_ROOM), |
422 |
418 |
("OFFICE", OFFICE), |
423 |
419 |
("PRIVATE_ROOM", PRIVATE_ROOM), |
424 |
420 |
("WORKSHOP", WORKSHOP), |
425 |
421 |
("OTHER", OTHER), |
426 |
422 |
), |
427 |
423 |
help_text=_("The category that best suits the character of this room."), |
428 |
424 |
) |
429 |
425 |
reservable = models.BooleanField( |
430 |
426 |
default=True, |
431 |
427 |
help_text=_("Indicates if this room can be reserved for something."), |
432 |
428 |
) |
433 |
429 |
note = models.TextField( |
434 |
430 |
blank=True, |
435 |
431 |
help_text=_("If some additional info is required for this room, like a " |
436 |
432 |
"characteristic property (e.g. 'Usually occupied by 2BACH " |
437 |
433 |
"informatics'), state it here."), |
438 |
434 |
) |
439 |
435 |
# TODO: Add a campus/building field or not? |
440 |
436 |
|
441 |
437 |
def reservation_possible(self, begin, end, seats=None): |
442 |
438 |
""" Returns a boolean indicating if reservating during the given time |
443 |
439 |
is possible. If the begin overlaps with a reservation's end or vice versa, |
444 |
440 |
this is regarded as possible. |
445 |
441 |
Takes seats as optional argument. If not specified, it is assumed the entire |
446 |
442 |
room has to be reserved. """ |
447 |
443 |
if self.reservable is False: |
448 |
444 |
return False |
449 |
445 |
if seats is not None and seats < 0: raise ValueError(_("seats ∈ ℕ")) |
450 |
446 |
|
451 |
447 |
reservations = RoomReservation.objects.filter(room=self) |
452 |
448 |
for reservation in reservations: |
453 |
449 |
if reservation.end <= begin or reservation.begin >= end: |
454 |
450 |
continue # Can be trivially skipped, no overlap here |
455 |
451 |
elif seats is None or reservation.seats is None: |
456 |
452 |
return False # The whole room cannot be reserved -> False |
457 |
453 |
elif seats + reservation.seats > self.seats: |
458 |
454 |
return False # Total amount of seats exceeds the available amount -> False |
459 |
455 |
return True # No overlappings found -> True |
460 |
456 |
|
461 |
457 |
def __str__(self): |
462 |
458 |
return self.name |
463 |
459 |
|
464 |
460 |
class RoomReservation(models.Model): |
465 |
461 |
""" Rooms are to be reserved from time to time. They can be reserved |
466 |
462 |
by externals, for something else, and whatnot. That is stored in this table. |
467 |
463 |
""" |
468 |
464 |
room = models.ForeignKey( |
469 |
465 |
"Room", |
470 |
466 |
on_delete=models.CASCADE, |
471 |
467 |
null=False, |
472 |
468 |
editable=False, |
473 |
- | db_index=True, |
+ |
469 |
db_index=True, |
474 |
470 |
limit_choices_to={"reservable": True}, |
475 |
471 |
help_text=_("The room that is being reserved at this point."), |
476 |
472 |
) |
477 |
473 |
reservator = models.ForeignKey( |
478 |
474 |
"User", |
479 |
475 |
on_delete=models.CASCADE, |
480 |
476 |
null=False, |
481 |
477 |
editable=False, |
482 |
- | help_text=_("The person that made the reservation (and thus responsible)."), |
+ |
478 |
help_text=_("The person that made the reservation (and thus responsible)."), |
483 |
479 |
) |
484 |
480 |
timestamp = models.DateTimeField(auto_now_add=True) |
485 |
481 |
start_time = models.DateTimeField( |
486 |
482 |
null=False, |
487 |
483 |
help_text=_("The time that this reservation starts."), |
488 |
484 |
) |
489 |
485 |
end_time = models.DateTimeField( |
490 |
486 |
null=False, |
491 |
487 |
help_text=_("The time that this reservation ends."), |
492 |
488 |
) |
493 |
489 |
seats = models.PositiveSmallIntegerField( |
494 |
490 |
null=True, |
495 |
491 |
help_text=_("Indicates how many seats are required. If this is left null, " |
496 |
492 |
"it is assumed the entire room has to be reserved."), |
497 |
493 |
) |
498 |
494 |
reason = models.CharField( |
499 |
495 |
max_length=64, |
500 |
496 |
blank=True, |
501 |
497 |
help_text=_("The reason for this reservation, if useful."), |
502 |
498 |
) |
503 |
499 |
note = models.TextField( |
504 |
500 |
blank=True, |
505 |
501 |
help_text=_("If some additional info is required for this reservation, " |
506 |
502 |
"state it here."), |
507 |
503 |
) |
508 |
504 |
|
509 |
505 |
def __str__(self): |
510 |
506 |
start = self.start_time.strftime("%H:%M") |
511 |
507 |
end = self.end_time.strftime("%H:%M") |
512 |
508 |
return str(self.room) +" | "+ start +"-"+ end |
513 |
509 |
|
514 |
510 |
class Degree(models.Model): |
515 |
511 |
""" Contains all degrees that were achieved at this university. |
516 |
512 |
There are no foreign keys in this field. This allows system |
517 |
513 |
administrators to safely remove accounts from alumni, without |
518 |
514 |
the risk of breaking referential integrity or accidentally removing |
519 |
515 |
degrees. |
520 |
516 |
While keeping some fields editable that look like they shouldn't be |
521 |
517 |
(e.g. first_name), this makes it possible for alumni to have a name change |
522 |
518 |
later in their life, and still being able to get a copy of their degree. """ |
523 |
519 |
""" Reason for an ID field for every degree: |
524 |
520 |
This system allows for employers to verify that a certain applicant has indeed, |
525 |
521 |
achieved the degrees (s)he proclaims to have. Because of privacy concerns, |
526 |
522 |
a university cannot disclose information about alumni. |
527 |
523 |
That's where the degree ID comes in. This ID can be printed on all future |
528 |
524 |
degrees. The employer can then visit the university's website, and simply |
529 |
525 |
enter the ID. The website will then simply print what study is attached to |
530 |
526 |
this degree, but not disclose names or anything identifiable. This strikes |
531 |
527 |
thé perfect balance between (easy and digital) degree verification for employers, and maintaining |
532 |
528 |
alumni privacy to the highest extent possible. """ |
533 |
529 |
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) |
534 |
530 |
first_name = models.CharField( |
535 |
531 |
max_length=64, |
536 |
532 |
blank=False, |
537 |
533 |
) |
538 |
534 |
last_name = models.CharField( |
539 |
535 |
max_length=64, |
540 |
536 |
blank=False, |
541 |
537 |
) |
542 |
538 |
additional_names = models.CharField( |
543 |
539 |
max_length=64, |
544 |
540 |
) |
+ |
541 |
) |
545 |
542 |
DOB = models.DateField(editable=False, null=False) # This can't be changed, of course |
546 |
- | POB = models.CharField( |
+ |
543 |
POB = models.CharField( |
547 |
544 |
max_length=64, |
548 |
545 |
blank=False, |
549 |
546 |
editable=False, |
550 |
- | ) |
+ |
547 |
) |
551 |
548 |
# The study also has to be a charfield, because if a study is removed, |
552 |
549 |
# The information will be lost. |
553 |
550 |
study = models.CharField( |
554 |
551 |
max_length=64, |
555 |
552 |
blank=False, |
556 |
553 |
editable=False, |
557 |
- | ) |
+ |
554 |
) |
558 |
555 |
achieved = models.DateField(editable=False, null=False) |
559 |
- | user = models.ForeignKey( |
+ |
556 |
user = models.ForeignKey( |
560 |
557 |
"User", |
561 |
558 |
on_delete=models.SET_NULL, |
562 |
559 |
null=True, |
563 |
560 |
help_text=_("The person that achieved this degree, if (s)he still has " |
564 |
561 |
"an account at this university. If the account is deleted " |
565 |
562 |
"at a later date, this field will be set to NULL, but the " |
566 |
563 |
"other fields will be retained."), |
567 |
564 |
) |
568 |
565 |
|
569 |
566 |
def __str__(self): |
570 |
567 |
return self.first_name +" "+ self.last_name +" | "+ self.study |
571 |
568 |
agora/forms.py ¶
4 additions and 0 deletions.
agora/migrations/0003_auto_20171121_1906.py ¶
28 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 |
('agora', '0002_auto_20171118_2203'), |
+ |
10 |
] |
+ |
11 |
|
+ |
12 |
operations = [ |
+ |
13 |
migrations.RemoveField( |
+ |
14 |
model_name='accountsettings', |
+ |
15 |
name='id', |
+ |
16 |
), |
+ |
17 |
migrations.AlterField( |
+ |
18 |
model_name='accountsettings', |
+ |
19 |
name='account', |
+ |
20 |
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='agora.Account'), |
+ |
21 |
), |
+ |
22 |
migrations.AlterField( |
+ |
23 |
model_name='post', |
+ |
24 |
name='response_to', |
+ |
25 |
field=models.ForeignKey(editable=False, help_text='The post to which this was a response, if applicable.', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='responses', to='agora.Post'), |
+ |
26 |
), |
+ |
27 |
] |
+ |
28 |
agora/migrations/0004_auto_20171121_2018.py ¶
94 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 |
('agora', '0003_auto_20171121_1906'), |
+ |
10 |
] |
+ |
11 |
|
+ |
12 |
operations = [ |
+ |
13 |
migrations.AlterField( |
+ |
14 |
model_name='accountcollection', |
+ |
15 |
name='account', |
+ |
16 |
field=models.ForeignKey(help_text='The account that created this collection.', on_delete=django.db.models.deletion.CASCADE, to='agora.Account'), |
+ |
17 |
), |
+ |
18 |
migrations.AlterField( |
+ |
19 |
model_name='filepost', |
+ |
20 |
name='file', |
+ |
21 |
field=models.FileField(help_text='The file you wish to share.', upload_to='agora/posts/%Y/%m/%d/'), |
+ |
22 |
), |
+ |
23 |
migrations.AlterField( |
+ |
24 |
model_name='groupchat', |
+ |
25 |
name='group', |
+ |
26 |
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='agora.Group'), |
+ |
27 |
), |
+ |
28 |
migrations.AlterField( |
+ |
29 |
model_name='groupinvite', |
+ |
30 |
name='group', |
+ |
31 |
field=models.ForeignKey(help_text='The group for which this invitation is.', on_delete=django.db.models.deletion.CASCADE, to='agora.Group'), |
+ |
32 |
), |
+ |
33 |
migrations.AlterField( |
+ |
34 |
model_name='groupinvite', |
+ |
35 |
name='invitee', |
+ |
36 |
field=models.ForeignKey(help_text='The account which will receive the invitation.', on_delete=django.db.models.deletion.CASCADE, related_name='invitee', to='agora.Account'), |
+ |
37 |
), |
+ |
38 |
migrations.AlterField( |
+ |
39 |
model_name='groupinvite', |
+ |
40 |
name='inviter', |
+ |
41 |
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='agora.Account'), |
+ |
42 |
), |
+ |
43 |
migrations.AlterField( |
+ |
44 |
model_name='message', |
+ |
45 |
name='sender', |
+ |
46 |
field=models.ForeignKey(help_text='The account that sent this message.', on_delete=django.db.models.deletion.CASCADE, to='agora.Account'), |
+ |
47 |
), |
+ |
48 |
migrations.AlterField( |
+ |
49 |
model_name='post', |
+ |
50 |
name='author', |
+ |
51 |
field=models.ForeignKey(help_text='The authoring account of this post.', on_delete=django.db.models.deletion.CASCADE, to='agora.Account'), |
+ |
52 |
), |
+ |
53 |
migrations.AlterField( |
+ |
54 |
model_name='post', |
+ |
55 |
name='placed_on', |
+ |
56 |
field=models.ForeignKey(help_text='The page on which this post was placed.', on_delete=django.db.models.deletion.CASCADE, to='agora.Page'), |
+ |
57 |
), |
+ |
58 |
migrations.AlterField( |
+ |
59 |
model_name='post', |
+ |
60 |
name='response_to', |
+ |
61 |
field=models.ForeignKey(help_text='The post to which this was a response, if applicable.', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='responses', to='agora.Post'), |
+ |
62 |
), |
+ |
63 |
migrations.AlterField( |
+ |
64 |
model_name='privatechat', |
+ |
65 |
name='account1', |
+ |
66 |
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='agora.Account'), |
+ |
67 |
), |
+ |
68 |
migrations.AlterField( |
+ |
69 |
model_name='privatechat', |
+ |
70 |
name='account2', |
+ |
71 |
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='correspondent', to='agora.Account'), |
+ |
72 |
), |
+ |
73 |
migrations.AlterField( |
+ |
74 |
model_name='sharedfile', |
+ |
75 |
name='file', |
+ |
76 |
field=models.FileField(help_text='The file you want to share.', upload_to='agora/chat/%Y/%m/%d/'), |
+ |
77 |
), |
+ |
78 |
migrations.AlterField( |
+ |
79 |
model_name='sharedfile', |
+ |
80 |
name='uploader', |
+ |
81 |
field=models.ForeignKey(help_text='The account that uploaded this file.', on_delete=django.db.models.deletion.CASCADE, to='agora.Account'), |
+ |
82 |
), |
+ |
83 |
migrations.AlterField( |
+ |
84 |
model_name='vote', |
+ |
85 |
name='post', |
+ |
86 |
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='agora.Post'), |
+ |
87 |
), |
+ |
88 |
migrations.AlterField( |
+ |
89 |
model_name='vote', |
+ |
90 |
name='voter', |
+ |
91 |
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='agora.Account'), |
+ |
92 |
), |
+ |
93 |
] |
+ |
94 |
agora/models.py ¶
22 additions and 16 deletions.
View changes Hide changes
1 |
1 |
from django.utils.translation import ugettext_lazy as _ |
2 |
2 |
from joeni import constants |
3 |
3 |
|
4 |
4 |
def validate_hex_color(value): |
5 |
5 |
pass # TODO |
6 |
6 |
|
7 |
7 |
class Account(models.Model): |
8 |
8 |
user = models.OneToOneField( |
9 |
9 |
'administration.User', |
10 |
10 |
on_delete=models.CASCADE, |
11 |
11 |
primary_key=True, |
12 |
12 |
) |
13 |
13 |
alias = models.CharField(max_length=64, unique=True) |
14 |
14 |
|
15 |
15 |
def __str__(self): |
16 |
16 |
return self.alias |
17 |
17 |
|
18 |
18 |
def account_user_directory(instance, filename): |
19 |
19 |
return '{0}/account/settings/{1}'.format(instace.account.alias, filename) |
20 |
20 |
|
21 |
21 |
class AccountSettings(models.Model): |
22 |
22 |
account = models.OneToOneField( |
23 |
23 |
'Account', |
24 |
24 |
on_delete=models.CASCADE, |
25 |
25 |
) |
+ |
26 |
) |
26 |
27 |
# TODO: Build validator for primary_color to make sure what is given is a |
27 |
28 |
# valid hexadecimal RGB value. |
28 |
29 |
color = models.CharField( |
29 |
30 |
max_length=6, |
30 |
31 |
help_text=_("The hexadecimal code of the color for this account."), |
31 |
32 |
default = constants.COLORS["UHasselt default"], |
32 |
33 |
blank=False, |
33 |
34 |
validators=[validate_hex_color], |
34 |
35 |
) |
35 |
36 |
account_page_banner = models.ImageField( # Requires the Pillow library! |
36 |
37 |
upload_to=account_user_directory, |
37 |
38 |
help_text=_("The banner image to be shown on this account's homepage."), |
38 |
39 |
) |
39 |
40 |
avatar = models.ImageField( |
40 |
41 |
upload_to=account_user_directory, |
41 |
42 |
help_text=_("The avatar image of this account."), |
42 |
43 |
) |
43 |
44 |
|
44 |
45 |
def __str__(self): |
45 |
46 |
return str(self.account) |
46 |
47 |
|
47 |
48 |
class Post(models.Model): |
48 |
49 |
timestamp = models.DateTimeField(auto_now_add=True) |
49 |
50 |
title = models.CharField( |
50 |
51 |
max_length=64, |
51 |
52 |
blank=True, |
52 |
53 |
help_text=_("The title for this post."), |
53 |
54 |
) |
54 |
55 |
text = models.TextField( |
55 |
56 |
blank=True, |
56 |
57 |
help_text=_("A text message for this post. May be left blank."), |
57 |
58 |
) |
58 |
59 |
author = models.ForeignKey( |
59 |
60 |
"Account", |
60 |
61 |
on_delete=models.CASCADE, |
61 |
62 |
null=False, # There must be an author |
62 |
63 |
editable=False, # It makes no sense to change the author after creation |
63 |
- | help_text=_("The authoring account of this post."), |
+ |
64 |
help_text=_("The authoring account of this post."), |
64 |
65 |
) |
65 |
66 |
response_to = models.ForeignKey( |
66 |
67 |
"self", |
67 |
68 |
on_delete=models.CASCADE, |
68 |
69 |
null=True, # If this is null, this post is not a response, but a beginning post |
69 |
70 |
editable=False, # This cannot be changed after creation, wouldn't make sense |
70 |
- | help_text=_("The post to which this was a response, if applicable."), |
+ |
71 |
related_name="responses", |
+ |
72 |
help_text=_("The post to which this was a response, if applicable."), |
71 |
73 |
) |
72 |
74 |
placed_on = models.ForeignKey( |
73 |
75 |
"Page", |
74 |
76 |
on_delete=models.CASCADE, |
75 |
77 |
null=False, |
76 |
78 |
editable=False, |
77 |
- | help_text=_("The page on which this post was placed."), |
+ |
79 |
help_text=_("The page on which this post was placed."), |
78 |
80 |
) |
79 |
81 |
# Voting fields |
80 |
82 |
allow_votes = models.BooleanField( |
81 |
83 |
default=True, |
82 |
84 |
help_text=_("Decide whether to allow voting or disable it for this post."), |
83 |
85 |
) |
84 |
86 |
allow_responses = models.BooleanField( |
85 |
87 |
default=True, |
86 |
88 |
help_text=_("Decide if other people can respond to this post or not. " |
87 |
89 |
"This does not influence what people allow on their posts."), |
88 |
90 |
) |
89 |
91 |
|
90 |
92 |
def __str__(self): |
+ |
93 |
""" Return how many votes this post has. """ |
+ |
94 |
return len(Vote.objects.filter(post=self)) |
+ |
95 |
|
+ |
96 |
def __str__(self): |
91 |
97 |
return str(self.timestamp) + " | " + str(self.author) |
92 |
98 |
# TODO Add a way to attach geographical data to a post, which could |
93 |
99 |
# then be used with OpenStreetMap or something |
94 |
100 |
|
95 |
101 |
class FilePost(Post): |
96 |
102 |
""" A special type of Post, which has a file linked with it. |
97 |
103 |
The poster can specify how to treat this file. """ |
98 |
104 |
image = _("Image") |
99 |
105 |
video = _("Video") |
100 |
106 |
music = _("Sound") |
101 |
107 |
text = _("Text" ) |
102 |
108 |
other = _("Other") |
103 |
109 |
file = models.FileField( |
104 |
110 |
upload_to="agora/posts/%Y/%m/%d/", |
105 |
111 |
null=False, |
106 |
112 |
editable=False, |
107 |
- | help_text=_("The file you wish to share."), |
+ |
113 |
help_text=_("The file you wish to share."), |
108 |
114 |
) |
109 |
115 |
file_type = models.CharField( |
110 |
116 |
max_length=16, |
111 |
117 |
blank=False, |
112 |
118 |
choices = ( |
113 |
119 |
('image', image), |
114 |
120 |
('video', video), |
115 |
121 |
('music', music), |
116 |
122 |
('text' , text ), |
117 |
123 |
('other', other), |
118 |
124 |
), |
119 |
125 |
help_text=_("How this file should be seen as."), |
120 |
126 |
) |
121 |
127 |
|
122 |
128 |
class Page(models.Model): |
123 |
129 |
""" In the university, people can create pages for everything they want and |
124 |
130 |
then some. These pages are put in the database through this table. """ |
125 |
131 |
name = models.CharField( |
126 |
132 |
max_length=64, |
127 |
133 |
primary_key=True, |
128 |
134 |
blank=False, |
129 |
135 |
help_text=_("The name of this page."), |
130 |
136 |
) |
131 |
137 |
created = models.DateTimeField(auto_now_add=True) |
132 |
138 |
hidden = models.BooleanField( |
133 |
139 |
default=False, |
134 |
140 |
help_text=_("Determines if this page can be found without a direct link."), |
135 |
141 |
) |
136 |
142 |
main_content = models.TextField( |
137 |
143 |
blank=True, |
138 |
144 |
help_text=_("If you want to put some text on this page, " |
139 |
145 |
"you can put it here. You can use Orgmode-syntax to " |
140 |
146 |
"get as much out of your page as possible. While doing so, " |
141 |
147 |
"be aware of the limitations imposed by the code of conduct."), |
142 |
148 |
) |
143 |
149 |
public_posting = models.BooleanField( |
144 |
150 |
default=True, |
145 |
151 |
help_text=_("Determines if everyone can post on this page, or only the " |
146 |
152 |
"people that are linked with it. Know that if a post is made " |
147 |
153 |
"and responding is allowed, everyone can respond to that post."), |
148 |
154 |
) |
149 |
155 |
|
150 |
156 |
"""class Meta: |
151 |
157 |
abstract=True""" |
152 |
158 |
""" I've disabled the abstraction, because otherwise I can't relate a Post to a Page. """ |
153 |
159 |
|
154 |
160 |
class AccountPage(Page): |
155 |
161 |
""" Every account has its own homepage. This is that page. |
156 |
162 |
This page can only be edited by the account holder, or staff members. """ |
157 |
163 |
# TODO: Find a way to auto-create one of these every time a new account is created |
158 |
164 |
# TODO: Require that changes can only occur by either the account holder or staff |
159 |
165 |
account = models.OneToOneField( |
160 |
166 |
"Account", |
161 |
167 |
null=False, |
162 |
168 |
on_delete=models.CASCADE, |
163 |
169 |
) |
164 |
170 |
|
165 |
171 |
class GroupPage(Page): |
166 |
172 |
""" A page where a group can present itself to the university. |
167 |
173 |
This page can only be edited by group members or staff members. """ |
168 |
174 |
# TODO: Find a way to auto-create one of these every time a new group is created |
169 |
175 |
# TODO: Require that changes can only occur by either the group or staff |
170 |
176 |
group = models.ForeignKey( |
171 |
177 |
"Group", |
172 |
178 |
null=False, |
173 |
179 |
on_delete=models.CASCADE, |
174 |
180 |
) |
175 |
181 |
|
176 |
182 |
class CoursePage(Page): |
177 |
183 |
""" A page that serves as a course's main entry point. |
178 |
184 |
This page can only be edited by the course's educating team or staff members. """ |
179 |
185 |
# TODO: Find a way to auto-create one of these every time a new course is created |
180 |
186 |
# TODO: Require that changes can only occur by either the course team or staff |
181 |
187 |
course = models.OneToOneField( |
182 |
188 |
"courses.Course", |
183 |
189 |
null=False, |
184 |
190 |
on_delete=models.CASCADE, |
185 |
191 |
) |
186 |
192 |
|
187 |
193 |
class Group(models.Model): |
188 |
194 |
""" It is imperative that everyone can come together with other people. |
189 |
195 |
A Group record is the way to accomplish this. """ |
190 |
196 |
name = models.CharField( |
191 |
197 |
max_length=64, |
192 |
198 |
primary_key=True, # So be unique I'd say |
193 |
199 |
blank=False, |
194 |
200 |
help_text=_("The name of your group."), |
195 |
201 |
) |
196 |
202 |
color = models.CharField( |
197 |
203 |
max_length=6, |
198 |
204 |
help_text=_("The hexadecimal code of the color for this group."), |
199 |
205 |
default = constants.COLORS["UHasselt default"], |
200 |
206 |
blank=False, |
201 |
207 |
validators=[validate_hex_color], |
202 |
208 |
) |
203 |
209 |
members = models.ManyToManyField( |
204 |
210 |
"Account", |
205 |
211 |
help_text=_("The members of this group."), |
206 |
212 |
) |
207 |
213 |
invite_only = models.BooleanField( |
208 |
214 |
default=True, |
209 |
215 |
help_text=_("Determines if everyone can join this group, or if " |
210 |
216 |
"only members can invite others."), |
211 |
217 |
) |
212 |
218 |
private = models.BooleanField( |
213 |
219 |
default=True, |
214 |
220 |
help_text=_("Determines if this group is visible to non-members."), |
215 |
221 |
) |
216 |
222 |
|
217 |
223 |
def __str__(self): |
218 |
224 |
return self.name |
219 |
225 |
|
220 |
226 |
|
221 |
227 |
class AccountCollection(models.Model): |
222 |
228 |
""" Every account can make a collection in which (s)he can list accounts |
223 |
229 |
at his/her wish. This can be a collection of Friends, study collegues, |
224 |
230 |
project partners, and so on. |
225 |
231 |
Accounts that are in a certain collection are not notified of this. |
226 |
232 |
However, there is one exception: |
227 |
233 |
If both accounts have a collection named "Friends" (or the localized |
228 |
234 |
equivalent), and both feature each other in that collection, then |
229 |
235 |
this is shared between the two accounts. """ |
230 |
236 |
account = models.ForeignKey( |
231 |
237 |
"Account", |
232 |
238 |
null=False, |
233 |
239 |
editable=False, |
234 |
- | on_delete=models.CASCADE, |
+ |
240 |
on_delete=models.CASCADE, |
235 |
241 |
help_text=_("The account that created this collection."), |
236 |
242 |
) |
237 |
243 |
name = models.CharField( |
238 |
244 |
max_length=32, |
239 |
245 |
blank=False, |
240 |
246 |
help_text=_("The name of this collection."), |
241 |
247 |
) |
242 |
248 |
accounts = models.ManyToManyField( |
243 |
249 |
"Account", |
244 |
250 |
help_text=_("All accounts that are part of this collection."), |
245 |
251 |
related_name="in_collection", |
246 |
252 |
) |
247 |
253 |
visible_to_public = models.BooleanField( |
248 |
254 |
default=False, |
249 |
255 |
help_text=_("Make this collection visible to everybody."), |
250 |
256 |
) |
251 |
257 |
visible_to_collection = models.BooleanField( |
252 |
258 |
default=True, |
253 |
259 |
help_text=_("Make this collection visible to the accounts in this collection. Other collections are not affected by this."), |
254 |
260 |
) |
255 |
261 |
|
256 |
262 |
def __str__(self): |
257 |
263 |
return str(self.account) + " | " + self.name |
258 |
264 |
|
259 |
265 |
class Vote(models.Model): |
260 |
266 |
""" Accounts can vote on posts (using ▲, which is funny because UHasselt). |
261 |
267 |
These votes are registered in this table. """ |
262 |
268 |
voter = models.ForeignKey( |
263 |
269 |
"Account", |
264 |
270 |
null=False, |
265 |
271 |
editable=False, |
266 |
- | on_delete=models.CASCADE, |
+ |
272 |
on_delete=models.CASCADE, |
267 |
273 |
) |
268 |
274 |
post = models.ForeignKey( |
269 |
275 |
"Post", |
270 |
276 |
null=False, # Duh. |
271 |
277 |
editable=False, # Transferring votes doesn't make sense |
272 |
- | on_delete=models.CASCADE, |
+ |
278 |
on_delete=models.CASCADE, |
273 |
279 |
) |
274 |
280 |
|
275 |
281 |
class SharedFile(models.Model): |
276 |
282 |
""" Groups and people can share files with each other, through a chat system. |
277 |
283 |
These files are represented here. """ |
278 |
284 |
timestamp = models.DateTimeField(auto_now_add=True) |
279 |
285 |
file = models.FileField( |
280 |
286 |
upload_to="agora/chat/%Y/%m/%d/", |
281 |
287 |
null=False, |
282 |
288 |
editable=False, |
283 |
- | help_text=_("The file you want to share."), |
+ |
289 |
help_text=_("The file you want to share."), |
284 |
290 |
) |
285 |
291 |
uploader = models.ForeignKey( |
286 |
292 |
"Account", |
287 |
293 |
on_delete=models.CASCADE, |
288 |
294 |
null=False, |
289 |
295 |
editable=False, |
290 |
- | help_text=_("The account that uploaded this file."), |
+ |
296 |
help_text=_("The account that uploaded this file."), |
291 |
297 |
) |
292 |
298 |
# TODO __str__ ... or maybe not? |
293 |
299 |
|
294 |
300 |
class Message(models.Model): |
295 |
301 |
""" Everyone can communicate with someone else using private messages. |
296 |
302 |
These messages are recorded here. """ |
297 |
303 |
timestamp = models.DateTimeField(auto_now_add=True) |
298 |
304 |
text = models.TextField( |
299 |
305 |
blank=False, |
300 |
306 |
) |
301 |
307 |
sender = models.ForeignKey( |
302 |
308 |
"Account", |
303 |
309 |
on_delete=models.CASCADE, |
304 |
310 |
null=False, |
305 |
311 |
editable=False, |
306 |
- | help_text=_("The account that sent this message."), |
+ |
312 |
help_text=_("The account that sent this message."), |
307 |
313 |
) |
308 |
314 |
# TODO __str__ |
309 |
315 |
|
310 |
316 |
|
311 |
317 |
class Chat(models.Model): |
312 |
318 |
""" Chats can happen between a group, or between two people in private. |
313 |
319 |
These messages are connected to a particular chat. """ |
314 |
320 |
messages = models.ManyToManyField( |
315 |
321 |
"Message", |
316 |
322 |
help_text=_("All messages that were shared in this chat."), |
317 |
323 |
) |
318 |
324 |
shared_files = models.ManyToManyField( |
319 |
325 |
"SharedFile", |
320 |
326 |
help_text=_("The files that are shared in this chat."), |
321 |
327 |
) |
322 |
328 |
class Meta: |
323 |
329 |
abstract=True |
324 |
330 |
|
325 |
331 |
class GroupChat(Chat): |
326 |
332 |
""" Represents a chat of a group. """ |
327 |
333 |
group = models.ForeignKey( |
328 |
334 |
"Group", |
329 |
335 |
on_delete=models.CASCADE, |
330 |
336 |
null=False, |
331 |
337 |
editable=False, |
332 |
- | ) |
+ |
338 |
) |
333 |
339 |
|
334 |
340 |
def __str__(self): |
335 |
341 |
return str(self.group) |
336 |
342 |
|
337 |
343 |
class PrivateChat(Chat): |
338 |
344 |
""" Represents a private chat between two accounts. """ |
339 |
345 |
# FIXME: It's theoretically possible to start a chat by one person, and have |
340 |
346 |
# the other person start the same as well. Find a reliable way to block that. |
341 |
347 |
account1 = models.ForeignKey( |
342 |
348 |
"Account", |
343 |
349 |
on_delete=models.CASCADE, |
344 |
350 |
null=False, |
345 |
351 |
editable=False, |
346 |
- | ) |
+ |
352 |
) |
347 |
353 |
account2 = models.ForeignKey( |
348 |
354 |
"Account", |
349 |
355 |
on_delete=models.CASCADE, |
350 |
356 |
null=False, |
351 |
357 |
editable=False, |
352 |
- | related_name="correspondent", |
+ |
358 |
related_name="correspondent", |
353 |
359 |
) |
354 |
360 |
|
355 |
361 |
def __str__(self): |
356 |
362 |
return str(self.account1) +" - "+ str(self.account2) |
357 |
363 |
|
358 |
364 |
class GroupInvite(models.Model): |
359 |
365 |
""" Groups can send invites to other people to join the group, and this is |
360 |
366 |
not limited to private groups. Group invitations are stored here, as well as their outcome. """ |
361 |
367 |
inviter = models.ForeignKey( |
362 |
368 |
"Account", |
363 |
369 |
on_delete=models.CASCADE, |
364 |
370 |
null=False, |
365 |
371 |
editable=False, |
366 |
- | ) |
+ |
372 |
) |
367 |
373 |
invitee = models.ForeignKey( |
368 |
374 |
"Account", |
369 |
375 |
on_delete=models.CASCADE, |
370 |
376 |
null=False, |
371 |
377 |
editable=False, |
372 |
- | help_text=_("The account which will receive the invitation."), |
+ |
378 |
help_text=_("The account which will receive the invitation."), |
373 |
379 |
related_name="invitee", |
374 |
380 |
) |
375 |
381 |
group = models.ForeignKey( |
376 |
382 |
"Group", |
377 |
383 |
on_delete=models.CASCADE, |
378 |
384 |
null=False, |
379 |
385 |
editable=False, |
380 |
- | db_index=True, |
+ |
386 |
db_index=True, |
381 |
387 |
help_text=_("The group for which this invitation is."), |
382 |
388 |
) |
383 |
389 |
accepted = models.NullBooleanField( |
384 |
390 |
default=None, |
385 |
391 |
help_text=_("Indicates if the invitation was accepted, rejected, or " |
386 |
392 |
"pending an answer. if somebody rejects the invitation, " |
387 |
393 |
"that group can no longer send an invitation to the invitee, " |
388 |
394 |
"unless (s)he removes the answer from her history. Also, " |
389 |
395 |
"a person can not reject an invitation, and accept it later. " |
390 |
396 |
"For that, a new invitation must be received."), |
391 |
397 |
) |
392 |
398 |
|
393 |
399 |
def __str__(self): |
394 |
400 |
return str(self.invitee) +" | "+ str(self.group) |
395 |
401 |
agora/templates/agora/post.djhtml ¶
51 additions and 0 deletions.
View changes Hide changes
+ |
1 |
{% load i18n %} |
+ |
2 |
{% load humanize %} |
+ |
3 |
{% load crispy_forms_tags %} |
+ |
4 |
{% get_media_prefix as media %} |
+ |
5 |
|
+ |
6 |
|
+ |
7 |
{{ post.author.alias }} |
+ |
8 |
|
+ |
9 |
{{ post.timestamp|naturaltime }} |
+ |
10 |
|
+ |
11 |
|
+ |
12 |
{% if post.title %} |
+ |
13 |
{{ post.title }} |
+ |
14 |
{% endif %} |
+ |
15 |
|
+ |
16 |
{% if post.text %} |
+ |
17 |
|
+ |
18 |
{{ post.text|safe }} {# Disable safe to enhance security #} |
+ |
19 |
|
+ |
20 |
{% endif %} |
+ |
21 |
|
+ |
22 |
{% if post.file %} |
+ |
23 |
{% if post.file_type = "image" %} |
+ |
24 |
|
+ |
25 |
{% elif post.file_type = "video" %} |
+ |
26 |
|
+ |
27 |
{% elif post.file_type = "music" %} |
+ |
28 |
|
+ |
29 |
{% else %} {# TODO text should be rendered a bit if possible #} |
+ |
30 |
{% trans "Download file" %} |
+ |
31 |
{% endif %} |
+ |
32 |
{% endif %} |
+ |
33 |
|
+ |
34 |
{% if post.allow_votes %} |
+ |
35 |
▲ {{ post.votes }} |
+ |
36 |
|
+ |
37 |
{% trans "Vote" %} |
+ |
38 |
|
+ |
39 |
{% endif %} |
+ |
40 |
|
+ |
41 |
{% if post.allow_responses %} |
+ |
42 |
|
+ |
43 |
|
+ |
44 |
{% crispy agora-form-post %} |
+ |
45 |
{% endif %} |
+ |
46 |
|
+ |
47 |
{% for subpost in post.responses %} |
+ |
48 |
{% include "agora/post.djhtml" with post=subpost %} |
+ |
49 |
{% endfor %} |
+ |
50 |
|
+ |
51 |
courses/migrations/0002_auto_20171121_2018.py ¶
50 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', '0001_initial'), |
+ |
11 |
] |
+ |
12 |
|
+ |
13 |
operations = [ |
+ |
14 |
migrations.AlterField( |
+ |
15 |
model_name='announcement', |
+ |
16 |
name='course', |
+ |
17 |
field=models.ForeignKey(help_text='The course for which this announcement is made.', on_delete=django.db.models.deletion.CASCADE, to='courses.Course'), |
+ |
18 |
), |
+ |
19 |
migrations.AlterField( |
+ |
20 |
model_name='assignment', |
+ |
21 |
name='course', |
+ |
22 |
field=models.ForeignKey(help_text='The course for which this task is assigned.', on_delete=django.db.models.deletion.CASCADE, to='courses.Course'), |
+ |
23 |
), |
+ |
24 |
migrations.AlterField( |
+ |
25 |
model_name='studygroup', |
+ |
26 |
name='course', |
+ |
27 |
field=models.ForeignKey(help_text='The course for which this group is.', on_delete=django.db.models.deletion.CASCADE, to='courses.Course'), |
+ |
28 |
), |
+ |
29 |
migrations.AlterField( |
+ |
30 |
model_name='studygroup', |
+ |
31 |
name='group', |
+ |
32 |
field=models.ForeignKey(help_text='The group that will be seen as the study group.', on_delete=django.db.models.deletion.PROTECT, to='agora.Group'), |
+ |
33 |
), |
+ |
34 |
migrations.AlterField( |
+ |
35 |
model_name='upload', |
+ |
36 |
name='assignment', |
+ |
37 |
field=models.ForeignKey(help_text='For which assignment this upload is.', limit_choices_to={'digital_task': True}, on_delete=django.db.models.deletion.CASCADE, to='courses.Assignment'), |
+ |
38 |
), |
+ |
39 |
migrations.AlterField( |
+ |
40 |
model_name='upload', |
+ |
41 |
name='file', |
+ |
42 |
field=models.FileField(help_text='The file you want to upload for this assignment.', upload_to='assignments/uploads/%Y/%m/'), |
+ |
43 |
), |
+ |
44 |
migrations.AlterField( |
+ |
45 |
model_name='upload', |
+ |
46 |
name='student', |
+ |
47 |
field=models.ForeignKey(help_text='The student who handed this in.', limit_choices_to={'is_student': True}, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), |
+ |
48 |
), |
+ |
49 |
] |
+ |
50 |
courses/models.py ¶
46 additions and 9 deletions.
View changes Hide changes
1 |
1 |
from django.utils.translation import ugettext_lazy as _ |
2 |
2 |
|
3 |
3 |
class Course(models.Model): |
4 |
4 |
""" Represents a course that is taught at the university. """ |
5 |
5 |
number = models.PositiveSmallIntegerField( |
6 |
6 |
primary_key=True, |
7 |
7 |
blank=False, |
8 |
8 |
help_text=_("The number associated with this course. A leading '0' will be added if the number is smaller than 1000."), |
9 |
9 |
) |
10 |
10 |
name = models.CharField( |
11 |
11 |
max_length=64, |
12 |
12 |
blank=False, |
13 |
13 |
help_text=_("The name of this course, in the language that it is taught. Translations are for the appropriate template."), |
14 |
14 |
) |
15 |
15 |
contact_person = models.ForeignKey( |
+ |
16 |
blank=False, |
+ |
17 |
allow_unicode=True, |
+ |
18 |
unique=True, |
+ |
19 |
help_text=_("A so-called 'slug name' for this course."), |
+ |
20 |
) |
+ |
21 |
contact_person = models.ForeignKey( |
16 |
22 |
"administration.User", |
17 |
23 |
on_delete=models.PROTECT, # A course must have a contact person |
18 |
24 |
limit_choices_to={'is_staff': |