joeni

Add URL patters and new tables

The new URL patters are ready for translation. The tables are not finished, but I will do that tomorrow.

Author
Maarten 'Vngngdn' Vangeneugden
Date
Nov. 18, 2017, 12:02 a.m.
Hash
b2c65ea82b9bc3245a2713b3105ef0deb132edb2
Parent
ca91ec6e5f2ff3a7e709170fe42e1fe882ea819d
Modified files
administration/models.py
agora/models.py
agora/urls.py
courses/models.py
joeni/templates/joeni/index.html

administration/models.py

4 additions and 0 deletions.

View changes Hide changes
1
1
from django.core.exceptions import ValidationError
2
2
from django.core.validators import MaxValueValidator
3
3
from django.utils.translation import ugettext_lazy as _
4
4
5
5
class PersonalDetails(models.Model):
6
6
    user = models.OneToOneField(
7
7
        'joeni.User',
8
8
        on_delete=models.CASCADE,
9
9
        )
10
10
11
11
class Curriculum(models.Model):
12
12
    """ The curriculum of a particular student.
13
13
    Every academic year, a student has to hand in a curriculum (s)he wishes to
14
14
    follow. This is then reviewed by a committee. A curriculum exists of all the
15
15
    courses one wants to partake in in a certain year. """
16
16
    student = models.ForeignKey(
17
17
        "joeni.User",
18
18
        on_delete=models.CASCADE,
19
19
        limit_choices_to={'is_student': True},
20
20
        null=False,
21
21
        editable=False,
22
22
        unique_for_year="year",  # Only 1 curriculum per year
23
23
        )
24
24
    year = models.DateField(
25
25
        auto_now_add=True,
26
26
        db_index=True,
27
27
        help_text=_("The academic year for which this curriculum is."),
28
28
        )
29
29
    last_modified = models.DateTimeField(
30
30
        auto_now=True,
31
31
        help_text=_("The last timestamp that this was updated."),
32
32
        )
33
33
    courses = models.ManyToManyField(
34
34
        "courses.Course",
35
35
        null=False,
36
36
        help_text=_("All the courses included in this curriculum."),
37
37
        )
38
38
    approved = models.NullBooleanField(
39
39
        default=None,
40
40
        help_text=_("Indicates if this curriculum has been approved. If true, "
41
41
                    "that means the responsible committee has reviewed and "
42
42
                    "approved the student for this curriculum. False otherwise. "
43
43
                    "If review is still pending, the value is NULL. Modifying "
44
44
                    "the curriculum implies this setting is set to NULL again."),
45
45
        )
46
46
    note = models.TextField(
47
47
        blank=True,
48
48
        help_text=_("Additional notes regarding this curriculum. This has "
49
49
                    "multiple uses. For the student, it is used to clarify "
50
50
                    "any questions, or to motivate why (s)he wants to take a "
51
51
                    "course for which the requirements were not met. "
52
52
                    "The reviewing committee can use this field to argument "
53
53
                    "their decision, especially for when the curriculum is "
54
54
                    "denied."),
55
55
        )
56
56
57
57
    def curriculum_type(self):
58
58
        """ Returns the type of this curriculum. At the moment, this is
59
59
        either a standard programme, or an individualized programme. """
60
60
        # Currently: A standard programme means: All courses are from the
61
61
        # same study, ánd from the same year. Additionally, all courses
62
62
        # from that year must've been taken.
63
63
        # FIXME: Need a way to determine what is the standard programme.
64
64
        # If not possible, make this a charfield with options or something
65
65
        pass
66
66
67
67
    def __str__(self):
68
68
        year = self.year.year
69
69
        if self.year.month < 7:
70
70
            return str(self.student) +" | "+ str(year-1) +"-"+ str(year)
71
71
        else:
72
72
            return str(self.student) +" | "+ str(year) +"-"+ str(year+1)
73
73
74
74
75
75
class CourseResult(models.Model):
76
76
    """ A student has to obtain a certain course result. These are stored here,
77
77
    together with all the appropriate information. """
78
78
    # TODO: Validate that a course programme for a student can only be made once per year for each course, if possible.
79
79
    CRED = _("Credit acquired")
80
80
    FAIL = _("Credit not acquired")
81
81
    TLRD = _("Tolerated")
82
82
    ITLD = _("Tolerance used")
83
83
    # Possible to add more in the future
84
84
85
85
    student = models.ForeignKey(
86
86
        "joeni.User",
87
87
        on_delete=models.CASCADE,
88
88
        limit_choices_to={'is_student': True},
89
89
        null=False,
90
90
        )
91
91
    course_programme = models.ForeignKey(
92
92
        "courses.ProgrammeInformation",
93
93
        on_delete=models.PROTECT,
94
94
        null=False,
95
95
        )
96
96
    released = models.DateField(
97
97
        auto_now=True,
98
98
        help_text=_("The date that this result was last updated."),
99
99
        )
