joeni

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.

View changes Hide changes
+
1
from crispy_forms.layout import Submit
+
2
# See https://django-crispy-forms.readthedocs.io/en/latest/crispy_tag_forms.html
+
3
# for more information
+
4

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':