100
100
    first_score = models.PositiveSmallIntegerField(
101
101
        null=True,  # It's possible a score does not exist.
102
102
        validators=[MaxValueValidator(
103
103
            20,
104
104
            _("%(score)s mustn't be higher than 20."),
105
105
                params={'score': score},
106
106
            )],
107
107
        )
108
108
    second_score = models.PositiveSmallIntegerField(
109
109
        null=True,
110
110
        validators=[MaxValueValidator(
111
111
            20,
112
112
            _("%(score)s mustn't be higher than 20."),
113
113
                params={'score': score},
114
114
            )],
115
115
        )
116
116
    result = models.CharField(
117
117
        max_length=10,
118
118
        choices = (
119
119
            ("CRED", CRED),
120
120
            ("FAIL", FAIL),
121
121
            ("TLRD", TLRD),
122
122
            ("ITLD", ITLD),
123
123
            ),
124
124
        blank=False,
125
125
        help_text=_("The final result this record constitutes."),
126
126
        )
127
127
128
128
    def __str__(self):
129
129
        stdnum = str(self.student.number)
130
130
        result = self.result
131
131
        if result == "CRED":
132
132
            if self.first_score < 10:
133
133
                result = "C" + self.first_score + "1"
134
134
            else:
135
135
                result = "C" + self.second_score + "2"
136
136
        course = str(self.course_programme.course)
137
137
        return stdnum +" ("+ result +") | "+ course
138
138
139
139
class PreRegistration(models.Model):
140
140
    """ At the beginning of the new academic year, students can register
141
141
    themselves at the university. Online, they can do a preregistration already.
142
142
    These records are stored here and can later be retrieved for the actual
143
143
    registration process.
144
144
    Note: The current system in use at Hasselt University provides a password system.
145
145
    That will be eliminated here. Just make sure that the entered details are correct.
146
146
    Should there be an error, and the same email address is used to update something,
147
147
    a mail will be sent to that address to verify this was a genuine update."""
148
148
    created = models.DateField(auto_now_add=True)
149
149
    first_name = models.CharField(max_length=64, blank=False, help_text=_("Your first name."))
150
150
    last_name = models.CharField(max_length=64, blank=False, help_text=_("Your last name."))
151
151
    additional_names = models.CharField(max_length=64, blank=True, help_text=_("Any additional names."))
152
152
    title = models.CharField(
153
153
        max_length=64,
154
154
        blank=True,
155
155
        help_text=_("Any additional titles, prefixes, ..."),
156
156
        )
157
157
    DOB = models.DateField(
158
158
        blank=False,
159
159
        editable=False,
160
160
        help_text=_("Your date of birth."),
161
161
        )
162
162
    POB = models.CharField(
163
163
        max_length=64,
164
164
        blank=False,
165
165
        editable=False,
166
166
        help_text=_("The place you were born."),
167
167
        )
168
168
    nationality = models.CharField(
169
169
        max_length=64,
170
170
        blank=False,
171
171
        help_text=_("Your current nationality."),
172
172
        )
173
173
    national_registry_number = models.BigIntegerField(
174
174
        null=True,
175
175
        help_text=_("If you have one, your national registry number."),
176
176
        )
177
177
    civil_status = models.CharField(
178
178
        choices = (
179
179
            ("Single", _("Single")),
180
180
            ("Married", _("Married")),
181
181
            ("Divorced", _("Divorced")),
182
182
            ("Widowed", _("Widowed")),
183
183
            ("Partnership", _("Partnership")),
184
184
            ),
185
185
        blank=False,
186
186
        # There may be more; consult http://www.aantrekkingskracht.com/trefwoord/burgerlijke-staat
187
187
        # for more information.
188
188
        help_text=_("Your civil/marital status."),
189
189
        )
190
190
    email = models.EmailField(
191
191
        blank=False,
192
192
        unique=True,
193
193
        help_text=_("The e-mail address we will use to communicate until your actual registration."),
194
194
        )
195
195
    study = models.ForeignKey(
196
196
        "courses.Study",
197
197
        on_delete=models.PROTECT,
198
198
        null=False,
199
199
        help_text=_("The study you wish to follow. Be sure to provide all legal"
200
200
                    "documents that are required for this study with this "
201
201
                    "application, or bring them with you to the final registration."),
202
202
        )
203
203
    study_type = models.CharField(
204
204
        max_length=32,
205
205
        choices = (
206
206
            ("Diplom contract", _("Diplom contract")),
207
207
            ("Exam contract", _("Exam contract")),
208
208
            ("Credit contract", _("Credit contract")),
209
209
            ),
210
210
        blank=False,
211
211
        help_text=_("The type of study contract you wish to follow."),
212
212
        )
213
213
    document = models.FileField(
214
214
        upload_to="pre-enrollment/%Y",
215
215
        help_text=_("Any legal documents regarding your enrollment."),
216
216
        )
217
217
    # XXX: If the database in production is PostgreSQL, comment document, and
218
218
    # uncomment the next column.
219
219
    """documents = models.ArrayField(
220
220
        models.FileField(upload_to="pre-enrollment/%Y"),
221
221
        help_text=_("Any legal documents regarding your enrollment."),
222
222
        )"""
223
223
224
224
    def __str__(self):
225
225
        name = self.last_name +" "+ self.first_name
226
226
        dob = self.DOB.strftime("%d/%m/%Y")
227
227
        return name +" | "+ dob
228
228
229
229
+
230
# Planning and organization related tables
+
231
class RoomReservation(models.Model):
+
232
    pass
+
233

agora/models.py

156 additions and 12 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
class Account(models.Model):
5
5
    user = models.OneToOneField(
6
6
        'joeni.User',
7
7
        on_delete=models.CASCADE,
8
8
        primary_key=True,
9
9
        )
10
10
    alias = models.CharField(max_length=64, unique=True)
11
11
12
12
    def __str__(self):
13
13
        return self.alias
14
14
15
15
def account_user_directory(instance, filename):
16
16
    return '{0}/account/settings/{1}'.format(instace.account.alias, filename)
17
17
18
18
class AccountSettings(models.Model):
19
19
    account = models.OneToOneField(
20
20
        'Account',
21
21
        on_delete=models.CASCADE,
22
22
        )
23
23
    # TODO: Build validator for primary_color to make sure what is given is a
24
24
    # valid hexadecimal RGB value.
25
25
    color = models.CharField(
26
26
        max_length=6,
27
27
        help_text=_("The hexadecimal code of the color for this account."),
28
28
        default = constants.COLORS["UHasselt default"],
29
29
        blank=False,
30
30
        )
+
31
        )
31
32
    account_page_banner = models.ImageField(  # Requires the Pillow library!
32
33
        upload_to=account_user_directory,
33
34
        help_text=_("The banner image to be shown on this account's homepage."),
34
35
        )
35
36
    avatar = models.ImageField(
36
37
        upload_to=account_user_directory,
37
38
        help_text=_("The avatar image of this account."),
38
39
        )
39
40
40
41
    def __str__(self):
41
42
        return str(self.account)
42
43
43
44
class Post(models.Model):
44
45
    """ An abstract model that represents a post. """
45
-
    timestamp = models.DateTimeField(auto_now_add=True)
46
46
    title = models.CharField(
47
47
        max_length=64,
48
48
        blank=True,
49
49
        help_text=_("The title for this post."),
50
50
        )
51
51
    text = models.TextField(
52
52
        blank=True,
53
53
        help_text=_("A text message for this post. May be left blank."),
54
54
        )
55
55
    author = models.ForeignKey(
56
56
        "Account",
57
57
        on_delete=models.CASCADE,
58
58
        null=False,  # There must be an author
59
59
        editable=False,  # It makes no sense to change the author after creation
60
60
        help_text=_("The authoring account of this post."),
61
61
        )
62
62
    response_to = models.ForeignKey(
63
63
        "self",
64
64
        on_delete=models.CASCADE,
65
65
        null=True,  # If this is null, this post is not a response, but a beginning post
66
66
        editable=False,  # This cannot be changed after creation, wouldn't make sense
67
67
        help_text=_("The post to which this was a response, if applicable."),
68
68
        )
69
69
    placed_on = models.ForeignKey(
70
70
        "Page",
71
71
        on_delete=models.CASCADE,
72
72
        null=False,
73
73
        editable=False,
74
74
        help_text=_("The page on which this post was placed."),
75
75
        )
76
76
    # Voting fields
77
77
    allow_votes = models.BooleanField(
78
78
        default=True,
79
79
        help_text=_("Decide whether to allow voting or disable it for this post."),
80
80
        )
81
81
+
82
        default=True,
+
83
        help_text=_("Decide if other people can respond to this post or not. "
+
84
                    "This does not influence what people allow on their posts."),
+
85
        )
+
86
82
87
    def __str__(self):
83
88
        return str(self.timestamp) + " | " + str(self.author)
84
89
+
90
    # then be used with OpenStreetMap or something
+
91
85
92
class VideoPost(Post):
86
-
    pass
87
-
88
-
class ImagePost(Post):
89
-
    pass
90
-
91
-
class MusicPost(Post):
92
-
    pass
93
-
94
-
class DocumentPost(Post):
95
-
    pass
96
-
+
93
    """ A special type of Post, which has a file linked with it.
+
94
    The poster can specify how to treat this file. """
+
95
    image = _("Image")
+
96
    video = _("Video")
+
97
    music = _("Sound")
+
98
    text  = _("Text" )
+
99
    other = _("Other")
+
100
    file = models.FileField(
+
101
        upload_to="agora/posts/%Y/%m/%d/",
+
102
        null=False,
+
103
        editable=False,
+
104
        help_text=_("The file you wish to share."),
+
105
        )
+
106
    file_type = models.CharField(
+
107
        max_length=16,
+
108
        blank=False,
+
109
        choices = (
+
110
            ('image', image),
+
111
            ('video', video),
+
112
            ('music', music),
+
113
            ('text' , text ),
+
114
            ('other', other),
+
115
            ),
+
116
        help_text=_("How this file should be seen as."),
+
117
        )
+
118
97
119
class Page(models.Model):
98
120
    """ In the university, people can create pages for everything they want and
99
121
    then some. These pages are put in the database through this table. """
100
122
    name = models.CharField(
101
123
        max_length=64,
102
124
        primary_key=True,
103
125
        blank=False,
104
126
        help_text=_("The name of this page."),
105
127
        )
106
128
    created = models.DateTimeField(auto_now_add=True)
107
129
    hidden = models.BooleanField(
108
130
        default=False,
109
131
        help_text=_("Determines if this page can be found without a direct link."),
110
132
        )
111
133
+
134
        blank=True,
+
135
        help_text=_("If you want to put some text on this page, "
+
136
                    "you can put it here. You can use Orgmode-syntax to "
+
137
                    "get as much out of your page as possible. While doing so, "
+
138
                    "be aware of the limitations imposed by the code of conduct."),
+
139
        )
+
140
    public_posting = models.BooleanField(
+
141
        default=True,
+
142
        help_text=_("Determines if everyone can post on this page, or only the "
+
143
                    "people that are linked with it. Know that if a post is made "
+
144
                    "and responding is allowed, everyone can respond to that post."),
+
145
        )
+
146
112
147
    class Meta:
113
148
        abstract=True
114
149
115
150
class AccountPage(Page):
116
151
    account = models.OneToOneField(
+
152
    This page can only be edited by the account holder, or staff members. """
+
153
    # TODO: Find a way to auto-create one of these every time a new account is created
+
154
    # TODO: Require that changes can only occur by either the account holder or staff
+
155
    account = models.OneToOneField(
117
156
        "Account",
118
157
        null=False,
119
158
        on_delete=models.CASCADE,
120
159
        )
121
160
122
161
class GroupPage(Page):
123
162
    group = models.ForeignKey(
+
163
    This page can only be edited by group members or staff members. """
+
164
    # TODO: Find a way to auto-create one of these every time a new group is created
+
165
    # TODO: Require that changes can only occur by either the group or staff
+
166
    group = models.ForeignKey(
124
167
        "Group",
125
168
        null=False,
126
169
        on_delete=models.CASCADE,
127
170
        )
128
171
129
172
class CoursePage(Page):
130
173
    course = models.OneToOneField(
+
174
    This page can only be edited by the course's educating team or staff members. """
+
175
    # TODO: Find a way to auto-create one of these every time a new course is created
+
176
    # TODO: Require that changes can only occur by either the course team or staff
+
177
    course = models.OneToOneField(
131
178
        "courses.Course",
132
179
        null=False,
133
180
        on_delete=models.CASCADE,
134
181
        )
135
182
136
183
+
184
    """ It is imperative that everyone can come together with other people.
+
185
    A Group record is the way to accomplish this. """
+
186
    name = models.CharField(
+
187
        max_length=64,
+
188
        primary_key=True,  # So be unique I'd say
+
189
        blank=False,
+
190
        help_text=_("The name of your group."),
+
191
        )
+
192
    color = models.CharField(
+
193
        max_length=6,
+
194
        help_text=_("The hexadecimal code of the color for this group."),
+
195
        default = constants.COLORS["UHasselt default"],
+
196
        blank=False,
+
197
        validators=[validate_hex_color],
+
198
        )
+
199
    members = models.ManyToManyField(
+
200
        "Account",
+
201
        help_text=_("The members of this group."),
+
202
        )
+
203
    invite_only = models.BooleanField(
+
204
        default=True,
+
205
        help_text=_("Determines if everyone can join this group, or if "
+
206
                    "only members can invite others."),
+
207
        )
+
208
    private = models.BooleanField(
+
209
        default=True,
+
210
        help_text=_("Determines if this group is visible to non-members."),
+
211
        )
+
212
+
213
    def __str__(self):
+
214
        return self.name
+
215
+
216
137
217
class AccountCollection(models.Model):
138
218
    """ Every account can make a collection in which (s)he can list accounts
139
219
    at his/her wish. This can be a collection of Friends, study collegues,
140
220
    project partners, and so on.
141
221
    Accounts that are in a certain collection are not notified of this.
142
222
    However, there is one exception:
143
223
    If both accounts have a collection named "Friends" (or the localized
144
224
    equivalent), and both feature each other in that collection, then
145
225
    this is shared between the two accounts. """
146
226
    account = models.ForeignKey(
147
227
        "Account",
148
228
        null=False,
149
229
        editable=False,
150
230
        on_delete=models.CASCADE,
151
231
        help_text=_("The account that created this collection."),
152
232
        )
153
233
    name = models.CharField(
154
234
        max_length=32,
155
235
        blank=False,
156
236
        help_text=_("The name of this collection."),
157
237
        )
158
238
    accounts = models.ManyToManyField(
159
239
        "Account",
160
240
        help_text=_("All accounts that are part of this collection."),
161
241
        )
162
242
    visible_to_public = models.BooleanField(
163
243
        default=False,
164
244
        help_text=_("Make this collection visible to everybody."),
165
245
        )
166
246
    visible_to_collection = models.BooleanField(
167
247
        default=True,
168
248
        help_text=_("Make this collection visible to the accounts in this collection. Other collections are not affected by this."),
169
249
        )
170
250
171
251
    def __str__(self):
172
252
        return str(self.account) + " | " + self.name
173
253
174
254
class Vote(models.Model):
175
255
    """ Accounts can vote on posts (using ▲, which is funny because UHasselt).
176
256
    These votes are registered in this table. """
177
257
    voter = models.ForeignKey(
178
258
        "Account",
179
259
        null=False,
180
260
        editable=False,
181
261
        on_delete=models.CASCADE,
182
262
        )
183
263
    post = models.ForeignKey(
184
264
        "Post",
185
265
        null=False,  # Duh.
186
266
        editable=False,  # Transferring votes doesn't make sense
187
267
        on_delete=models.CASCADE,
188
268
        )
189
269
+
270
class SharedFile(models.Model):
+
271
    """ Groups and people can share files with each other, through a chat system.
+
272
    These files are represented here. """
+
273
    chat = models.ForeignKey(
+
274
        "Chat",
+
275
        on_delete=models.CASCADE,
+
276
        null=False,
+
277
        editable=False,
+
278
        help_text=_("The chat where this file is being shared in."),
+
279
        )
+
280
    timestamp = models.DateTimeField(auto_now_add=True)
+
281
    file = models.FileField(
+
282
        upload_to="agora/chat/%Y/%m/%d/",
+
283
        null=False,
+
284
        editable=False,
+
285
        help_text=_("The file you want to share."),
+
286
        )
+
287
    uploader = models.ForeignKey(
+
288
        "Account",
+
289
        on_delete=models.CASCADE,
+
290
        null=False,
+
291
        editable=False,
+
292
        help_text=_("The account that uploaded this file."),
+
293
        )
+
294
    # TODO __str__
+
295
+
296
class Message(models.Model):
+
297
    """ Everyone can communicate with someone else using private messages.
+
298
    These messages are recorded here. """
+
299
    chat = models.ForeignKey(
+
300
        "Chat",
+
301
        on_delete=models.CASCADE,
+
302
        null=False,
+
303
        editable=False,
+
304
        help_text=_("The chat where this message is being shared in."),
+
305
        )
+
306
    timestamp = models.DateTimeField(auto_now_add=True)
+
307
    text = models.TextField(
+
308
        blank=False,
+
309
        )
+
310
    sender = models.ForeignKey(
+
311
        "Account",
+
312
        on_delete=models.CASCADE,
+
313
        null=False,
+
314
        editable=False,
+
315
        help_text=_("The account that sent this message."),
+
316
        )
+
317
    # TODO __str__
+
318
+
319
+
320
class Chat(models.Model):
+
321
    """ Chats can happen between a group, or between two people in private.
+
322
    These messages are connected to a particular chat. """
+
323
    class Meta:
+
324
        abstract=True
+
325
+
326
class GroupChat(Chat):
+
327
    pass
+
328
class PrivateChat(Chat):
+
329
    pass
+
330
+
331
class GroupInvite(models.Model):
+
332
    pass
+
333

agora/urls.py

13 additions and 0 deletions.

View changes Hide changes
+
1
from . import views
+
2
from django.utils.translation import ugettext_lazy as _
+
3
+
4
urlpatterns = [] + i18n_patterns(
+
5
    path(_('main'), views.main, name='agora-main'),
+
6
    path(_('group/<str:group>'), views.group, name='agora-group'),
+
7
    path(_('page/<str:page>'), views.page, name='agora-page'),
+
8
    path(_('settings/account'), views.account_settings, name='agora-account-settings'),
+
9
    path(_('settings/page/<str:page>'), views.page_settings, name='agora-page-settings'),
+
10
    path(_('settings/group/<str:group>'), views.group_settings, name='agora-group-settings'),
+
11
    #path(_('settings'), views.settings, name='agora-settings'),  # Uncomment if all the other pages are done. This page links to all the other settings pages available to this account.
+
12
    )
+
13

courses/models.py

10 additions and 0 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
16
        "joeni.user",
17
17
        on_delete=models.PROTECT,  # A course must have a contact person
18
18
        limit_choices_to={'is_staff': True},
19
19
        null=False,
20
20
        help_text=_("The person to contact regarding this course."),
21
21
        )
22
22
    coordinator = models.ForeignKey(
23
23
        "joeni.user",
24
24
        on_delete=models.PROTECT,  # A course must have a coordinator
25
25
        limit_choices_to={'is_staff': True},
26
26
        null=False,
27
27
        help_text=_("The person whom's the coordinator of this course."),
28
28
        )
29
29
    educating_team = models.ManyToManyField(
30
30
        "joeni.user",
31
31
        # No on_delete, since M->M cannot be required at database level
32
32
        limit_choices_to={'is_staff': True},
33
33
        null=False,
34
34
        help_text=_("The team members of this course."),
35
35
        )
36
36
    language = models.CharField(
37
37
        max_length=64,
38
38
        choices = (
39
39
            ('NL', _("Dutch")),
40
40
            ('EN', _("English")),
41
41
            ('FR', _("French")),
42
42
            ),
43
43
        null=False,
44
44
        help_text=_("The language in which this course is given."),
45
45
        )
46
46
    requirements = models.ManyToManyField()
47
47
48
48
    def __str__(self):
49
49
        number = str(self.number)
50
50
        for i in [10,100,1000]:
51
51
            if self.number < i:
52
52
                number = "0" + number
53
53
        return "(" + number + ") " + self.name
54
54
55
55
56
56
class Prerequisites(models.Model):
57
57
    """ Represents a collection of prerequisites a student must have obtained
58
58
    before being allowed to partake in this course.
59
59
    It's possible that, if a student has obtained credits in a certain set of
60
60
    courses, a certain part of the prerequisites do not have to be obtained.
61
61
    Because of this, make a different record for each different set. In other
62
62
    words: If one set of prerequisites is obtained, and another one isn't, BUT
63
63
    they point to the same course, the student is allowed to partake. """
64
64
    course = models.ForeignKey(
65
65
        "Course",
66
66
        on_delete=models.CASCADE,
67
67
        null=False,
68
68
        help_text=_("The course that these prerequisites are for."),
69
69
        )
70
70
    name = models.ForeignKey(
71
71
        blank=True,
72
72
        help_text=_("To specify a name for this set, if necessary."),
73
73
        )
74
74
    sequentialities = models.ManyToManyField(
75
75
        "Course",
76
76
        help_text=_("All courses for which a credit must've been received in order to follow the course."),
77
77
        )
78
78
    in_curriculum = models.ManyToManyField(
79
79
        "Course",
80
80
        help_text=_("All courses that have to be in the curriculum to follow this. If a credit was achieved, that course can be omitted."),
81
81
        )
82
82
    required_study = models.ForeignKey(
83
83
        "Study",
84
84
        on_delete=models.CASCADE,
85
85
        null=True,
86
86
        help_text=_("If one must have a certain amount of obtained ECTS points for a particular course, state that course here."),
87
87
        )
88
88
    ECTS_for_required_study = models.PositiveSmallIntegerField(
89
89
        null=True,
90
90
        help_text=_("The amount of obtained ECTS points for the required course, if any."),
91
91
        )
92
92
93
93
    def __str__(self):
94
94
        if self.name == "":
95
95
            return _("Prerequisites for %(course)s") % {'course': str(self.course)}
96
96
        else:
97
97
            return self.name + " | " + str(self.course)
98
98
99
99
100
100
class ProgrammeInformation(models.Model):
101
101
    """ It's possible that a course is taught in multiple degree programmes; For
102
102
    example: Calculus can easily be taught to physics and mathematics students
103
103
    alike. In this table, these relations are set up, and the related properties
104
104
    are defined as well. """
105
105
    study = models.ForeignKey(
106
106
        "Study",
107
107
        on_delete=models.CASCADE,
108
108
        null=False,
109
109
        help_text=_("The study in which the course is taught."),
110
110
        )
111
111
    course = models.ForeignKey(
112
112
        "Course",
113
113
        on_delete=models.CASCADE,
114
114
        null=False,
115
115
        help_text=_("The course that this information is for."),
116
116
        )
117
117
    study_programme = models.ForeignKey(
118
118
        "StudyProgramme",
119
119
        on_delete=models.CASCADE,
120
120
        null=False,
121
121
        help_text=_("The study programme that this course belongs to."),
122
122
        )
123
123
    programme_type = models.CharField(
124
124
        max_length=1,
125
125
        blank=False,
126
126
        choices = (
127
127
            ('C', _("Compulsory")),
128
128
            ('O', _("Optional")),
129
129
            ),
130
130
        help_text=_("Type of this course for this study."),
131
131
        )
132
132
    study_hours = models.PositiveSmallIntegerField(
133
133
        blank=False,
134
134
        help_text=_("The required amount of hours to study this course."),
135
135
        )
136
136
    ECTS = models.PositiveSmallIntegerField(
137
137
        blank=False,
138
138
        help_text=_("The amount of ECTS points attached to this course."),
139
139
        )
140
140
    semester = models.PositiveSmallIntegerField(
141
141
        blank=False,
142
142
        choices = (
143
143
            (1, _("First semester")),
144
144
            (2, _("Second semester")),
145
145
            (3, _("Full year course")),
146
146
            (4, _("Taught in first quarter")),
147
147
            (5, _("Taught in second quarter")),
148
148
            (6, _("Taught in third quarter")),
149
149
            (7, _("Taught in fourth quarter")),
150
150
            ),
151
151
        help_text=_("The period in which this course is being taught in this study."),
152
152
        )
153
153
    year = models.PositiveSmallIntegerField(
154
154
        blank=False,
155
155
        help_text=_("The year in which this course is taught for this study."),
156
156
        )
157
157
    second_chance = models.BooleanField(
158
158
        default=True,
159
159
        help_text=_("Defines if a second chance exam is planned for this course."),
160
160
        )
161
161
    tolerable = models.BooleanField(
162
162
        default=True,
163
163
        help_text=_("Defines if a failed result can be tolerated."),
164
164
        )
165
165
    scoring = models.CharField(
166
166
        max_length=2,
167
167
        choices = (
168
168
            ('N', _("Numerical")),
169
169
            ('FP', _("Fail/Pass")),
170
170
            ),
171
171
        default='N',
172
172
        blank=False,
173
173
        help_text=_("How the obtained score for this course is given."),
174
174
        )
175
175
176
176
    def __str__(self):
177
177
        return str(self.study) + " - " + str(self.course)
178
178
179
179
class Study(models.Model):
180
180
    """ Defines a certain study that can be followed at the university.
181
181
    This also includes abridged study programmes, like transition programmes.
182
182
    Other information, such as descriptions, are kept in the template file
183
183
    of this study, which can be manually edited. Joeni searches for a file
184
184
    with the exact name as the study + ".html". So if the study is called
185
185
    "Bachelor of Informatics", it will search for "Bachelor of Informatics.html".
186
186
    """
187
187
    # Degree types
188
188
    BSc = _("Bachelor of Science")
189
189
    Msc = _("Master of Science")
190
190
    LLB = _("Bachelor of Laws")
191
191
    LLM = _("Master of Laws")
192
192
    ir  = _("Engineer")
193
193
    ing = _("Technological Engineer")
194
194
    # Faculties
195
195
    FoMaLS = _("Faculty of Medicine and Life Sciences")
196
196
    Fos    = _("Faculty of Sciences")
197
197
    FoTS   = _("Faculty of Transportation Sciences")
198
198
    FoAaA  = _("Faculty of Architecture and Arts")
199
199
    FoBE   = _("Faculty of Business Economics")
200
200
    FoET   = _("Faculty of Engineering Technology")
201
201
    FoL    = _("Faculty of Law")
202
202
203
203
    name = models.CharField(
204
204
        max_length=128,
205
205
        blank=False,
206
206
        unique=True,
207
207
        help_text=_("The full name of this study, in the language it's taught in."),
208
208
        )
209
209
    degree_type = models.CharField(
210
210
        max_length=64,
211
211
        choices = (
212
212
            ('BSc', Bsc),
213
213
            ('MSc', Msc),
214
214
            ('LL.B', LLB),
215
215
            ('LL.M', LLM),
216
216
            ('ir.', ir ),
217
217
            ('ing.',ing),
218
218
            ),
219
219
        blank=False,
220
220
        help_text=_("The type of degree one obtains upon passing this study."),
221
221
        )
222
222
    language = models.CharField(
223
223
        max_length=64,
224
224
        choices = (
225
225
            ('NL', _("Dutch")),
226
226
            ('EN', _("English")),
227
227
            ('FR', _("French")),
228
228
            ),
229
229
        null=False,
230
230
        help_text=_("The language in which this study is given."),
231
231
        )
232
232
    # Information about exam committee
233
233
    chairman = models.ForeignKey(
234
234
        "Joeni.users",
235
235
        on_delete=models.PROTECT,
236
236
        null=False,
237
237
        limit_choices_to={'is_staff': True},
238
238
        help_text=_("The chairman of this study."),
239
239
        )
240
240
    vice_chairman = models.ForeignKey(
241
241
        "Joeni.users",
242
242
        on_delete=models.PROTECT,
243
243
        null=False,
244
244
        help_text=_("The vice-chairman of this study."),
245
245
        limit_choices_to={'is_staff': True},
246
246
        )
247
247
    secretary = models.ForeignKey(
248
248
        "Joeni.users",
249
249
        on_delete=models.PROTECT,
250
250
        null=False,
251
251
        help_text=_("The secretary of this study."),
252
252
        limit_choices_to={'is_staff': True},
253
253
        )
254
254
    ombuds = models.ForeignKey(
255
255
        "Joeni.users",
256
256
        on_delete=models.PROTECT,
257
257
        null=False,
258
258
        help_text=_("The ombuds person of this study."),
259
259
        limit_choices_to={'is_staff': True},
260
260
        )
261
261
    vice_ombuds = models.ForeignKey(
262
262
        "Joeni.users",
263
263
        on_delete=models.PROTECT,
264
264
        null=False,
265
265
        help_text=_("The (replacing) ombuds person of this study."),
266
266
        limit_choices_to={'is_staff': True},
267
267
        )
268
268
    additional_members = models.ManyToManyField(
269
269
        "Joeni.users",
270
270
        help_text=_("All the other members of the exam committee."),
271
271
        limit_choices_to={'is_staff': True},
272
272
        )
273
273
    faculty = models.CharField(
274
274
        max_length=6,
275
275
        choices = (
276
276
            ('FoS', FoS),
277
277
            ('FoTS', FoTS),
278
278
            ('FoAaA', FoAaA),
279
279
            ('FoBE', FoBE),
280
280
            ('FoMaLS', FoMaLS),
281
281
            ('FoET', FoET),
282
282
            ('FoL', FoL),
283
283
            ),
284
284
        blank=False,
285
285
        help_text=_("The faculty where this study belongs to."),
286
286
        )
287
287
288
288
    #def study_points(self):
289
289
        """ Returns the amount of study points for this year.
290
290
        This value is inferred based on the study programme information
291
291
        records that lists this study as their foreign key. """
292
292
        #total_ECTS = 0
293
293
        #for course in ProgrammeInformation.objects.filter(study=self):
294
294
            #total_ECTS += course.ECTS
295
295
        #return total_ECTS
296
296
    # XXX: Commented because this is actually something for the StudyProgramme
297
297
    def years(self):
298
298
        """ Returns the amount of years this study takes.
299
299
        This value is inferred based on the study programme information
300
300
        records that lists this study as their foreign key. """
301
301
        highest_year = 0
302
302
        for course in ProgrammeInformation.objects.filter(study=self):
303
303
            highest_year = max(highest_year, course.year)
304
304
        return highest_year
305
305
306
306
    def __str__(self):
307
307
        return self.name
308
308
309
309
class StudyProgramme(models.Model):
310
310
    """ Represents a programme within a certain study.
311
311
    A good example for this is the different specializations, minors, majors, ...
312
312
    one can follow within the same study. Nevertheless, they're all made of
313
313
    a certain set of courses. This table collects all these, and allows one to name
314
314
    them, so they're distinct from one another. """
315
315
    def name = models.CharField(
316
316
            max_length=64,
317
317
            blank=False,
318
318
            help_text=_("The name of this programme."),
319
319
            )
320
320
321
321
    def courses(self):
322
322
        """ All courses that are part of this study programme. """
323
323
        programmes = ProgrammeInformation.objects.filter(study_programme=self)
324
324
        courses = {}
325
325
        for program in programmes:
326
326
            courses.add(program.course)
327
327
        return courses
328
328
329
329
    def study_points(self, year=None):
330
330
        """ Returns the amount of study points this programme contains.
331
331
        Accepts year as an optional argument. If not given, the study points
332
332
        of all years are returned. """
333
333
        programmes = ProgrammeInformation.objects.filter(study_programme=self)
334
334
        ECTS = 0
335
335
        for program in programmes:
336
336
            if year is None or program.year == year:
337
337
                # XXX: This only works if the used implementation does lazy
338
338
                # evaluation, otherwise this is a type error!
339
339
                ECTS += program.ECTS
340
340
        return ECTS
341
341
342
342
    def __str__(self):
343
343
        return self.name
344
344
345
345
# Tables about things related to the courses:
+
346
346
347
class HomeworkTask(models.Model):
+
348
    pass
+
349
class Announcement(models.Model):
+
350
    pass
+
351
class Upload(models.Model):
+
352
    pass
+
353
class StudyGroup(models.Model):
+
354
    # Should foreignkey with Agora groups and stuff
+
355
    pass
+
356

joeni/templates/joeni/index.html

1 addition and 2 deletions.

View changes Hide changes
1
1
{% load i18n %}
2
2
{% get_current_language as LANGUAGE_CODE %}
3
3
{% get_language_info for LANGUAGE_CODE as lang %}
4
4
{% load static %}
5
5
6
6
<!DOCTYPE html>
7
7
<html lang="{{ lang.code }}">
8
8
	<head>
9
9
        <title>{% block title %}{% trans "Joeni | Test" %}{% endblock title %}</title>
10
10
11
11
        {% block stylesheets %}
12
12
            <link href="{% static 'stylesheets/main.css' %}" rel="stylesheet" media="screen,projection" />
13
13
        {% endblock stylesheets %}
14
14
15
15
        {% block metaflags %}
16
16
        {# This is standard for all web pages and doesn't require changing. #}
17
17
        <meta charset="utf-8" />
18
18
        <meta name="author" content="Maarten Vangeneugden">
19
19
        <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <!--Because this page is suited for mobile devices.-->
20
20
        {% endblock metaflags %}
21
21
22
22
        <meta name="description" content="{% block description %}{% trans "The web informatics system for Hasselt University." %}{% endblock description %}" />
23
23
	</head>
24
24
	<body>
25
25
		{% block header %}
26
26
        <header>
27
-
        </header>
28
-
		{% endblock header %}
+
27
		{% endblock header %}
29
28
30
29
		{% block main %}
31
30
        <main>
32
31
        </main>
33
32
		{% endblock main %}
34
33
		{% block footer %}
35
34
			{% include "joeni/footer.html" %}
36
35
		{% endblock footer %}
37
36
	</body>
38
37
</html>
39
38