Made database migrations and other slight changes
- Author
- Maarten 'Vngngdn' Vangeneugden
- Date
- Nov. 18, 2017, 11:20 p.m.
- Hash
- f1175e5c018fb47d6b6dd76045df6de897b8ed39
- Parent
- fd0e170b5e330e5cdcdc6a4ceb04f88c6231ff40
- Modified files
- administration/admin.py
- administration/migrations/0001_initial.py
- administration/migrations/0002_auto_20171118_2203.py
- administration/migrations/0003_auto_20171118_2204.py
- administration/models.py
- agora/admin.py
- agora/migrations/0001_initial.py
- agora/migrations/0002_auto_20171118_2203.py
- agora/models.py
- courses/admin.py
- courses/migrations/0001_initial.py
- courses/models.py
- joeni/admin.py
- joeni/apps.py
- joeni/models.py
- joeni/settings.py
administration/admin.py ¶
11 additions and 1 deletion.
View changes Hide changes
1 |
1 |
|
+ |
2 |
from django.contrib.auth.admin import UserAdmin |
+ |
3 |
|
+ |
4 |
admin.site.register(User, UserAdmin) |
+ |
5 |
|
+ |
6 |
admin.site.register(Curriculum) |
+ |
7 |
admin.site.register(CourseResult) |
+ |
8 |
admin.site.register(PreRegistration) |
+ |
9 |
admin.site.register(Room) |
+ |
10 |
admin.site.register(RoomReservation) |
+ |
11 |
admin.site.register(Degree) |
+ |
12 |
|
2 |
13 |
# Register your models here. |
3 |
- |
administration/migrations/0001_initial.py ¶
158 additions and 0 deletions.
View changes Hide changes
+ |
1 |
|
+ |
2 |
import administration.models |
+ |
3 |
from django.conf import settings |
+ |
4 |
import django.contrib.auth.models |
+ |
5 |
import django.contrib.auth.validators |
+ |
6 |
import django.core.validators |
+ |
7 |
from django.db import migrations, models |
+ |
8 |
import django.db.models.deletion |
+ |
9 |
import django.utils.timezone |
+ |
10 |
import uuid |
+ |
11 |
|
+ |
12 |
|
+ |
13 |
class Migration(migrations.Migration): |
+ |
14 |
|
+ |
15 |
initial = True |
+ |
16 |
|
+ |
17 |
dependencies = [ |
+ |
18 |
] |
+ |
19 |
|
+ |
20 |
operations = [ |
+ |
21 |
migrations.CreateModel( |
+ |
22 |
name='User', |
+ |
23 |
fields=[ |
+ |
24 |
('password', models.CharField(max_length=128, verbose_name='password')), |
+ |
25 |
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), |
+ |
26 |
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), |
+ |
27 |
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), |
+ |
28 |
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), |
+ |
29 |
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), |
+ |
30 |
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), |
+ |
31 |
('number', models.PositiveIntegerField(help_text='The number assigned to this user.', primary_key=True, serialize=False)), |
+ |
32 |
('created', models.DateField(auto_now_add=True)), |
+ |
33 |
('passphrase', models.CharField(help_text='The passphrase used for this account. This field must only contain hashed information.', max_length=512)), |
+ |
34 |
('first_name', models.CharField(max_length=64)), |
+ |
35 |
('last_name', models.CharField(max_length=64)), |
+ |
36 |
('title', models.CharField(blank=True, help_text='The academic title of this user, if applicable.', max_length=64)), |
+ |
37 |
('DOB', models.DateField(editable=False, help_text='The date of birth of this user.')), |
+ |
38 |
('POB', models.CharField(editable=False, help_text='The place of birth of this user.', max_length=64)), |
+ |
39 |
('nationality', models.CharField(help_text='The current nationality of this user.', max_length=64)), |
+ |
40 |
('national_registry_number', models.BigIntegerField(editable=False, help_text='The assigned national registry number of this user.', unique=True)), |
+ |
41 |
('civil_status', models.CharField(choices=[('Single', 'Single'), ('Married', 'Married'), ('Divorced', 'Divorced'), ('Widowed', 'Widowed'), ('Partnership', 'Partnership')], help_text='The civil/marital status of the user.', max_length=32)), |
+ |
42 |
('is_staff', models.BooleanField(default=False, help_text="Determines if this user is part of the university's staff.")), |
+ |
43 |
('is_student', models.BooleanField(default=True, help_text='Indicates if this user is a student at the university.')), |
+ |
44 |
('home_street', models.CharField(max_length=64)), |
+ |
45 |
('home_number', models.PositiveSmallIntegerField()), |
+ |
46 |
('home_bus', models.PositiveSmallIntegerField()), |
+ |
47 |
('home_postal_code', models.PositiveSmallIntegerField()), |
+ |
48 |
('home_country', models.CharField(max_length=64)), |
+ |
49 |
('home_telephone', models.CharField(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)), |
+ |
50 |
('study_street', models.CharField(max_length=64)), |
+ |
51 |
('study_number', models.PositiveSmallIntegerField()), |
+ |
52 |
('study_bus', models.PositiveSmallIntegerField()), |
+ |
53 |
('study_postal_code', models.PositiveSmallIntegerField()), |
+ |
54 |
('study_country', models.CharField(max_length=64)), |
+ |
55 |
('study_telephone', models.CharField(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)), |
+ |
56 |
('study_cellphone', models.CharField(help_text='The cellphone number of the person. Prefix 0 can be presented with then national call code in the system.', max_length=64)), |
+ |
57 |
('titularis_street', models.CharField(max_length=64)), |
+ |
58 |
('titularis_number', models.PositiveSmallIntegerField()), |
+ |
59 |
('titularis_bus', models.PositiveSmallIntegerField()), |
+ |
60 |
('titularis_postal_code', models.PositiveSmallIntegerField()), |
+ |
61 |
('titularis_country', models.CharField(max_length=64)), |
+ |
62 |
('titularis_telephone', 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)), |
+ |
63 |
('bank_account_number', models.CharField(help_text='The IBAN of this user. No spaces!', max_length=34, validators=[administration.models.validate_IBAN])), |
+ |
64 |
('BIC', models.CharField(help_text="The BIC of this user's bank.", max_length=11, validators=[administration.models.validate_BIC])), |
+ |
65 |
], |
+ |
66 |
options={ |
+ |
67 |
'verbose_name': 'user', |
+ |
68 |
'verbose_name_plural': 'users', |
+ |
69 |
'abstract': False, |
+ |
70 |
}, |
+ |
71 |
managers=[ |
+ |
72 |
('objects', django.contrib.auth.models.UserManager()), |
+ |
73 |
], |
+ |
74 |
), |
+ |
75 |
migrations.CreateModel( |
+ |
76 |
name='CourseResult', |
+ |
77 |
fields=[ |
+ |
78 |
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
+ |
79 |
('released', models.DateField(auto_now=True, help_text='The date that this result was last updated.')), |
+ |
80 |
('first_score', models.PositiveSmallIntegerField(null=True, validators=[django.core.validators.MaxValueValidator(20, "The score mustn't be higher than 20.")])), |
+ |
81 |
('second_score', models.PositiveSmallIntegerField(null=True, validators=[django.core.validators.MaxValueValidator(20, "The score mustn't be higher than 20.")])), |
+ |
82 |
('result', models.CharField(choices=[('CRED', 'Credit acquired'), ('FAIL', 'Credit not acquired'), ('TLRD', 'Tolerated'), ('ITLD', 'Tolerance used')], help_text='The final result this record constitutes.', max_length=10)), |
+ |
83 |
], |
+ |
84 |
), |
+ |
85 |
migrations.CreateModel( |
+ |
86 |
name='Curriculum', |
+ |
87 |
fields=[ |
+ |
88 |
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
+ |
89 |
('year', models.DateField(auto_now_add=True, db_index=True, help_text='The academic year for which this curriculum is.')), |
+ |
90 |
('last_modified', models.DateTimeField(auto_now=True, help_text='The last timestamp that this was updated.')), |
+ |
91 |
('approved', models.NullBooleanField(default=None, help_text='Indicates if this curriculum has been approved. If true, that means the responsible committee has reviewed and approved the student for this curriculum. False otherwise. If review is still pending, the value is NULL. Modifying the curriculum implies this setting is set to NULL again.')), |
+ |
92 |
('note', models.TextField(blank=True, help_text='Additional notes regarding this curriculum. This has multiple uses. For the student, it is used to clarify any questions, or to motivate why (s)he wants to take a course for which the requirements were not met. The reviewing committee can use this field to argument their decision, especially for when the curriculum is denied.')), |
+ |
93 |
], |
+ |
94 |
), |
+ |
95 |
migrations.CreateModel( |
+ |
96 |
name='Degree', |
+ |
97 |
fields=[ |
+ |
98 |
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), |
+ |
99 |
('first_name', models.CharField(max_length=64)), |
+ |
100 |
('last_name', models.CharField(max_length=64)), |
+ |
101 |
('additional_names', models.CharField(max_length=64)), |
+ |
102 |
('DOB', models.DateField(editable=False)), |
+ |
103 |
('POB', models.CharField(editable=False, max_length=64)), |
+ |
104 |
('study', models.CharField(editable=False, max_length=64)), |
+ |
105 |
('achieved', models.DateField(editable=False)), |
+ |
106 |
], |
+ |
107 |
), |
+ |
108 |
migrations.CreateModel( |
+ |
109 |
name='PreRegistration', |
+ |
110 |
fields=[ |
+ |
111 |
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
+ |
112 |
('created', models.DateField(auto_now_add=True)), |
+ |
113 |
('first_name', models.CharField(help_text='Your first name.', max_length=64)), |
+ |
114 |
('last_name', models.CharField(help_text='Your last name.', max_length=64)), |
+ |
115 |
('additional_names', models.CharField(blank=True, help_text='Any additional names.', max_length=64)), |
+ |
116 |
('title', models.CharField(blank=True, help_text='Any additional titles, prefixes, ...', max_length=64)), |
+ |
117 |
('DOB', models.DateField(editable=False, help_text='Your date of birth.')), |
+ |
118 |
('POB', models.CharField(editable=False, help_text='The place you were born.', max_length=64)), |
+ |
119 |
('nationality', models.CharField(help_text='Your current nationality.', max_length=64)), |
+ |
120 |
('national_registry_number', models.BigIntegerField(help_text='If you have one, your national registry number.', null=True)), |
+ |
121 |
('civil_status', models.CharField(choices=[('Single', 'Single'), ('Married', 'Married'), ('Divorced', 'Divorced'), ('Widowed', 'Widowed'), ('Partnership', 'Partnership')], help_text='Your civil/marital status.', max_length=32)), |
+ |
122 |
('email', models.EmailField(help_text='The e-mail address we will use to communicate until your actual registration.', max_length=254, unique=True)), |
+ |
123 |
('study_type', models.CharField(choices=[('Diplom contract', 'Diplom contract'), ('Exam contract', 'Exam contract'), ('Credit contract', 'Credit contract')], help_text='The type of study contract you wish to follow.', max_length=32)), |
+ |
124 |
('document', models.FileField(help_text='Any legal documents regarding your enrollment.', upload_to='pre-enrollment/%Y')), |
+ |
125 |
], |
+ |
126 |
), |
+ |
127 |
migrations.CreateModel( |
+ |
128 |
name='Room', |
+ |
129 |
fields=[ |
+ |
130 |
('name', models.CharField(help_text='The name of this room. If more appropriate, this can be the colloquial name.', max_length=20, primary_key=True, serialize=False)), |
+ |
131 |
('seats', models.PositiveSmallIntegerField(help_text='The amount of available seats in this room. This can be handy for exams for example.')), |
+ |
132 |
('wheelchair_accessible', models.BooleanField(default=True)), |
+ |
133 |
('exams_equipped', models.BooleanField(default=True, help_text='Indicates if exams can reasonably be held in this room.')), |
+ |
134 |
('computers_available', models.PositiveSmallIntegerField(default=False, help_text='Indicates how many computers are available in this room.')), |
+ |
135 |
('projector_available', models.BooleanField(default=False, help_text='Indicates if a projector is available at this room.')), |
+ |
136 |
('blackboards_available', models.PositiveSmallIntegerField(help_text='The amount of blackboards available in this room.')), |
+ |
137 |
('whiteboards_available', models.PositiveSmallIntegerField(help_text='The amount of whiteboards available in this room.')), |
+ |
138 |
('category', models.CharField(choices=[('LABORATORY', 'Laboratory'), ('CLASS_ROOM', 'Class room'), ('AUDITORIUM', 'Auditorium'), ('PC_ROOM', 'PC room'), ('PUBLIC_ROOM', 'Public room'), ('OFFICE', 'Office'), ('PRIVATE_ROOM', 'Private room'), ('WORKSHOP', 'Workshop'), ('OTHER', 'Other')], help_text='The category that best suits the character of this room.', max_length=16)), |
+ |
139 |
('reservable', models.BooleanField(default=True, help_text='Indicates if this room can be reserved for something.')), |
+ |
140 |
('note', models.TextField(blank=True, help_text="If some additional info is required for this room, like a characteristic property (e.g. 'Usually occupied by 2BACH informatics'), state it here.")), |
+ |
141 |
], |
+ |
142 |
), |
+ |
143 |
migrations.CreateModel( |
+ |
144 |
name='RoomReservation', |
+ |
145 |
fields=[ |
+ |
146 |
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
+ |
147 |
('timestamp', models.DateTimeField(auto_now_add=True)), |
+ |
148 |
('start_time', models.DateTimeField(help_text='The time that this reservation starts.')), |
+ |
149 |
('end_time', models.DateTimeField(help_text='The time that this reservation ends.')), |
+ |
150 |
('seats', models.PositiveSmallIntegerField(help_text='Indicates how many seats are required. If this is left null, it is assumed the entire room has to be reserved.', null=True)), |
+ |
151 |
('reason', models.CharField(blank=True, help_text='The reason for this reservation, if useful.', max_length=64)), |
+ |
152 |
('note', models.TextField(blank=True, help_text='If some additional info is required for this reservation, state it here.')), |
+ |
153 |
('reservator', models.ForeignKey(editable=False, help_text='The person that made the reservation (and thus responsible).', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), |
+ |
154 |
('room', models.ForeignKey(editable=False, 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')), |
+ |
155 |
], |
+ |
156 |
), |
+ |
157 |
] |
+ |
158 |
administration/migrations/0002_auto_20171118_2203.py ¶
59 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 |
initial = True |
+ |
10 |
|
+ |
11 |
dependencies = [ |
+ |
12 |
('courses', '0001_initial'), |
+ |
13 |
('administration', '0001_initial'), |
+ |
14 |
('auth', '0009_alter_user_last_name_max_length'), |
+ |
15 |
] |
+ |
16 |
|
+ |
17 |
operations = [ |
+ |
18 |
migrations.AddField( |
+ |
19 |
model_name='preregistration', |
+ |
20 |
name='study', |
+ |
21 |
field=models.ForeignKey(help_text='The study you wish to follow. Be sure to provide all legaldocuments that are required for this study with this application, or bring them with you to the final registration.', on_delete=django.db.models.deletion.PROTECT, to='courses.Study'), |
+ |
22 |
), |
+ |
23 |
migrations.AddField( |
+ |
24 |
model_name='degree', |
+ |
25 |
name='user', |
+ |
26 |
field=models.ForeignKey(help_text='The person that achieved this degree, if (s)he still has an account at this university. If the account is deleted at a later date, this field will be set to NULL, but the other fields will be retained.', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL), |
+ |
27 |
), |
+ |
28 |
migrations.AddField( |
+ |
29 |
model_name='curriculum', |
+ |
30 |
name='courses', |
+ |
31 |
field=models.ManyToManyField(help_text='All the courses included in this curriculum.', to='courses.Course'), |
+ |
32 |
), |
+ |
33 |
migrations.AddField( |
+ |
34 |
model_name='curriculum', |
+ |
35 |
name='student', |
+ |
36 |
field=models.ForeignKey(editable=False, limit_choices_to={'is_student': True}, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, unique_for_year='year'), |
+ |
37 |
), |
+ |
38 |
migrations.AddField( |
+ |
39 |
model_name='courseresult', |
+ |
40 |
name='course_programme', |
+ |
41 |
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='courses.ProgrammeInformation'), |
+ |
42 |
), |
+ |
43 |
migrations.AddField( |
+ |
44 |
model_name='courseresult', |
+ |
45 |
name='student', |
+ |
46 |
field=models.ForeignKey(limit_choices_to={'is_student': True}, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), |
+ |
47 |
), |
+ |
48 |
migrations.AddField( |
+ |
49 |
model_name='user', |
+ |
50 |
name='groups', |
+ |
51 |
field=models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups'), |
+ |
52 |
), |
+ |
53 |
migrations.AddField( |
+ |
54 |
model_name='user', |
+ |
55 |
name='user_permissions', |
+ |
56 |
field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions'), |
+ |
57 |
), |
+ |
58 |
] |
+ |
59 |
administration/migrations/0003_auto_20171118_2204.py ¶
18 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', '0002_auto_20171118_2203'), |
+ |
9 |
] |
+ |
10 |
|
+ |
11 |
operations = [ |
+ |
12 |
migrations.AlterField( |
+ |
13 |
model_name='user', |
+ |
14 |
name='number', |
+ |
15 |
field=models.AutoField(help_text='The number assigned to this user.', primary_key=True, serialize=False), |
+ |
16 |
), |
+ |
17 |
] |
+ |
18 |
administration/models.py ¶
150 additions and 12 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 |
import datetime |
+ |
6 |
import os |
+ |
7 |
import uuid |
+ |
8 |
|
5 |
9 |
class PersonalDetails(models.Model): |
6 |
- | user = models.OneToOneField( |
7 |
- | 'joeni.User', |
8 |
- | on_delete=models.CASCADE, |
9 |
- | ) |
+ |
10 |
""" Validates if the given value qualifies as a valid IBAN number. |
+ |
11 |
This validator checks if the structure is valid, and calculates the control |
+ |
12 |
number if the structure is correct. If the control number fails, or the |
+ |
13 |
structure is invalid, a ValidationError will be raised. In that case, |
+ |
14 |
the Error will specify whether the structure is incorrect, or the control |
+ |
15 |
number is not valid. |
+ |
16 |
""" |
+ |
17 |
# FIXME: This function is not complete. When there's time, implement |
+ |
18 |
# as specified at https://nl.wikipedia.org/wiki/International_Bank_Account_Number#Structuur |
+ |
19 |
if False: |
+ |
20 |
raise ValidationError( |
+ |
21 |
_('%(value)s is not a valid IBAN number.'), |
+ |
22 |
params={'value': value},) |
+ |
23 |
def validate_BIC(value): |
+ |
24 |
""" Same functionality as validate_IBAN, but for BIC-codes. """ |
+ |
25 |
# FIXME: This function is not complete. When there's time, implement |
+ |
26 |
# as specified at https://nl.wikipedia.org/wiki/Business_Identifier_Code |
+ |
27 |
pass |
+ |
28 |
|
+ |
29 |
class User(AbstractUser): |
+ |
30 |
""" Replacement for the standard Django User model. """ |
+ |
31 |
number = models.AutoField( |
+ |
32 |
primary_key=True, |
+ |
33 |
help_text=_("The number assigned to this user."), |
+ |
34 |
) |
+ |
35 |
created = models.DateField(auto_now_add=True) |
+ |
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 |
last_name = models.CharField(max_length=64, blank=False) |
+ |
43 |
title = models.CharField( |
+ |
44 |
max_length=64, |
+ |
45 |
blank=True, |
+ |
46 |
help_text=_("The academic title of this user, if applicable."), |
+ |
47 |
) |
+ |
48 |
DOB = models.DateField( |
+ |
49 |
blank=False, |
+ |
50 |
editable=False, |
+ |
51 |
help_text=_("The date of birth of this user."), |
+ |
52 |
) |
+ |
53 |
POB = models.CharField( |
+ |
54 |
max_length=64, |
+ |
55 |
blank=False, |
+ |
56 |
editable=False, |
+ |
57 |
help_text=_("The place of birth of this user."), |
+ |
58 |
) |
+ |
59 |
nationality = models.CharField( |
+ |
60 |
max_length=64, |
+ |
61 |
blank=False, |
+ |
62 |
help_text=_("The current nationality of this user."), |
+ |
63 |
) |
+ |
64 |
# XXX: What if this starts with zeros? |
+ |
65 |
national_registry_number = models.BigIntegerField( |
+ |
66 |
unique=True, |
+ |
67 |
editable=False, |
+ |
68 |
help_text=_("The assigned national registry number of this user."), |
+ |
69 |
) |
+ |
70 |
civil_status = models.CharField( |
+ |
71 |
max_length=32, |
+ |
72 |
choices = ( |
+ |
73 |
("Single", _("Single")), |
+ |
74 |
("Married", _("Married")), |
+ |
75 |
("Divorced", _("Divorced")), |
+ |
76 |
("Widowed", _("Widowed")), |
+ |
77 |
("Partnership", _("Partnership")), |
+ |
78 |
), |
+ |
79 |
blank=False, |
+ |
80 |
# There may be more; consult http://www.aantrekkingskracht.com/trefwoord/burgerlijke-staat |
+ |
81 |
# for more information. |
+ |
82 |
help_text=_("The civil/marital status of the user."), |
+ |
83 |
) |
10 |
84 |
|
11 |
85 |
class Curriculum(models.Model): |
+ |
86 |
default=False, |
+ |
87 |
help_text=_("Determines if this user is part of the university's staff."), |
+ |
88 |
) |
+ |
89 |
is_student = models.BooleanField( |
+ |
90 |
default=True, |
+ |
91 |
help_text=_("Indicates if this user is a student at the university."), |
+ |
92 |
) |
+ |
93 |
|
+ |
94 |
# Home address |
+ |
95 |
home_street = models.CharField(max_length=64, blank=False) |
+ |
96 |
home_number = models.PositiveSmallIntegerField(blank=False) |
+ |
97 |
home_bus = models.PositiveSmallIntegerField() |
+ |
98 |
home_postal_code = models.PositiveSmallIntegerField(blank=False) |
+ |
99 |
home_country = models.CharField(max_length=64, blank=False) |
+ |
100 |
home_telephone = models.CharField( |
+ |
101 |
max_length=64, |
+ |
102 |
help_text=_("The telephone number for the house address. Prefix 0 can be presented with the national call code in the system."), |
+ |
103 |
) |
+ |
104 |
# Study address |
+ |
105 |
study_street = models.CharField(max_length=64, blank=False) |
+ |
106 |
study_number = models.PositiveSmallIntegerField(blank=False) |
+ |
107 |
study_bus = models.PositiveSmallIntegerField() |
+ |
108 |
study_postal_code = models.PositiveSmallIntegerField(blank=False) |
+ |
109 |
study_country = models.CharField(max_length=64, blank=False) |
+ |
110 |
study_telephone = models.CharField( |
+ |
111 |
max_length=64, |
+ |
112 |
help_text=_("The telephone number for the study address. Prefix 0 can be presented with the national call code in the system."), |
+ |
113 |
) |
+ |
114 |
study_cellphone = models.CharField( |
+ |
115 |
max_length=64, |
+ |
116 |
help_text=_("The cellphone number of the person. Prefix 0 can be presented with then national call code in the system."), |
+ |
117 |
) |
+ |
118 |
# Titularis address |
+ |
119 |
# XXX: These fields are only required if this differs from the user itself. |
+ |
120 |
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( |
+ |
126 |
max_length=64, |
+ |
127 |
help_text=_("The telephone number of the titularis. Prefix 0 can be presented with the national call code in the system."), |
+ |
128 |
) |
+ |
129 |
|
+ |
130 |
# Financial details |
+ |
131 |
bank_account_number = models.CharField( |
+ |
132 |
max_length=34, # Max length of all IBAN account numbers |
+ |
133 |
validators=[validate_IBAN], |
+ |
134 |
help_text=_("The IBAN of this user. No spaces!"), |
+ |
135 |
) |
+ |
136 |
BIC = models.CharField( |
+ |
137 |
max_length=11, |
+ |
138 |
validators=[validate_BIC], |
+ |
139 |
help_text=_("The BIC of this user's bank."), |
+ |
140 |
) |
+ |
141 |
|
+ |
142 |
""" NOTE: What about all the other features that should be in the administration? |
+ |
143 |
While there are a lot of things to cover, as of now, I have no way to know which |
+ |
144 |
ones are still valid, which are deprecated, and so on... |
+ |
145 |
Additionally, every feature may have a different set of requirements, data, |
+ |
146 |
and it's very likely making an abstract class won't do any good. Thus I have |
+ |
147 |
decided to postpone making additional tables and forms for these features until |
+ |
148 |
I have clearance about certain aspects. """ |
+ |
149 |
|
+ |
150 |
class Curriculum(models.Model): |
12 |
151 |
""" The curriculum of a particular student. |
13 |
152 |
Every academic year, a student has to hand in a curriculum (s)he wishes to |
14 |
153 |
follow. This is then reviewed by a committee. A curriculum exists of all the |
15 |
154 |
courses one wants to partake in in a certain year. """ |
16 |
155 |
student = models.ForeignKey( |
17 |
156 |
"joeni.User", |
18 |
- | on_delete=models.CASCADE, |
+ |
157 |
on_delete=models.CASCADE, |
19 |
158 |
limit_choices_to={'is_student': True}, |
20 |
159 |
null=False, |
21 |
160 |
editable=False, |
22 |
161 |
unique_for_year="year", # Only 1 curriculum per year |
23 |
162 |
) |
24 |
163 |
year = models.DateField( |
25 |
164 |
auto_now_add=True, |
26 |
165 |
db_index=True, |
27 |
166 |
help_text=_("The academic year for which this curriculum is."), |
28 |
167 |
) |
29 |
168 |
last_modified = models.DateTimeField( |
30 |
169 |
auto_now=True, |
31 |
170 |
help_text=_("The last timestamp that this was updated."), |
32 |
171 |
) |
33 |
172 |
courses = models.ManyToManyField( |
34 |
173 |
"courses.Course", |
35 |
174 |
null=False, |
36 |
175 |
help_text=_("All the courses included in this curriculum."), |
37 |
176 |
) |
38 |
177 |
approved = models.NullBooleanField( |
39 |
178 |
default=None, |
40 |
179 |
help_text=_("Indicates if this curriculum has been approved. If true, " |
41 |
180 |
"that means the responsible committee has reviewed and " |
42 |
181 |
"approved the student for this curriculum. False otherwise. " |
43 |
182 |
"If review is still pending, the value is NULL. Modifying " |
44 |
183 |
"the curriculum implies this setting is set to NULL again."), |
45 |
184 |
) |
46 |
185 |
note = models.TextField( |
47 |
186 |
blank=True, |
48 |
187 |
help_text=_("Additional notes regarding this curriculum. This has " |
49 |
188 |
"multiple uses. For the student, it is used to clarify " |
50 |
189 |
"any questions, or to motivate why (s)he wants to take a " |
51 |
190 |
"course for which the requirements were not met. " |
52 |
191 |
"The reviewing committee can use this field to argument " |
53 |
192 |
"their decision, especially for when the curriculum is " |
54 |
193 |
"denied."), |
55 |
194 |
) |
56 |
195 |
|
57 |
196 |
def curriculum_type(self): |
58 |
197 |
""" Returns the type of this curriculum. At the moment, this is |
59 |
198 |
either a standard programme, or an individualized programme. """ |
60 |
199 |
# Currently: A standard programme means: All courses are from the |
61 |
200 |
# same study, ánd from the same year. Additionally, all courses |
62 |
201 |
# from that year must've been taken. |
63 |
202 |
# FIXME: Need a way to determine what is the standard programme. |
64 |
203 |
# If not possible, make this a charfield with options or something |
65 |
204 |
pass |
66 |
205 |
|
67 |
206 |
def __str__(self): |
68 |
207 |
year = self.year.year |
69 |
208 |
if self.year.month < 7: |
70 |
209 |
return str(self.student) +" | "+ str(year-1) +"-"+ str(year) |
71 |
210 |
else: |
72 |
211 |
return str(self.student) +" | "+ str(year) +"-"+ str(year+1) |
73 |
212 |
|
74 |
213 |
|
75 |
214 |
class CourseResult(models.Model): |
76 |
215 |
""" A student has to obtain a certain course result. These are stored here, |
77 |
216 |
together with all the appropriate information. """ |
78 |
217 |
# TODO: Validate that a course programme for a student can only be made once per year for each course, if possible. |
79 |
218 |
CRED = _("Credit acquired") |
80 |
219 |
FAIL = _("Credit not acquired") |
81 |
220 |
TLRD = _("Tolerated") |
82 |
221 |
ITLD = _("Tolerance used") |
83 |
222 |
# Possible to add more in the future |
84 |
223 |
|
85 |
224 |
student = models.ForeignKey( |
86 |
225 |
"joeni.User", |
87 |
- | on_delete=models.CASCADE, |
+ |
226 |
on_delete=models.CASCADE, |
88 |
227 |
limit_choices_to={'is_student': True}, |
89 |
228 |
null=False, |
90 |
229 |
) |
91 |
230 |
course_programme = models.ForeignKey( |
92 |
231 |
"courses.ProgrammeInformation", |
93 |
232 |
on_delete=models.PROTECT, |
94 |
233 |
null=False, |
95 |
234 |
) |
96 |
235 |
released = models.DateField( |
97 |
236 |
auto_now=True, |
98 |
237 |
help_text=_("The date that this result was last updated."), |
99 |
238 |
) |
100 |
239 |
first_score = models.PositiveSmallIntegerField( |
101 |
240 |
null=True, # It's possible a score does not exist. |
102 |
241 |
validators=[MaxValueValidator( |
103 |
242 |
20, |
104 |
243 |
_("%(score)s mustn't be higher than 20."), |
105 |
- | params={'score': score}, |
106 |
- | )], |
+ |
244 |
)], |
107 |
245 |
) |
108 |
246 |
second_score = models.PositiveSmallIntegerField( |
109 |
247 |
null=True, |
110 |
248 |
validators=[MaxValueValidator( |
111 |
249 |
20, |
112 |
250 |
_("%(score)s mustn't be higher than 20."), |
113 |
- | params={'score': score}, |
114 |
- | )], |
+ |
251 |
)], |
115 |
252 |
) |
116 |
253 |
result = models.CharField( |
117 |
254 |
max_length=10, |
118 |
255 |
choices = ( |
119 |
256 |
("CRED", CRED), |
120 |
257 |
("FAIL", FAIL), |
121 |
258 |
("TLRD", TLRD), |
122 |
259 |
("ITLD", ITLD), |
123 |
260 |
), |
124 |
261 |
blank=False, |
125 |
262 |
help_text=_("The final result this record constitutes."), |
126 |
263 |
) |
127 |
264 |
|
128 |
265 |
def __str__(self): |
129 |
266 |
stdnum = str(self.student.number) |
130 |
267 |
result = self.result |
131 |
268 |
if result == "CRED": |
132 |
269 |
if self.first_score < 10: |
133 |
270 |
result = "C" + self.first_score + "1" |
134 |
271 |
else: |
135 |
272 |
result = "C" + self.second_score + "2" |
136 |
273 |
course = str(self.course_programme.course) |
137 |
274 |
return stdnum +" ("+ result +") | "+ course |
138 |
275 |
|
139 |
276 |
class PreRegistration(models.Model): |
140 |
277 |
""" At the beginning of the new academic year, students can register |
141 |
278 |
themselves at the university. Online, they can do a preregistration already. |
142 |
279 |
These records are stored here and can later be retrieved for the actual |
143 |
280 |
registration process. |
144 |
281 |
Note: The current system in use at Hasselt University provides a password system. |
145 |
282 |
That will be eliminated here. Just make sure that the entered details are correct. |
146 |
283 |
Should there be an error, and the same email address is used to update something, |
147 |
284 |
a mail will be sent to that address to verify this was a genuine update.""" |
148 |
285 |
created = models.DateField(auto_now_add=True) |
149 |
286 |
first_name = models.CharField(max_length=64, blank=False, help_text=_("Your first name.")) |
150 |
287 |
last_name = models.CharField(max_length=64, blank=False, help_text=_("Your last name.")) |
151 |
288 |
additional_names = models.CharField(max_length=64, blank=True, help_text=_("Any additional names.")) |
152 |
289 |
title = models.CharField( |
153 |
290 |
max_length=64, |
154 |
291 |
blank=True, |
155 |
292 |
help_text=_("Any additional titles, prefixes, ..."), |
156 |
293 |
) |
157 |
294 |
DOB = models.DateField( |
158 |
295 |
blank=False, |
159 |
296 |
editable=False, |
160 |
297 |
help_text=_("Your date of birth."), |
161 |
298 |
) |
162 |
299 |
POB = models.CharField( |
163 |
300 |
max_length=64, |
164 |
301 |
blank=False, |
165 |
302 |
editable=False, |
166 |
303 |
help_text=_("The place you were born."), |
167 |
304 |
) |
168 |
305 |
nationality = models.CharField( |
169 |
306 |
max_length=64, |
170 |
307 |
blank=False, |
171 |
308 |
help_text=_("Your current nationality."), |
172 |
309 |
) |
173 |
310 |
national_registry_number = models.BigIntegerField( |
174 |
311 |
null=True, |
175 |
312 |
help_text=_("If you have one, your national registry number."), |
176 |
313 |
) |
177 |
314 |
civil_status = models.CharField( |
178 |
315 |
choices = ( |
+ |
316 |
choices = ( |
179 |
317 |
("Single", _("Single")), |
180 |
318 |
("Married", _("Married")), |
181 |
319 |
("Divorced", _("Divorced")), |
182 |
320 |
("Widowed", _("Widowed")), |
183 |
321 |
("Partnership", _("Partnership")), |
184 |
322 |
), |
185 |
323 |
blank=False, |
186 |
324 |
# There may be more; consult http://www.aantrekkingskracht.com/trefwoord/burgerlijke-staat |
187 |
325 |
# for more information. |
188 |
326 |
help_text=_("Your civil/marital status."), |
189 |
327 |
) |
190 |
328 |
email = models.EmailField( |
191 |
329 |
blank=False, |
192 |
330 |
unique=True, |
193 |
331 |
help_text=_("The e-mail address we will use to communicate until your actual registration."), |
194 |
332 |
) |
195 |
333 |
study = models.ForeignKey( |
196 |
334 |
"courses.Study", |
197 |
335 |
on_delete=models.PROTECT, |
198 |
336 |
null=False, |
199 |
337 |
help_text=_("The study you wish to follow. Be sure to provide all legal" |
200 |
338 |
"documents that are required for this study with this " |
201 |
339 |
"application, or bring them with you to the final registration."), |
202 |
340 |
) |
203 |
341 |
study_type = models.CharField( |
204 |
342 |
max_length=32, |
205 |
343 |
choices = ( |
206 |
344 |
("Diplom contract", _("Diplom contract")), |
207 |
345 |
("Exam contract", _("Exam contract")), |
208 |
346 |
("Credit contract", _("Credit contract")), |
209 |
347 |
), |
210 |
348 |
blank=False, |
211 |
349 |
help_text=_("The type of study contract you wish to follow."), |
212 |
350 |
) |
213 |
351 |
document = models.FileField( |
214 |
352 |
upload_to="pre-enrollment/%Y", |
215 |
353 |
help_text=_("Any legal documents regarding your enrollment."), |
216 |
354 |
) |
217 |
355 |
# XXX: If the database in production is PostgreSQL, comment document, and |
218 |
356 |
# uncomment the next column. |
219 |
357 |
"""documents = models.ArrayField( |
220 |
358 |
models.FileField(upload_to="pre-enrollment/%Y"), |
221 |
359 |
help_text=_("Any legal documents regarding your enrollment."), |
222 |
360 |
)""" |
223 |
361 |
|
224 |
362 |
def __str__(self): |
225 |
363 |
name = self.last_name +" "+ self.first_name |
226 |
364 |
dob = self.DOB.strftime("%d/%m/%Y") |
227 |
365 |
return name +" | "+ dob |
228 |
366 |
|
229 |
367 |
|
230 |
368 |
# Planning and organization related tables |
231 |
369 |
class Room(models.Model): |
232 |
370 |
""" Represents a room in the university. |
233 |
371 |
Rooms can have a number of properties, which are stored in the database. |
234 |
372 |
""" |
235 |
373 |
# Types of rooms |
236 |
374 |
LABORATORY = _("Laboratory") # Chemistry/Physics equipped rooms |
237 |
375 |
CLASS_ROOM = _("Class room") # Simple class rooms |
238 |
376 |
AUDITORIUM = _("Auditorium") # Large rooms with ample seating and equipment for lectures |
239 |
377 |
PC_ROOM = _("PC room" ) # Rooms equipped for executing PC related tasks |
240 |
378 |
PUBLIC_ROOM= _("Public room") # Restaurants, restrooms, ... general public spaces |
241 |
379 |
OFFICE = _("Office" ) # Private offices for staff |
242 |
380 |
PRIVATE_ROOM = _("Private room") # Rooms accessible for a limited public; cleaning cupboards, kitchens, ... |
243 |
381 |
WORKSHOP = _("Workshop" ) # Rooms with hardware equipment to build and work on materials |
244 |
382 |
OTHER = _("Other" ) # Rooms that do not fit in any other category |
245 |
383 |
|
246 |
384 |
|
247 |
385 |
name = models.CharField( |
248 |
386 |
max_length=20, |
249 |
387 |
primary_key=True, |
250 |
388 |
blank=False, |
251 |
389 |
help_text=_("The name of this room. If more appropriate, this can be the colloquial name."), |
252 |
390 |
) |
253 |
391 |
seats = models.PositiveSmallIntegerField( |
254 |
392 |
help_text=_("The amount of available seats in this room. This can be handy for exams for example."), |
255 |
393 |
) |
256 |
394 |
wheelchair_accessible = models.BooleanField(default=True) |
257 |
395 |
exams_equipped = models.BooleanField( |
258 |
396 |
default=True, |
259 |
397 |
help_text=_("Indicates if exams can reasonably be held in this room."), |
260 |
398 |
) |
261 |
399 |
computers_available = models.PositiveSmallIntegerField( |
262 |
400 |
default=False, |
263 |
401 |
help_text=_("Indicates how many computers are available in this room."), |
264 |
402 |
) |
265 |
403 |
projector_available = models.BooleanField( |
266 |
404 |
default=False, |
267 |
405 |
help_text=_("Indicates if a projector is available at this room."), |
268 |
406 |
) |
269 |
407 |
blackboards_available = models.PositiveSmallIntegerField( |
270 |
408 |
help_text=_("The amount of blackboards available in this room."), |
271 |
409 |
) |
272 |
410 |
whiteboards_available = models.PositiveSmallIntegerField( |
273 |
411 |
help_text=_("The amount of whiteboards available in this room."), |
274 |
412 |
) |
275 |
413 |
category = models.CharField( |
276 |
414 |
max_length=16, |
277 |
415 |
blank=False, |
278 |
416 |
choices = ( |
279 |
417 |
("LABORATORY", LABORATORY), |
280 |
418 |
("CLASS_ROOM", CLASS_ROOM), |
281 |
419 |
("AUDITORIUM", AUDITORIUM), |
282 |
420 |
("PC_ROOM", PC_ROOM), |
283 |
421 |
("PUBLIC_ROOM", PUBLIC_ROOM), |
284 |
422 |
("OFFICE", OFFICE), |
285 |
423 |
("PRIVATE_ROOM", PRIVATE_ROOM), |
286 |
424 |
("WORKSHOP", WORKSHOP), |
287 |
425 |
("OTHER", OTHER), |
288 |
426 |
), |
289 |
427 |
help_text=_("The category that best suits the character of this room."), |
290 |
428 |
) |
291 |
429 |
reservable = models.BooleanField( |
292 |
430 |
default=True, |
293 |
431 |
help_text=_("Indicates if this room can be reserved for something."), |
294 |
432 |
) |
295 |
433 |
note = models.TextField( |
296 |
434 |
blank=True, |
297 |
435 |
help_text=_("If some additional info is required for this room, like a " |
298 |
436 |
"characteristic property (e.g. 'Usually occupied by 2BACH " |
299 |
437 |
"informatics'), state it here."), |
300 |
438 |
) |
301 |
439 |
# TODO: Add a campus/building field or not? |
302 |
440 |
|
303 |
441 |
def reservation_possible(self, begin, end, seats=None): |
304 |
442 |
""" Returns a boolean indicating if reservating during the given time |
305 |
443 |
is possible. If the begin overlaps with a reservation's end or vice versa, |
306 |
444 |
this is regarded as possible. |
307 |
445 |
Takes seats as optional argument. If not specified, it is assumed the entire |
308 |
446 |
room has to be reserved. """ |
309 |
447 |
if self.reservable is False: |
310 |
448 |
return False |
311 |
449 |
if seats is not None and seats < 0: raise ValueError(_("seats ∈ ℕ")) |
312 |
450 |
|
313 |
451 |
reservations = RoomReservation.objects.filter(room=self) |
314 |
452 |
for reservation in reservations: |
315 |
453 |
if reservation.end <= begin or reservation.begin >= end: |
316 |
454 |
continue # Can be trivially skipped, no overlap here |
317 |
455 |
elif seats is None or reservation.seats is None: |
318 |
456 |
return False # The whole room cannot be reserved -> False |
319 |
457 |
elif seats + reservation.seats > self.seats: |
320 |
458 |
return False # Total amount of seats exceeds the available amount -> False |
321 |
459 |
return True # No overlappings found -> True |
322 |
460 |
|
323 |
461 |
def __str__(self): |
324 |
462 |
return self.name |
325 |
463 |
|
326 |
464 |
class RoomReservation(models.Model): |
327 |
465 |
""" Rooms are to be reserved from time to time. They can be reserved |
328 |
466 |
by externals, for something else, and whatnot. That is stored in this table. |
329 |
467 |
""" |
330 |
468 |
room = models.ForeignKey( |
331 |
469 |
"Room", |
332 |
470 |
on_delete=models.CASCADE, |
333 |
471 |
null=False, |
334 |
472 |
editable=False, |
335 |
473 |
db_index=True, |
336 |
474 |
limit_choices_to={"reservable": True}, |
337 |
475 |
help_text=_("The room that is being reserved at this point."), |
338 |
476 |
) |
339 |
477 |
reservator = models.ForeignKey( |
340 |
478 |
"joeni.User", |
341 |
- | on_delete=models.CASCADE, |
+ |
479 |
on_delete=models.CASCADE, |
342 |
480 |
null=False, |
343 |
481 |
editable=False, |
344 |
482 |
help_text=_("The person that made the reservation (and thus responsible)."), |
345 |
483 |
) |
346 |
484 |
timestamp = models.DateTimeField(auto_now_add=True) |
347 |
485 |
start_time = models.DateTimeField( |
348 |
486 |
null=False, |
349 |
487 |
help_text=_("The time that this reservation starts."), |
350 |
488 |
) |
351 |
489 |
end_time = models.DateTimeField( |
352 |
490 |
null=False, |
353 |
491 |
help_text=_("The time that this reservation ends."), |
354 |
492 |
) |
355 |
493 |
seats = models.PositiveSmallIntegerField( |
356 |
494 |
null=True, |
357 |
495 |
help_text=_("Indicates how many seats are required. If this is left null, " |
358 |
496 |
"it is assumed the entire room has to be reserved."), |
359 |
497 |
) |
360 |
498 |
reason = models.CharField( |
361 |
499 |
max_length=64, |
362 |
500 |
blank=True, |
363 |
501 |
help_text=_("The reason for this reservation, if useful."), |
364 |
502 |
) |
365 |
503 |
note = models.TextField( |
366 |
504 |
blank=True, |
367 |
505 |
help_text=_("If some additional info is required for this reservation, " |
368 |
506 |
"state it here."), |
369 |
507 |
) |
370 |
508 |
|
371 |
509 |
def __str__(self): |
372 |
510 |
start = self.start_time.strftime("%H:%M") |
373 |
511 |
end = self.end_time.strftime("%H:%M") |
374 |
512 |
return str(self.room) +" | "+ start +"-"+ end |
375 |
513 |
|
376 |
514 |
class Degree(models.Model): |
377 |
515 |
""" Contains all degrees that were achieved at this university. |
378 |
516 |
There are no foreign keys in this field. This allows system |
379 |
517 |
administrators to safely remove accounts from alumni, without |
380 |
518 |
the risk of breaking referential integrity or accidentally removing |
381 |
519 |
degrees. |
382 |
520 |
While keeping some fields editable that look like they shouldn't be |
383 |
521 |
(e.g. first_name), this makes it possible for alumni to have a name change |
384 |
522 |
later in their life, and still being able to get a copy of their degree. """ |
385 |
523 |
""" Reason for an ID field for every degree: |
386 |
524 |
This system allows for employers to verify that a certain applicant has indeed, |
387 |
525 |
achieved the degrees (s)he proclaims to have. Because of privacy concerns, |
388 |
526 |
a university cannot disclose information about alumni. |
389 |
527 |
That's where the degree ID comes in. This ID can be printed on all future |
390 |
528 |
degrees. The employer can then visit the university's website, and simply |
391 |
529 |
enter the ID. The website will then simply print what study is attached to |
392 |
530 |
this degree, but not disclose names or anything identifiable. This strikes |
393 |
531 |
thé perfect balance between (easy and digital) degree verification for employers, and maintaining |
394 |
532 |
alumni privacy to the highest extent possible. """ |
395 |
533 |
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) |
396 |
534 |
first_name = models.CharField( |
397 |
535 |
max_length=64, |
398 |
536 |
blank=False, |
399 |
537 |
) |
400 |
538 |
last_name = models.CharField( |
401 |
539 |
max_length=64, |
402 |
540 |
blank=False, |
403 |
541 |
) |
404 |
542 |
additional_names = models.CharField( |
405 |
543 |
max_length=64, |
406 |
544 |
) |
407 |
545 |
DOB = models.DateField(editable=False, null=False) # This can't be changed, of course |
408 |
546 |
POB = models.CharField( |
409 |
547 |
max_length=64, |
410 |
548 |
blank=False, |
411 |
549 |
editable=False, |
412 |
550 |
) |
413 |
551 |
# The study also has to be a charfield, because if a study is removed, |
414 |
552 |
# The information will be lost. |
415 |
553 |
study = models.CharField( |
416 |
554 |
max_length=64, |
417 |
555 |
blank=False, |
418 |
556 |
editable=False, |
419 |
557 |
) |
420 |
558 |
achieved = models.DateField(editable=False, null=False) |
421 |
559 |
user = models.ForeignKey( |
422 |
560 |
"joeni.User", |
423 |
- | on_delete=models.SET_NULL, |
+ |
561 |
on_delete=models.SET_NULL, |
424 |
562 |
null=True, |
425 |
563 |
help_text=_("The person that achieved this degree, if (s)he still has " |
426 |
564 |
"an account at this university. If the account is deleted " |
427 |
565 |
"at a later date, this field will be set to NULL, but the " |
428 |
566 |
"other fields will be retained."), |
429 |
567 |
) |
430 |
568 |
|
431 |
569 |
def __str__(self): |
432 |
570 |
return self.first_name +" "+ self.last_name +" | "+ self.study |
433 |
571 |
agora/admin.py ¶
19 additions and 1 deletion.
View changes Hide changes
1 |
1 |
|
+ |
2 |
|
2 |
3 |
# Register your models here. |
3 |
- | |
+ |
4 |
admin.site.register(Account) |
+ |
5 |
admin.site.register(AccountSettings) |
+ |
6 |
admin.site.register(Post) |
+ |
7 |
admin.site.register(FilePost) |
+ |
8 |
#admin.site.register(Page) # Abstract |
+ |
9 |
admin.site.register(AccountPage) |
+ |
10 |
admin.site.register(GroupPage) |
+ |
11 |
admin.site.register(CoursePage) |
+ |
12 |
admin.site.register(Group) |
+ |
13 |
admin.site.register(AccountCollection) |
+ |
14 |
#admin.site.register(Vote) # Not really necessary |
+ |
15 |
admin.site.register(SharedFile) |
+ |
16 |
admin.site.register(Message) |
+ |
17 |
#admin.site.register(Chat) # Abstract |
+ |
18 |
admin.site.register(GroupChat) |
+ |
19 |
admin.site.register(PrivateChat) |
+ |
20 |
admin.site.register(GroupInvite) |
+ |
21 |
agora/migrations/0001_initial.py ¶
204 additions and 0 deletions.
View changes Hide changes
+ |
1 |
|
+ |
2 |
import agora.models |
+ |
3 |
from django.conf import settings |
+ |
4 |
from django.db import migrations, models |
+ |
5 |
import django.db.models.deletion |
+ |
6 |
|
+ |
7 |
|
+ |
8 |
class Migration(migrations.Migration): |
+ |
9 |
|
+ |
10 |
initial = True |
+ |
11 |
|
+ |
12 |
dependencies = [ |
+ |
13 |
('administration', '0001_initial'), |
+ |
14 |
] |
+ |
15 |
|
+ |
16 |
operations = [ |
+ |
17 |
migrations.CreateModel( |
+ |
18 |
name='Account', |
+ |
19 |
fields=[ |
+ |
20 |
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL)), |
+ |
21 |
('alias', models.CharField(max_length=64, unique=True)), |
+ |
22 |
], |
+ |
23 |
), |
+ |
24 |
migrations.CreateModel( |
+ |
25 |
name='AccountCollection', |
+ |
26 |
fields=[ |
+ |
27 |
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
+ |
28 |
('name', models.CharField(help_text='The name of this collection.', max_length=32)), |
+ |
29 |
('visible_to_public', models.BooleanField(default=False, help_text='Make this collection visible to everybody.')), |
+ |
30 |
('visible_to_collection', models.BooleanField(default=True, help_text='Make this collection visible to the accounts in this collection. Other collections are not affected by this.')), |
+ |
31 |
('account', models.ForeignKey(editable=False, help_text='The account that created this collection.', on_delete=django.db.models.deletion.CASCADE, to='agora.Account')), |
+ |
32 |
('accounts', models.ManyToManyField(help_text='All accounts that are part of this collection.', related_name='in_collection', to='agora.Account')), |
+ |
33 |
], |
+ |
34 |
), |
+ |
35 |
migrations.CreateModel( |
+ |
36 |
name='AccountSettings', |
+ |
37 |
fields=[ |
+ |
38 |
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
+ |
39 |
('color', models.CharField(default='E73B2B', help_text='The hexadecimal code of the color for this account.', max_length=6, validators=[agora.models.validate_hex_color])), |
+ |
40 |
('account_page_banner', models.ImageField(help_text="The banner image to be shown on this account's homepage.", upload_to=agora.models.account_user_directory)), |
+ |
41 |
('avatar', models.ImageField(help_text='The avatar image of this account.', upload_to=agora.models.account_user_directory)), |
+ |
42 |
('account', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='agora.Account')), |
+ |
43 |
], |
+ |
44 |
), |
+ |
45 |
migrations.CreateModel( |
+ |
46 |
name='Group', |
+ |
47 |
fields=[ |
+ |
48 |
('name', models.CharField(help_text='The name of your group.', max_length=64, primary_key=True, serialize=False)), |
+ |
49 |
('color', models.CharField(default='E73B2B', help_text='The hexadecimal code of the color for this group.', max_length=6, validators=[agora.models.validate_hex_color])), |
+ |
50 |
('invite_only', models.BooleanField(default=True, help_text='Determines if everyone can join this group, or if only members can invite others.')), |
+ |
51 |
('private', models.BooleanField(default=True, help_text='Determines if this group is visible to non-members.')), |
+ |
52 |
('members', models.ManyToManyField(help_text='The members of this group.', to='agora.Account')), |
+ |
53 |
], |
+ |
54 |
), |
+ |
55 |
migrations.CreateModel( |
+ |
56 |
name='GroupChat', |
+ |
57 |
fields=[ |
+ |
58 |
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
+ |
59 |
('group', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to='agora.Group')), |
+ |
60 |
], |
+ |
61 |
options={ |
+ |
62 |
'abstract': False, |
+ |
63 |
}, |
+ |
64 |
), |
+ |
65 |
migrations.CreateModel( |
+ |
66 |
name='GroupInvite', |
+ |
67 |
fields=[ |
+ |
68 |
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
+ |
69 |
('accepted', models.NullBooleanField(default=None, help_text='Indicates if the invitation was accepted, rejected, or pending an answer. if somebody rejects the invitation, that group can no longer send an invitation to the invitee, unless (s)he removes the answer from her history. Also, a person can not reject an invitation, and accept it later. For that, a new invitation must be received.')), |
+ |
70 |
('group', models.ForeignKey(editable=False, help_text='The group for which this invitation is.', on_delete=django.db.models.deletion.CASCADE, to='agora.Group')), |
+ |
71 |
('invitee', models.ForeignKey(editable=False, help_text='The account which will receive the invitation.', on_delete=django.db.models.deletion.CASCADE, related_name='invitee', to='agora.Account')), |
+ |
72 |
('inviter', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to='agora.Account')), |
+ |
73 |
], |
+ |
74 |
), |
+ |
75 |
migrations.CreateModel( |
+ |
76 |
name='Message', |
+ |
77 |
fields=[ |
+ |
78 |
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
+ |
79 |
('timestamp', models.DateTimeField(auto_now_add=True)), |
+ |
80 |
('text', models.TextField()), |
+ |
81 |
('sender', models.ForeignKey(editable=False, help_text='The account that sent this message.', on_delete=django.db.models.deletion.CASCADE, to='agora.Account')), |
+ |
82 |
], |
+ |
83 |
), |
+ |
84 |
migrations.CreateModel( |
+ |
85 |
name='Page', |
+ |
86 |
fields=[ |
+ |
87 |
('name', models.CharField(help_text='The name of this page.', max_length=64, primary_key=True, serialize=False)), |
+ |
88 |
('created', models.DateTimeField(auto_now_add=True)), |
+ |
89 |
('hidden', models.BooleanField(default=False, help_text='Determines if this page can be found without a direct link.')), |
+ |
90 |
('main_content', models.TextField(blank=True, help_text='If you want to put some text on this page, you can put it here. You can use Orgmode-syntax to get as much out of your page as possible. While doing so, be aware of the limitations imposed by the code of conduct.')), |
+ |
91 |
('public_posting', models.BooleanField(default=True, help_text='Determines if everyone can post on this page, or only the people that are linked with it. Know that if a post is made and responding is allowed, everyone can respond to that post.')), |
+ |
92 |
], |
+ |
93 |
), |
+ |
94 |
migrations.CreateModel( |
+ |
95 |
name='Post', |
+ |
96 |
fields=[ |
+ |
97 |
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
+ |
98 |
('timestamp', models.DateTimeField(auto_now_add=True)), |
+ |
99 |
('title', models.CharField(blank=True, help_text='The title for this post.', max_length=64)), |
+ |
100 |
('text', models.TextField(blank=True, help_text='A text message for this post. May be left blank.')), |
+ |
101 |
('allow_votes', models.BooleanField(default=True, help_text='Decide whether to allow voting or disable it for this post.')), |
+ |
102 |
('allow_responses', models.BooleanField(default=True, help_text='Decide if other people can respond to this post or not. This does not influence what people allow on their posts.')), |
+ |
103 |
], |
+ |
104 |
), |
+ |
105 |
migrations.CreateModel( |
+ |
106 |
name='PrivateChat', |
+ |
107 |
fields=[ |
+ |
108 |
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
+ |
109 |
('account1', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to='agora.Account')), |
+ |
110 |
('account2', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='correspondent', to='agora.Account')), |
+ |
111 |
('messages', models.ManyToManyField(help_text='All messages that were shared in this chat.', to='agora.Message')), |
+ |
112 |
], |
+ |
113 |
options={ |
+ |
114 |
'abstract': False, |
+ |
115 |
}, |
+ |
116 |
), |
+ |
117 |
migrations.CreateModel( |
+ |
118 |
name='SharedFile', |
+ |
119 |
fields=[ |
+ |
120 |
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
+ |
121 |
('timestamp', models.DateTimeField(auto_now_add=True)), |
+ |
122 |
('file', models.FileField(editable=False, help_text='The file you want to share.', upload_to='agora/chat/%Y/%m/%d/')), |
+ |
123 |
('uploader', models.ForeignKey(editable=False, help_text='The account that uploaded this file.', on_delete=django.db.models.deletion.CASCADE, to='agora.Account')), |
+ |
124 |
], |
+ |
125 |
), |
+ |
126 |
migrations.CreateModel( |
+ |
127 |
name='Vote', |
+ |
128 |
fields=[ |
+ |
129 |
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
+ |
130 |
], |
+ |
131 |
), |
+ |
132 |
migrations.CreateModel( |
+ |
133 |
name='AccountPage', |
+ |
134 |
fields=[ |
+ |
135 |
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='agora.Page')), |
+ |
136 |
], |
+ |
137 |
bases=('agora.page',), |
+ |
138 |
), |
+ |
139 |
migrations.CreateModel( |
+ |
140 |
name='CoursePage', |
+ |
141 |
fields=[ |
+ |
142 |
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='agora.Page')), |
+ |
143 |
], |
+ |
144 |
bases=('agora.page',), |
+ |
145 |
), |
+ |
146 |
migrations.CreateModel( |
+ |
147 |
name='FilePost', |
+ |
148 |
fields=[ |
+ |
149 |
('post_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='agora.Post')), |
+ |
150 |
('file', models.FileField(editable=False, help_text='The file you wish to share.', upload_to='agora/posts/%Y/%m/%d/')), |
+ |
151 |
('file_type', models.CharField(choices=[('image', 'Image'), ('video', 'Video'), ('music', 'Sound'), ('text', 'Text'), ('other', 'Other')], help_text='How this file should be seen as.', max_length=16)), |
+ |
152 |
], |
+ |
153 |
bases=('agora.post',), |
+ |
154 |
), |
+ |
155 |
migrations.CreateModel( |
+ |
156 |
name='GroupPage', |
+ |
157 |
fields=[ |
+ |
158 |
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='agora.Page')), |
+ |
159 |
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='agora.Group')), |
+ |
160 |
], |
+ |
161 |
bases=('agora.page',), |
+ |
162 |
), |
+ |
163 |
migrations.AddField( |
+ |
164 |
model_name='vote', |
+ |
165 |
name='post', |
+ |
166 |
field=models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to='agora.Post'), |
+ |
167 |
), |
+ |
168 |
migrations.AddField( |
+ |
169 |
model_name='vote', |
+ |
170 |
name='voter', |
+ |
171 |
field=models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to='agora.Account'), |
+ |
172 |
), |
+ |
173 |
migrations.AddField( |
+ |
174 |
model_name='privatechat', |
+ |
175 |
name='shared_files', |
+ |
176 |
field=models.ManyToManyField(help_text='The files that are shared in this chat.', to='agora.SharedFile'), |
+ |
177 |
), |
+ |
178 |
migrations.AddField( |
+ |
179 |
model_name='post', |
+ |
180 |
name='author', |
+ |
181 |
field=models.ForeignKey(editable=False, help_text='The authoring account of this post.', on_delete=django.db.models.deletion.CASCADE, to='agora.Account'), |
+ |
182 |
), |
+ |
183 |
migrations.AddField( |
+ |
184 |
model_name='post', |
+ |
185 |
name='placed_on', |
+ |
186 |
field=models.ForeignKey(editable=False, help_text='The page on which this post was placed.', on_delete=django.db.models.deletion.CASCADE, to='agora.Page'), |
+ |
187 |
), |
+ |
188 |
migrations.AddField( |
+ |
189 |
model_name='post', |
+ |
190 |
name='response_to', |
+ |
191 |
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, to='agora.Post'), |
+ |
192 |
), |
+ |
193 |
migrations.AddField( |
+ |
194 |
model_name='groupchat', |
+ |
195 |
name='messages', |
+ |
196 |
field=models.ManyToManyField(help_text='All messages that were shared in this chat.', to='agora.Message'), |
+ |
197 |
), |
+ |
198 |
migrations.AddField( |
+ |
199 |
model_name='groupchat', |
+ |
200 |
name='shared_files', |
+ |
201 |
field=models.ManyToManyField(help_text='The files that are shared in this chat.', to='agora.SharedFile'), |
+ |
202 |
), |
+ |
203 |
] |
+ |
204 |
agora/migrations/0002_auto_20171118_2203.py ¶
27 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 |
initial = True |
+ |
9 |
|
+ |
10 |
dependencies = [ |
+ |
11 |
('agora', '0001_initial'), |
+ |
12 |
('courses', '0001_initial'), |
+ |
13 |
] |
+ |
14 |
|
+ |
15 |
operations = [ |
+ |
16 |
migrations.AddField( |
+ |
17 |
model_name='coursepage', |
+ |
18 |
name='course', |
+ |
19 |
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='courses.Course'), |
+ |
20 |
), |
+ |
21 |
migrations.AddField( |
+ |
22 |
model_name='accountpage', |
+ |
23 |
name='account', |
+ |
24 |
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='agora.Account'), |
+ |
25 |
), |
+ |
26 |
] |
+ |
27 |
agora/models.py ¶
12 additions and 3 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 |
pass # TODO |
+ |
6 |
|
+ |
7 |
class Account(models.Model): |
5 |
8 |
user = models.OneToOneField( |
6 |
9 |
'joeni.User', |
7 |
- | on_delete=models.CASCADE, |
+ |
10 |
on_delete=models.CASCADE, |
8 |
11 |
primary_key=True, |
9 |
12 |
) |
10 |
13 |
alias = models.CharField(max_length=64, unique=True) |
11 |
14 |
|
12 |
15 |
def __str__(self): |
13 |
16 |
return self.alias |
14 |
17 |
|
15 |
18 |
def account_user_directory(instance, filename): |
16 |
19 |
return '{0}/account/settings/{1}'.format(instace.account.alias, filename) |
17 |
20 |
|
18 |
21 |
class AccountSettings(models.Model): |
19 |
22 |
account = models.OneToOneField( |
20 |
23 |
'Account', |
21 |
24 |
on_delete=models.CASCADE, |
22 |
25 |
) |
23 |
26 |
# TODO: Build validator for primary_color to make sure what is given is a |
24 |
27 |
# valid hexadecimal RGB value. |
25 |
28 |
color = models.CharField( |
26 |
29 |
max_length=6, |
27 |
30 |
help_text=_("The hexadecimal code of the color for this account."), |
28 |
31 |
default = constants.COLORS["UHasselt default"], |
29 |
32 |
blank=False, |
30 |
33 |
validators=[validate_hex_color], |
31 |
34 |
) |
32 |
35 |
account_page_banner = models.ImageField( # Requires the Pillow library! |
33 |
36 |
upload_to=account_user_directory, |
34 |
37 |
help_text=_("The banner image to be shown on this account's homepage."), |
35 |
38 |
) |
36 |
39 |
avatar = models.ImageField( |
37 |
40 |
upload_to=account_user_directory, |
38 |
41 |
help_text=_("The avatar image of this account."), |
39 |
42 |
) |
40 |
43 |
|
41 |
44 |
def __str__(self): |
42 |
45 |
return str(self.account) |
43 |
46 |
|
44 |
47 |
class Post(models.Model): |
45 |
48 |
timestamp = models.DateTimeField(auto_now_add=True) |
46 |
49 |
title = models.CharField( |
47 |
50 |
max_length=64, |
48 |
51 |
blank=True, |
49 |
52 |
help_text=_("The title for this post."), |
50 |
53 |
) |
51 |
54 |
text = models.TextField( |
52 |
55 |
blank=True, |
53 |
56 |
help_text=_("A text message for this post. May be left blank."), |
54 |
57 |
) |
55 |
58 |
author = models.ForeignKey( |
56 |
59 |
"Account", |
57 |
60 |
on_delete=models.CASCADE, |
58 |
61 |
null=False, # There must be an author |
59 |
62 |
editable=False, # It makes no sense to change the author after creation |
60 |
63 |
help_text=_("The authoring account of this post."), |
61 |
64 |
) |
62 |
65 |
response_to = models.ForeignKey( |
63 |
66 |
"self", |
64 |
67 |
on_delete=models.CASCADE, |
65 |
68 |
null=True, # If this is null, this post is not a response, but a beginning post |
66 |
69 |
editable=False, # This cannot be changed after creation, wouldn't make sense |
67 |
70 |
help_text=_("The post to which this was a response, if applicable."), |
68 |
71 |
) |
69 |
72 |
placed_on = models.ForeignKey( |
70 |
73 |
"Page", |
71 |
74 |
on_delete=models.CASCADE, |
72 |
75 |
null=False, |
73 |
76 |
editable=False, |
74 |
77 |
help_text=_("The page on which this post was placed."), |
75 |
78 |
) |
76 |
79 |
# Voting fields |
77 |
80 |
allow_votes = models.BooleanField( |
78 |
81 |
default=True, |
79 |
82 |
help_text=_("Decide whether to allow voting or disable it for this post."), |
80 |
83 |
) |
81 |
84 |
allow_responses = models.BooleanField( |
82 |
85 |
default=True, |
83 |
86 |
help_text=_("Decide if other people can respond to this post or not. " |
84 |
87 |
"This does not influence what people allow on their posts."), |
85 |
88 |
) |
86 |
89 |
|
87 |
90 |
def __str__(self): |
88 |
91 |
return str(self.timestamp) + " | " + str(self.author) |
89 |
92 |
# TODO Add a way to attach geographical data to a post, which could |
90 |
93 |
# then be used with OpenStreetMap or something |
91 |
94 |
|
92 |
95 |
class FilePost(Post): |
93 |
96 |
""" A special type of Post, which has a file linked with it. |
94 |
97 |
The poster can specify how to treat this file. """ |
95 |
98 |
image = _("Image") |
96 |
99 |
video = _("Video") |
97 |
100 |
music = _("Sound") |
98 |
101 |
text = _("Text" ) |
99 |
102 |
other = _("Other") |
100 |
103 |
file = models.FileField( |
101 |
104 |
upload_to="agora/posts/%Y/%m/%d/", |
102 |
105 |
null=False, |
103 |
106 |
editable=False, |
104 |
107 |
help_text=_("The file you wish to share."), |
105 |
108 |
) |
106 |
109 |
file_type = models.CharField( |
107 |
110 |
max_length=16, |
108 |
111 |
blank=False, |
109 |
112 |
choices = ( |
110 |
113 |
('image', image), |
111 |
114 |
('video', video), |
112 |
115 |
('music', music), |
113 |
116 |
('text' , text ), |
114 |
117 |
('other', other), |
115 |
118 |
), |
116 |
119 |
help_text=_("How this file should be seen as."), |
117 |
120 |
) |
118 |
121 |
|
119 |
122 |
class Page(models.Model): |
120 |
123 |
""" In the university, people can create pages for everything they want and |
121 |
124 |
then some. These pages are put in the database through this table. """ |
122 |
125 |
name = models.CharField( |
123 |
126 |
max_length=64, |
124 |
127 |
primary_key=True, |
125 |
128 |
blank=False, |
126 |
129 |
help_text=_("The name of this page."), |
127 |
130 |
) |
128 |
131 |
created = models.DateTimeField(auto_now_add=True) |
129 |
132 |
hidden = models.BooleanField( |
130 |
133 |
default=False, |
131 |
134 |
help_text=_("Determines if this page can be found without a direct link."), |
132 |
135 |
) |
133 |
136 |
main_content = models.TextField( |
134 |
137 |
blank=True, |
135 |
138 |
help_text=_("If you want to put some text on this page, " |
136 |
139 |
"you can put it here. You can use Orgmode-syntax to " |
137 |
140 |
"get as much out of your page as possible. While doing so, " |
138 |
141 |
"be aware of the limitations imposed by the code of conduct."), |
139 |
142 |
) |
140 |
143 |
public_posting = models.BooleanField( |
141 |
144 |
default=True, |
142 |
145 |
help_text=_("Determines if everyone can post on this page, or only the " |
143 |
146 |
"people that are linked with it. Know that if a post is made " |
144 |
147 |
"and responding is allowed, everyone can respond to that post."), |
145 |
148 |
) |
146 |
149 |
|
147 |
150 |
class Meta: |
148 |
- | abstract=True |
149 |
- | |
+ |
151 |
abstract=True""" |
+ |
152 |
""" I've disabled the abstraction, because otherwise I can't relate a Post to a Page. """ |
+ |
153 |
|
150 |
154 |
class AccountPage(Page): |
151 |
155 |
""" Every account has its own homepage. This is that page. |
152 |
156 |
This page can only be edited by the account holder, or staff members. """ |
153 |
157 |
# TODO: Find a way to auto-create one of these every time a new account is created |
154 |
158 |
# TODO: Require that changes can only occur by either the account holder or staff |
155 |
159 |
account = models.OneToOneField( |
156 |
160 |
"Account", |
157 |
161 |
null=False, |
158 |
162 |
on_delete=models.CASCADE, |
159 |
163 |
) |
160 |
164 |
|
161 |
165 |
class GroupPage(Page): |
162 |
166 |
""" A page where a group can present itself to the university. |
163 |
167 |
This page can only be edited by group members or staff members. """ |
164 |
168 |
# TODO: Find a way to auto-create one of these every time a new group is created |
165 |
169 |
# TODO: Require that changes can only occur by either the group or staff |
166 |
170 |
group = models.ForeignKey( |
167 |
171 |
"Group", |
168 |
172 |
null=False, |
169 |
173 |
on_delete=models.CASCADE, |
170 |
174 |
) |
171 |
175 |
|
172 |
176 |
class CoursePage(Page): |
173 |
177 |
""" A page that serves as a course's main entry point. |
174 |
178 |
This page can only be edited by the course's educating team or staff members. """ |
175 |
179 |
# TODO: Find a way to auto-create one of these every time a new course is created |
176 |
180 |
# TODO: Require that changes can only occur by either the course team or staff |
177 |
181 |
course = models.OneToOneField( |
178 |
182 |
"courses.Course", |
179 |
183 |
null=False, |
180 |
184 |
on_delete=models.CASCADE, |
181 |
185 |
) |
182 |
186 |
|
183 |
187 |
class Group(models.Model): |
184 |
188 |
""" It is imperative that everyone can come together with other people. |
185 |
189 |
A Group record is the way to accomplish this. """ |
186 |
190 |
name = models.CharField( |
187 |
191 |
max_length=64, |
188 |
192 |
primary_key=True, # So be unique I'd say |
189 |
193 |
blank=False, |
190 |
194 |
help_text=_("The name of your group."), |
191 |
195 |
) |
192 |
196 |
color = models.CharField( |
193 |
197 |
max_length=6, |
194 |
198 |
help_text=_("The hexadecimal code of the color for this group."), |
195 |
199 |
default = constants.COLORS["UHasselt default"], |
196 |
200 |
blank=False, |
197 |
201 |
validators=[validate_hex_color], |
198 |
202 |
) |
199 |
203 |
members = models.ManyToManyField( |
200 |
204 |
"Account", |
201 |
205 |
help_text=_("The members of this group."), |
202 |
206 |
) |
203 |
207 |
invite_only = models.BooleanField( |
204 |
208 |
default=True, |
205 |
209 |
help_text=_("Determines if everyone can join this group, or if " |
206 |
210 |
"only members can invite others."), |
207 |
211 |
) |
208 |
212 |
private = models.BooleanField( |
209 |
213 |
default=True, |
210 |
214 |
help_text=_("Determines if this group is visible to non-members."), |
211 |
215 |
) |
212 |
216 |
|
213 |
217 |
def __str__(self): |
214 |
218 |
return self.name |
215 |
219 |
|
216 |
220 |
|
217 |
221 |
class AccountCollection(models.Model): |
218 |
222 |
""" Every account can make a collection in which (s)he can list accounts |
219 |
223 |
at his/her wish. This can be a collection of Friends, study collegues, |
220 |
224 |
project partners, and so on. |
221 |
225 |
Accounts that are in a certain collection are not notified of this. |
222 |
226 |
However, there is one exception: |
223 |
227 |
If both accounts have a collection named "Friends" (or the localized |
224 |
228 |
equivalent), and both feature each other in that collection, then |
225 |
229 |
this is shared between the two accounts. """ |
226 |
230 |
account = models.ForeignKey( |
227 |
231 |
"Account", |
228 |
232 |
null=False, |
229 |
233 |
editable=False, |
230 |
234 |
on_delete=models.CASCADE, |
231 |
235 |
help_text=_("The account that created this collection."), |
232 |
236 |
) |
233 |
237 |
name = models.CharField( |
234 |
238 |
max_length=32, |
235 |
239 |
blank=False, |
236 |
240 |
help_text=_("The name of this collection."), |
237 |
241 |
) |
238 |
242 |
accounts = models.ManyToManyField( |
239 |
243 |
"Account", |
240 |
244 |
help_text=_("All accounts that are part of this collection."), |
241 |
245 |
) |
+ |
246 |
) |
242 |
247 |
visible_to_public = models.BooleanField( |
243 |
248 |
default=False, |
244 |
249 |
help_text=_("Make this collection visible to everybody."), |
245 |
250 |
) |
246 |
251 |
visible_to_collection = models.BooleanField( |
247 |
252 |
default=True, |
248 |
253 |
help_text=_("Make this collection visible to the accounts in this collection. Other collections are not affected by this."), |
249 |
254 |
) |
250 |
255 |
|
251 |
256 |
def __str__(self): |
252 |
257 |
return str(self.account) + " | " + self.name |
253 |
258 |
|
254 |
259 |
class Vote(models.Model): |
255 |
260 |
""" Accounts can vote on posts (using ▲, which is funny because UHasselt). |
256 |
261 |
These votes are registered in this table. """ |
257 |
262 |
voter = models.ForeignKey( |
258 |
263 |
"Account", |
259 |
264 |
null=False, |
260 |
265 |
editable=False, |
261 |
266 |
on_delete=models.CASCADE, |
262 |
267 |
) |
263 |
268 |
post = models.ForeignKey( |
264 |
269 |
"Post", |
265 |
270 |
null=False, # Duh. |
266 |
271 |
editable=False, # Transferring votes doesn't make sense |
267 |
272 |
on_delete=models.CASCADE, |
268 |
273 |
) |
269 |
274 |
|
270 |
275 |
class SharedFile(models.Model): |
271 |
276 |
""" Groups and people can share files with each other, through a chat system. |
272 |
277 |
These files are represented here. """ |
273 |
278 |
timestamp = models.DateTimeField(auto_now_add=True) |
274 |
279 |
file = models.FileField( |
275 |
280 |
upload_to="agora/chat/%Y/%m/%d/", |
276 |
281 |
null=False, |
277 |
282 |
editable=False, |
278 |
283 |
help_text=_("The file you want to share."), |
279 |
284 |
) |
280 |
285 |
uploader = models.ForeignKey( |
281 |
286 |
"Account", |
282 |
287 |
on_delete=models.CASCADE, |
283 |
288 |
null=False, |
284 |
289 |
editable=False, |
285 |
290 |
help_text=_("The account that uploaded this file."), |
286 |
291 |
) |
287 |
292 |
# TODO __str__ ... or maybe not? |
288 |
293 |
|
289 |
294 |
class Message(models.Model): |
290 |
295 |
""" Everyone can communicate with someone else using private messages. |
291 |
296 |
These messages are recorded here. """ |
292 |
297 |
timestamp = models.DateTimeField(auto_now_add=True) |
293 |
298 |
text = models.TextField( |
294 |
299 |
blank=False, |
295 |
300 |
) |
296 |
301 |
sender = models.ForeignKey( |
297 |
302 |
"Account", |
298 |
303 |
on_delete=models.CASCADE, |
299 |
304 |
null=False, |
300 |
305 |
editable=False, |
301 |
306 |
help_text=_("The account that sent this message."), |
302 |
307 |
) |
303 |
308 |
# TODO __str__ |
304 |
309 |
|
305 |
310 |
|
306 |
311 |
class Chat(models.Model): |
307 |
312 |
""" Chats can happen between a group, or between two people in private. |
308 |
313 |
These messages are connected to a particular chat. """ |
309 |
314 |
messages = models.ManyToManyField( |
310 |
315 |
"Message", |
311 |
316 |
help_text=_("All messages that were shared in this chat."), |
312 |
317 |
) |
313 |
318 |
shared_files = models.ManyToManyField( |
314 |
319 |
"SharedFile", |
315 |
320 |
help_text=_("The files that are shared in this chat."), |
316 |
321 |
) |
317 |
322 |
class Meta: |
318 |
323 |
abstract=True |
319 |
324 |
|
320 |
325 |
class GroupChat(Chat): |
321 |
326 |
""" Represents a chat of a group. """ |
322 |
327 |
group = models.ForeignKey( |
323 |
328 |
"Group", |
324 |
329 |
on_delete=models.CASCADE, |
325 |
330 |
null=False, |
326 |
331 |
editable=False, |
327 |
332 |
) |
328 |
333 |
|
329 |
334 |
def __str__(self): |
330 |
335 |
return str(self.group) |
331 |
336 |
|
332 |
337 |
class PrivateChat(Chat): |
333 |
338 |
""" Represents a private chat between two accounts. """ |
334 |
339 |
# FIXME: It's theoretically possible to start a chat by one person, and have |
335 |
340 |
# the other person start the same as well. Find a reliable way to block that. |
336 |
341 |
account1 = models.ForeignKey( |
337 |
342 |
"Account", |
338 |
343 |
on_delete=models.CASCADE, |
339 |
344 |
null=False, |
340 |
345 |
editable=False, |
341 |
346 |
) |
342 |
347 |
account2 = models.ForeignKey( |
343 |
348 |
"Account", |
344 |
349 |
on_delete=models.CASCADE, |
345 |
350 |
null=False, |
346 |
351 |
editable=False, |
347 |
352 |
) |
+ |
353 |
) |
348 |
354 |
def __str__(self): |
+ |
355 |
def __str__(self): |
349 |
356 |
return str(self.account1) +" - "+ str(self.account2) |
350 |
357 |
|
351 |
358 |
class GroupInvite(models.Model): |
352 |
359 |
""" Groups can send invites to other people to join the group, and this is |
353 |
360 |
not limited to private groups. Group invitations are stored here, as well as their outcome. """ |
354 |
361 |
inviter = models.ForeignKey( |
355 |
362 |
"Account", |
356 |
363 |
on_delete=models.CASCADE, |
357 |
364 |
null=False, |
358 |
365 |
editable=False, |
359 |
366 |
) |
360 |
367 |
invitee = models.ForeignKey( |
361 |
368 |
"Account", |
362 |
369 |
on_delete=models.CASCADE, |
363 |
370 |
null=False, |
364 |
371 |
editable=False, |
365 |
372 |
help_text=_("The account which will receive the invitation."), |
366 |
373 |
) |
+ |
374 |
) |
367 |
375 |
group = models.ForeignKey( |
368 |
376 |
"Group", |
369 |
377 |
on_delete=models.CASCADE, |
370 |
378 |
null=False, |
371 |
379 |
editable=False, |
372 |
380 |
db_index=True, |
373 |
381 |
help_text=_("The group for which this invitation is."), |
374 |
382 |
) |
375 |
383 |
accepted = models.NullBooleanField( |
376 |
384 |
default=None, |
377 |
385 |
help_text=_("Indicates if the invitation was accepted, rejected, or " |
378 |
386 |
"pending an answer. if somebody rejects the invitation, " |
379 |
387 |
"that group can no longer send an invitation to the invitee, " |
380 |
388 |
"unless (s)he removes the answer from her history. Also, " |
381 |
389 |
"a person can not reject an invitation, and accept it later. " |
382 |
390 |
"For that, a new invitation must be received."), |
383 |
391 |
) |
384 |
392 |
|
385 |
393 |
def __str__(self): |
386 |
394 |
return str(self.invitee) +" | "+ str(self.group) |
387 |
395 |
|
+ |
396 |
courses/admin.py ¶
10 additions and 1 deletion.
View changes Hide changes
1 |
1 |
|
+ |
2 |
|
2 |
3 |
# Register your models here. |
3 |
- | |
+ |
4 |
admin.site.register(Prerequisites) |
+ |
5 |
admin.site.register(ProgrammeInformation) |
+ |
6 |
admin.site.register(Study) |
+ |
7 |
admin.site.register(StudyProgramme) |
+ |
8 |
admin.site.register(Assignment) |
+ |
9 |
admin.site.register(Announcement) |
+ |
10 |
admin.site.register(Upload) |
+ |
11 |
admin.site.register(StudyGroup) |
+ |
12 |
courses/migrations/0001_initial.py ¶
145 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 |
initial = True |
+ |
10 |
|
+ |
11 |
dependencies = [ |
+ |
12 |
('agora', '0001_initial'), |
+ |
13 |
migrations.swappable_dependency(settings.AUTH_USER_MODEL), |
+ |
14 |
] |
+ |
15 |
|
+ |
16 |
operations = [ |
+ |
17 |
migrations.CreateModel( |
+ |
18 |
name='Announcement', |
+ |
19 |
fields=[ |
+ |
20 |
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
+ |
21 |
('title', models.CharField(help_text='A quick title for what this is about.', max_length=20)), |
+ |
22 |
('text', models.TextField(help_text='The announcement itself. Orgmode syntax available.')), |
+ |
23 |
('posted', models.DateTimeField(auto_now_add=True)), |
+ |
24 |
], |
+ |
25 |
), |
+ |
26 |
migrations.CreateModel( |
+ |
27 |
name='Assignment', |
+ |
28 |
fields=[ |
+ |
29 |
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
+ |
30 |
('information', models.TextField(help_text='Any additional information regarding the assignment. Orgmode syntax available.')), |
+ |
31 |
('deadline', models.DateTimeField(help_text='The date and time this task is due.')), |
+ |
32 |
('posted', models.DateField(auto_now_add=True)), |
+ |
33 |
('digital_task', models.BooleanField(default=True, help_text='This determines whether this assignment requires handing in a digital file.')), |
+ |
34 |
], |
+ |
35 |
), |
+ |
36 |
migrations.CreateModel( |
+ |
37 |
name='Course', |
+ |
38 |
fields=[ |
+ |
39 |
('number', models.PositiveSmallIntegerField(help_text="The number associated with this course. A leading '0' will be added if the number is smaller than 1000.", primary_key=True, serialize=False)), |
+ |
40 |
('name', models.CharField(help_text='The name of this course, in the language that it is taught. Translations are for the appropriate template.', max_length=64)), |
+ |
41 |
('language', models.CharField(choices=[('NL', 'Dutch'), ('EN', 'English'), ('FR', 'French')], help_text='The language in which this course is given.', max_length=64)), |
+ |
42 |
('contact_person', models.ForeignKey(help_text='The person to contact regarding this course.', limit_choices_to={'is_staff': True}, on_delete=django.db.models.deletion.PROTECT, related_name='contact_person', to=settings.AUTH_USER_MODEL)), |
+ |
43 |
('coordinator', models.ForeignKey(help_text="The person whom's the coordinator of this course.", limit_choices_to={'is_staff': True}, on_delete=django.db.models.deletion.PROTECT, related_name='coordinator', to=settings.AUTH_USER_MODEL)), |
+ |
44 |
('educating_team', models.ManyToManyField(help_text='The team members of this course.', limit_choices_to={'is_staff': True}, related_name='educating_team', to=settings.AUTH_USER_MODEL)), |
+ |
45 |
], |
+ |
46 |
), |
+ |
47 |
migrations.CreateModel( |
+ |
48 |
name='Prerequisites', |
+ |
49 |
fields=[ |
+ |
50 |
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
+ |
51 |
('name', models.CharField(blank=True, help_text='To specify a name for this set, if necessary.', max_length=64)), |
+ |
52 |
('ECTS_for_required_study', models.PositiveSmallIntegerField(help_text='The amount of obtained ECTS points for the required course, if any.', null=True)), |
+ |
53 |
('course', models.ForeignKey(help_text='The course that these prerequisites are for.', on_delete=django.db.models.deletion.CASCADE, related_name='prerequisite_course', to='courses.Course')), |
+ |
54 |
('in_curriculum', models.ManyToManyField(help_text='All courses that have to be in the curriculum to follow this. If a credit was achieved, that course can be omitted.', related_name='in_curriculum', to='courses.Course')), |
+ |
55 |
], |
+ |
56 |
), |
+ |
57 |
migrations.CreateModel( |
+ |
58 |
name='ProgrammeInformation', |
+ |
59 |
fields=[ |
+ |
60 |
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
+ |
61 |
('programme_type', models.CharField(choices=[('C', 'Compulsory'), ('O', 'Optional')], help_text='Type of this course for this study.', max_length=1)), |
+ |
62 |
('study_hours', models.PositiveSmallIntegerField(help_text='The required amount of hours to study this course.')), |
+ |
63 |
('ECTS', models.PositiveSmallIntegerField(help_text='The amount of ECTS points attached to this course.')), |
+ |
64 |
('semester', models.PositiveSmallIntegerField(choices=[(1, 'First semester'), (2, 'Second semester'), (3, 'Full year course'), (4, 'Taught in first quarter'), (5, 'Taught in second quarter'), (6, 'Taught in third quarter'), (7, 'Taught in fourth quarter')], help_text='The period in which this course is being taught in this study.')), |
+ |
65 |
('year', models.PositiveSmallIntegerField(help_text='The year in which this course is taught for this study.')), |
+ |
66 |
('second_chance', models.BooleanField(default=True, help_text='Defines if a second chance exam is planned for this course.')), |
+ |
67 |
('tolerable', models.BooleanField(default=True, help_text='Defines if a failed result can be tolerated.')), |
+ |
68 |
('scoring', models.CharField(choices=[('N', 'Numerical'), ('FP', 'Fail/Pass')], default='N', help_text='How the obtained score for this course is given.', max_length=2)), |
+ |
69 |
('course', models.ForeignKey(help_text='The course that this information is for.', on_delete=django.db.models.deletion.CASCADE, to='courses.Course')), |
+ |
70 |
], |
+ |
71 |
), |
+ |
72 |
migrations.CreateModel( |
+ |
73 |
name='Study', |
+ |
74 |
fields=[ |
+ |
75 |
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
+ |
76 |
('name', models.CharField(help_text="The full name of this study, in the language it's taught in.", max_length=128, unique=True)), |
+ |
77 |
('degree_type', models.CharField(choices=[('BSc', 'Bachelor of Science'), ('MSc', 'Master of Science'), ('LL.B', 'Bachelor of Laws'), ('LL.M', 'Master of Laws'), ('ir.', 'Engineer'), ('ing.', 'Technological Engineer')], help_text='The type of degree one obtains upon passing this study.', max_length=64)), |
+ |
78 |
('language', models.CharField(choices=[('NL', 'Dutch'), ('EN', 'English'), ('FR', 'French')], help_text='The language in which this study is given.', max_length=64)), |
+ |
79 |
('faculty', models.CharField(choices=[('FoS', 'Faculty of Sciences'), ('FoTS', 'Faculty of Transportation Sciences'), ('FoAaA', 'Faculty of Architecture and Arts'), ('FoBE', 'Faculty of Business Economics'), ('FoMaLS', 'Faculty of Medicine and Life Sciences'), ('FoET', 'Faculty of Engineering Technology'), ('FoL', 'Faculty of Law')], help_text='The faculty where this study belongs to.', max_length=6)), |
+ |
80 |
('additional_members', models.ManyToManyField(help_text='All the other members of the exam committee.', limit_choices_to={'is_staff': True}, related_name='additional_members', to=settings.AUTH_USER_MODEL)), |
+ |
81 |
('chairman', models.ForeignKey(help_text='The chairman of this study.', limit_choices_to={'is_staff': True}, on_delete=django.db.models.deletion.PROTECT, related_name='chairman', to=settings.AUTH_USER_MODEL)), |
+ |
82 |
('ombuds', models.ForeignKey(help_text='The ombuds person of this study.', limit_choices_to={'is_staff': True}, on_delete=django.db.models.deletion.PROTECT, related_name='ombuds', to=settings.AUTH_USER_MODEL)), |
+ |
83 |
('secretary', models.ForeignKey(help_text='The secretary of this study.', limit_choices_to={'is_staff': True}, on_delete=django.db.models.deletion.PROTECT, related_name='secretary', to=settings.AUTH_USER_MODEL)), |
+ |
84 |
('vice_chairman', models.ForeignKey(help_text='The vice-chairman of this study.', limit_choices_to={'is_staff': True}, on_delete=django.db.models.deletion.PROTECT, related_name='vice_chairman', to=settings.AUTH_USER_MODEL)), |
+ |
85 |
('vice_ombuds', models.ForeignKey(help_text='The (replacing) ombuds person of this study.', limit_choices_to={'is_staff': True}, on_delete=django.db.models.deletion.PROTECT, related_name='vice_ombuds', to=settings.AUTH_USER_MODEL)), |
+ |
86 |
], |
+ |
87 |
), |
+ |
88 |
migrations.CreateModel( |
+ |
89 |
name='StudyGroup', |
+ |
90 |
fields=[ |
+ |
91 |
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
+ |
92 |
('course', models.ForeignKey(editable=False, help_text='The course for which this group is.', on_delete=django.db.models.deletion.CASCADE, to='courses.Course')), |
+ |
93 |
('group', models.ForeignKey(editable=False, help_text='The group that will be seen as the study group.', on_delete=django.db.models.deletion.PROTECT, to='agora.Group')), |
+ |
94 |
], |
+ |
95 |
), |
+ |
96 |
migrations.CreateModel( |
+ |
97 |
name='StudyProgramme', |
+ |
98 |
fields=[ |
+ |
99 |
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
+ |
100 |
('name', models.CharField(help_text='The name of this programme.', max_length=64)), |
+ |
101 |
], |
+ |
102 |
), |
+ |
103 |
migrations.CreateModel( |
+ |
104 |
name='Upload', |
+ |
105 |
fields=[ |
+ |
106 |
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
+ |
107 |
('upload_time', models.DateTimeField(auto_now_add=True)), |
+ |
108 |
('comment', models.TextField(blank=True, help_text='If you wish to add an additional comment, state it here.')), |
+ |
109 |
('file', models.FileField(editable=False, help_text='The file you want to upload for this assignment.', upload_to='assignments/uploads/%Y/%m/')), |
+ |
110 |
('assignment', models.ForeignKey(editable=False, help_text='For which assignment this upload is.', limit_choices_to={'digital_task': True}, on_delete=django.db.models.deletion.CASCADE, to='courses.Assignment')), |
+ |
111 |
('student', models.ForeignKey(editable=False, 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)), |
+ |
112 |
], |
+ |
113 |
), |
+ |
114 |
migrations.AddField( |
+ |
115 |
model_name='programmeinformation', |
+ |
116 |
name='study', |
+ |
117 |
field=models.ForeignKey(help_text='The study in which the course is taught.', on_delete=django.db.models.deletion.CASCADE, to='courses.Study'), |
+ |
118 |
), |
+ |
119 |
migrations.AddField( |
+ |
120 |
model_name='programmeinformation', |
+ |
121 |
name='study_programme', |
+ |
122 |
field=models.ForeignKey(help_text='The study programme that this course belongs to.', on_delete=django.db.models.deletion.CASCADE, to='courses.StudyProgramme'), |
+ |
123 |
), |
+ |
124 |
migrations.AddField( |
+ |
125 |
model_name='prerequisites', |
+ |
126 |
name='required_study', |
+ |
127 |
field=models.ForeignKey(help_text='If one must have a certain amount of obtained ECTS points for a particular course, state that course here.', null=True, on_delete=django.db.models.deletion.CASCADE, to='courses.Study'), |
+ |
128 |
), |
+ |
129 |
migrations.AddField( |
+ |
130 |
model_name='prerequisites', |
+ |
131 |
name='sequentialities', |
+ |
132 |
field=models.ManyToManyField(help_text="All courses for which a credit must've been received in order to follow the course.", related_name='sequentialities', to='courses.Course'), |
+ |
133 |
), |
+ |
134 |
migrations.AddField( |
+ |
135 |
model_name='assignment', |
+ |
136 |
name='course', |
+ |
137 |
field=models.ForeignKey(editable=False, help_text='The course for which this task is assigned.', on_delete=django.db.models.deletion.CASCADE, to='courses.Course'), |
+ |
138 |
), |
+ |
139 |
migrations.AddField( |
+ |
140 |
model_name='announcement', |
+ |
141 |
name='course', |
+ |
142 |
field=models.ForeignKey(editable=False, help_text='The course for which this announcement is made.', on_delete=django.db.models.deletion.CASCADE, to='courses.Course'), |
+ |
143 |
), |
+ |
144 |
] |
+ |
145 |
courses/models.py ¶
38 additions and 19 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 |
- | on_delete=models.PROTECT, # A course must have a contact person |
+ |
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 |
23 |
coordinator = models.ForeignKey( |
23 |
24 |
"joeni.user", |
24 |
- | on_delete=models.PROTECT, # A course must have a coordinator |
+ |
25 |
on_delete=models.PROTECT, # A course must have a coordinator |
25 |
26 |
limit_choices_to={'is_staff': True}, |
26 |
27 |
null=False, |
27 |
28 |
help_text=_("The person whom's the coordinator of this course."), |
28 |
29 |
) |
+ |
30 |
) |
29 |
31 |
educating_team = models.ManyToManyField( |
30 |
32 |
"joeni.user", |
31 |
- | # No on_delete, since M->M cannot be required at database level |
+ |
33 |
# No on_delete, since M->M cannot be required at database level |
32 |
34 |
limit_choices_to={'is_staff': True}, |
33 |
35 |
null=False, |
34 |
36 |
help_text=_("The team members of this course."), |
35 |
37 |
) |
+ |
38 |
) |
36 |
39 |
language = models.CharField( |
37 |
40 |
max_length=64, |
38 |
41 |
choices = ( |
39 |
42 |
('NL', _("Dutch")), |
40 |
43 |
('EN', _("English")), |
41 |
44 |
('FR', _("French")), |
42 |
45 |
), |
43 |
46 |
null=False, |
44 |
47 |
help_text=_("The language in which this course is given."), |
45 |
48 |
) |
46 |
49 |
requirements = models.ManyToManyField() |
47 |
- | |
48 |
50 |
def __str__(self): |
49 |
51 |
number = str(self.number) |
50 |
52 |
for i in [10,100,1000]: |
51 |
53 |
if self.number < i: |
52 |
54 |
number = "0" + number |
53 |
55 |
return "(" + number + ") " + self.name |
54 |
56 |
|
55 |
57 |
|
56 |
58 |
class Prerequisites(models.Model): |
57 |
59 |
""" Represents a collection of prerequisites a student must have obtained |
58 |
60 |
before being allowed to partake in this course. |
59 |
61 |
It's possible that, if a student has obtained credits in a certain set of |
60 |
62 |
courses, a certain part of the prerequisites do not have to be obtained. |
61 |
63 |
Because of this, make a different record for each different set. In other |
62 |
64 |
words: If one set of prerequisites is obtained, and another one isn't, BUT |
63 |
65 |
they point to the same course, the student is allowed to partake. """ |
64 |
66 |
course = models.ForeignKey( |
65 |
67 |
"Course", |
66 |
68 |
on_delete=models.CASCADE, |
67 |
69 |
null=False, |
68 |
70 |
help_text=_("The course that these prerequisites are for."), |
69 |
71 |
) |
+ |
72 |
) |
70 |
73 |
name = models.ForeignKey( |
71 |
- | blank=True, |
+ |
74 |
max_length=64, |
+ |
75 |
blank=True, |
72 |
76 |
help_text=_("To specify a name for this set, if necessary."), |
73 |
77 |
) |
74 |
78 |
sequentialities = models.ManyToManyField( |
75 |
79 |
"Course", |
76 |
80 |
help_text=_("All courses for which a credit must've been received in order to follow the course."), |
77 |
81 |
) |
+ |
82 |
) |
78 |
83 |
in_curriculum = models.ManyToManyField( |
79 |
84 |
"Course", |
80 |
85 |
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 |
86 |
) |
+ |
87 |
) |
82 |
88 |
required_study = models.ForeignKey( |
83 |
89 |
"Study", |
84 |
90 |
on_delete=models.CASCADE, |
85 |
91 |
null=True, |
86 |
92 |
help_text=_("If one must have a certain amount of obtained ECTS points for a particular course, state that course here."), |
87 |
93 |
) |
88 |
94 |
ECTS_for_required_study = models.PositiveSmallIntegerField( |
89 |
95 |
null=True, |
90 |
96 |
help_text=_("The amount of obtained ECTS points for the required course, if any."), |
91 |
97 |
) |
92 |
98 |
|
93 |
99 |
def __str__(self): |
94 |
100 |
if self.name == "": |
95 |
101 |
return _("Prerequisites for %(course)s") % {'course': str(self.course)} |
96 |
102 |
else: |
97 |
103 |
return self.name + " | " + str(self.course) |
98 |
104 |
|
99 |
105 |
|
100 |
106 |
class ProgrammeInformation(models.Model): |
101 |
107 |
""" It's possible that a course is taught in multiple degree programmes; For |
102 |
108 |
example: Calculus can easily be taught to physics and mathematics students |
103 |
109 |
alike. In this table, these relations are set up, and the related properties |
104 |
110 |
are defined as well. """ |
105 |
111 |
study = models.ForeignKey( |
106 |
112 |
"Study", |
107 |
113 |
on_delete=models.CASCADE, |
108 |
114 |
null=False, |
109 |
115 |
help_text=_("The study in which the course is taught."), |
110 |
116 |
) |
111 |
117 |
course = models.ForeignKey( |
112 |
118 |
"Course", |
113 |
119 |
on_delete=models.CASCADE, |
114 |
120 |
null=False, |
115 |
121 |
help_text=_("The course that this information is for."), |
116 |
122 |
) |
117 |
123 |
study_programme = models.ForeignKey( |
118 |
124 |
"StudyProgramme", |
119 |
125 |
on_delete=models.CASCADE, |
120 |
126 |
null=False, |
121 |
127 |
help_text=_("The study programme that this course belongs to."), |
122 |
128 |
) |
123 |
129 |
programme_type = models.CharField( |
124 |
130 |
max_length=1, |
125 |
131 |
blank=False, |
126 |
132 |
choices = ( |
127 |
133 |
('C', _("Compulsory")), |
128 |
134 |
('O', _("Optional")), |
129 |
135 |
), |
130 |
136 |
help_text=_("Type of this course for this study."), |
131 |
137 |
) |
132 |
138 |
study_hours = models.PositiveSmallIntegerField( |
133 |
139 |
blank=False, |
134 |
140 |
help_text=_("The required amount of hours to study this course."), |
135 |
141 |
) |
136 |
142 |
ECTS = models.PositiveSmallIntegerField( |
137 |
143 |
blank=False, |
138 |
144 |
help_text=_("The amount of ECTS points attached to this course."), |
139 |
145 |
) |
140 |
146 |
semester = models.PositiveSmallIntegerField( |
141 |
147 |
blank=False, |
142 |
148 |
choices = ( |
143 |
149 |
(1, _("First semester")), |
144 |
150 |
(2, _("Second semester")), |
145 |
151 |
(3, _("Full year course")), |
146 |
152 |
(4, _("Taught in first quarter")), |
147 |
153 |
(5, _("Taught in second quarter")), |
148 |
154 |
(6, _("Taught in third quarter")), |
149 |
155 |
(7, _("Taught in fourth quarter")), |
150 |
156 |
), |
151 |
157 |
help_text=_("The period in which this course is being taught in this study."), |
152 |
158 |
) |
153 |
159 |
year = models.PositiveSmallIntegerField( |
154 |
160 |
blank=False, |
155 |
161 |
help_text=_("The year in which this course is taught for this study."), |
156 |
162 |
) |
157 |
163 |
second_chance = models.BooleanField( |
158 |
164 |
default=True, |
159 |
165 |
help_text=_("Defines if a second chance exam is planned for this course."), |
160 |
166 |
) |
161 |
167 |
tolerable = models.BooleanField( |
162 |
168 |
default=True, |
163 |
169 |
help_text=_("Defines if a failed result can be tolerated."), |
164 |
170 |
) |
165 |
171 |
scoring = models.CharField( |
166 |
172 |
max_length=2, |
167 |
173 |
choices = ( |
168 |
174 |
('N', _("Numerical")), |
169 |
175 |
('FP', _("Fail/Pass")), |
170 |
176 |
), |
171 |
177 |
default='N', |
172 |
178 |
blank=False, |
173 |
179 |
help_text=_("How the obtained score for this course is given."), |
174 |
180 |
) |
175 |
181 |
|
176 |
182 |
def __str__(self): |
177 |
183 |
return str(self.study) + " - " + str(self.course) |
178 |
184 |
|
179 |
185 |
class Study(models.Model): |
180 |
186 |
""" Defines a certain study that can be followed at the university. |
181 |
187 |
This also includes abridged study programmes, like transition programmes. |
182 |
188 |
Other information, such as descriptions, are kept in the template file |
183 |
189 |
of this study, which can be manually edited. Joeni searches for a file |
184 |
190 |
with the exact name as the study + ".html". So if the study is called |
185 |
191 |
"Bachelor of Informatics", it will search for "Bachelor of Informatics.html". |
186 |
192 |
""" |
187 |
193 |
# Degree types |
188 |
194 |
BSc = _("Bachelor of Science") |
189 |
195 |
Msc = _("Master of Science") |
190 |
- | LLB = _("Bachelor of Laws") |
+ |
196 |
LLB = _("Bachelor of Laws") |
191 |
197 |
LLM = _("Master of Laws") |
192 |
198 |
ir = _("Engineer") |
193 |
199 |
ing = _("Technological Engineer") |
194 |
200 |
# Faculties |
195 |
201 |
FoMaLS = _("Faculty of Medicine and Life Sciences") |
196 |
202 |
Fos = _("Faculty of Sciences") |
197 |
- | FoTS = _("Faculty of Transportation Sciences") |
+ |
203 |
FoTS = _("Faculty of Transportation Sciences") |
198 |
204 |
FoAaA = _("Faculty of Architecture and Arts") |
199 |
205 |
FoBE = _("Faculty of Business Economics") |
200 |
206 |
FoET = _("Faculty of Engineering Technology") |
201 |
207 |
FoL = _("Faculty of Law") |
202 |
208 |
|
203 |
209 |
name = models.CharField( |
204 |
210 |
max_length=128, |
205 |
211 |
blank=False, |
206 |
212 |
unique=True, |
207 |
213 |
help_text=_("The full name of this study, in the language it's taught in."), |
208 |
214 |
) |
209 |
215 |
degree_type = models.CharField( |
210 |
216 |
max_length=64, |
211 |
217 |
choices = ( |
212 |
218 |
('BSc', Bsc), |
213 |
- | ('MSc', Msc), |
214 |
- | ('LL.B', LLB), |
+ |
219 |
('MSc', MSc), |
+ |
220 |
('LL.B', LLB), |
215 |
221 |
('LL.M', LLM), |
216 |
222 |
('ir.', ir ), |
217 |
223 |
('ing.',ing), |
218 |
224 |
), |
219 |
225 |
blank=False, |
220 |
226 |
help_text=_("The type of degree one obtains upon passing this study."), |
221 |
227 |
) |
222 |
228 |
language = models.CharField( |
223 |
229 |
max_length=64, |
224 |
230 |
choices = ( |
225 |
231 |
('NL', _("Dutch")), |
226 |
232 |
('EN', _("English")), |
227 |
233 |
('FR', _("French")), |
228 |
234 |
), |
229 |
235 |
null=False, |
230 |
236 |
help_text=_("The language in which this study is given."), |
231 |
237 |
) |
232 |
238 |
# Information about exam committee |
233 |
239 |
chairman = models.ForeignKey( |
234 |
240 |
"Joeni.users", |
235 |
- | on_delete=models.PROTECT, |
+ |
241 |
on_delete=models.PROTECT, |
236 |
242 |
null=False, |
237 |
243 |
limit_choices_to={'is_staff': True}, |
238 |
244 |
help_text=_("The chairman of this study."), |
239 |
245 |
) |
+ |
246 |
) |
240 |
247 |
vice_chairman = models.ForeignKey( |
241 |
248 |
"Joeni.users", |
242 |
- | on_delete=models.PROTECT, |
+ |
249 |
on_delete=models.PROTECT, |
243 |
250 |
null=False, |
244 |
251 |
help_text=_("The vice-chairman of this study."), |
245 |
252 |
limit_choices_to={'is_staff': True}, |
246 |
253 |
) |
+ |
254 |
) |
247 |
255 |
secretary = models.ForeignKey( |
248 |
256 |
"Joeni.users", |
249 |
- | on_delete=models.PROTECT, |
+ |
257 |
on_delete=models.PROTECT, |
250 |
258 |
null=False, |
251 |
259 |
help_text=_("The secretary of this study."), |
252 |
260 |
limit_choices_to={'is_staff': True}, |
253 |
261 |
) |
+ |
262 |
) |
254 |
263 |
ombuds = models.ForeignKey( |
255 |
264 |
"Joeni.users", |
256 |
- | on_delete=models.PROTECT, |
+ |
265 |
on_delete=models.PROTECT, |
257 |
266 |
null=False, |
258 |
267 |
help_text=_("The ombuds person of this study."), |
259 |
268 |
limit_choices_to={'is_staff': True}, |
260 |
269 |
) |
+ |
270 |
) |
261 |
271 |
vice_ombuds = models.ForeignKey( |
262 |
272 |
"Joeni.users", |
263 |
- | on_delete=models.PROTECT, |
+ |
273 |
on_delete=models.PROTECT, |
264 |
274 |
null=False, |
265 |
275 |
help_text=_("The (replacing) ombuds person of this study."), |
266 |
276 |
limit_choices_to={'is_staff': True}, |
267 |
277 |
) |
+ |
278 |
) |
268 |
279 |
additional_members = models.ManyToManyField( |
269 |
280 |
"Joeni.users", |
270 |
- | help_text=_("All the other members of the exam committee."), |
+ |
281 |
help_text=_("All the other members of the exam committee."), |
271 |
282 |
limit_choices_to={'is_staff': True}, |
272 |
283 |
) |
+ |
284 |
) |
273 |
285 |
faculty = models.CharField( |
274 |
286 |
max_length=6, |
275 |
287 |
choices = ( |
276 |
288 |
('FoS', FoS), |
277 |
289 |
('FoTS', FoTS), |
278 |
290 |
('FoAaA', FoAaA), |
279 |
291 |
('FoBE', FoBE), |
280 |
292 |
('FoMaLS', FoMaLS), |
281 |
293 |
('FoET', FoET), |
282 |
294 |
('FoL', FoL), |
283 |
295 |
), |
284 |
296 |
blank=False, |
285 |
297 |
help_text=_("The faculty where this study belongs to."), |
286 |
298 |
) |
287 |
299 |
|
288 |
300 |
#def study_points(self): |
289 |
301 |
""" Returns the amount of study points for this year. |
290 |
- | This value is inferred based on the study programme information |
+ |
302 |
This value is inferred based on the study programme information |
291 |
303 |
records that lists this study as their foreign key. """ |
292 |
304 |
#total_ECTS = 0 |
293 |
305 |
#for course in ProgrammeInformation.objects.filter(study=self): |
294 |
306 |
#total_ECTS += course.ECTS |
295 |
307 |
#return total_ECTS |
296 |
308 |
# XXX: Commented because this is actually something for the StudyProgramme |
297 |
309 |
def years(self): |
298 |
310 |
""" Returns the amount of years this study takes. |
299 |
311 |
This value is inferred based on the study programme information |
300 |
312 |
records that lists this study as their foreign key. """ |
301 |
313 |
highest_year = 0 |
302 |
314 |
for course in ProgrammeInformation.objects.filter(study=self): |
303 |
315 |
highest_year = max(highest_year, course.year) |
304 |
316 |
return highest_year |
305 |
317 |
|
306 |
318 |
def __str__(self): |
307 |
319 |
return self.name |
308 |
320 |
|
309 |
321 |
class StudyProgramme(models.Model): |
310 |
322 |
""" Represents a programme within a certain study. |
311 |
323 |
A good example for this is the different specializations, minors, majors, ... |
312 |
324 |
one can follow within the same study. Nevertheless, they're all made of |
313 |
325 |
a certain set of courses. This table collects all these, and allows one to name |
314 |
326 |
them, so they're distinct from one another. """ |
315 |
327 |
def name = models.CharField( |
316 |
- | max_length=64, |
+ |
328 |
max_length=64, |
317 |
329 |
blank=False, |
318 |
330 |
help_text=_("The name of this programme."), |
319 |
331 |
) |
320 |
332 |
|
321 |
333 |
def courses(self): |
322 |
334 |
""" All courses that are part of this study programme. """ |
323 |
335 |
programmes = ProgrammeInformation.objects.filter(study_programme=self) |
324 |
336 |
courses = {} |
325 |
337 |
for program in programmes: |
326 |
338 |
courses.add(program.course) |
327 |
339 |
return courses |
328 |
340 |
|
329 |
341 |
def study_points(self, year=None): |
330 |
342 |
""" Returns the amount of study points this programme contains. |
331 |
343 |
Accepts year as an optional argument. If not given, the study points |
332 |
344 |
of all years are returned. """ |
333 |
345 |
programmes = ProgrammeInformation.objects.filter(study_programme=self) |
334 |
346 |
ECTS = 0 |
335 |
347 |
for program in programmes: |
336 |
348 |
if year is None or program.year == year: |
337 |
349 |
# XXX: This only works if the used implementation does lazy |
338 |
350 |
# evaluation, otherwise this is a type error! |
339 |
351 |
ECTS += program.ECTS |
340 |
352 |
return ECTS |
341 |
353 |
|
342 |
354 |
def __str__(self): |
343 |
355 |
return self.name |
344 |
356 |
|
345 |
357 |
# Tables about things related to the courses: |
346 |
358 |
|
347 |
359 |
class Assignment(models.Model): |
348 |
360 |
""" For courses, it's possible to set up tasks. These tasks are recorded |
349 |
361 |
here. """ |
350 |
362 |
# TODO: Require that only the course team can create assignments for a team. |
351 |
363 |
course = models.ForeignKey( |
352 |
364 |
"Course", |
353 |
365 |
on_delete=models.CASCADE, |
354 |
366 |
null=False, |
355 |
367 |
editable=False, |
356 |
368 |
db_index=True, |
357 |
369 |
help_text=_("The course for which this task is assigned."), |
358 |
370 |
) |
359 |
371 |
information = models.TextField( |
360 |
372 |
help_text=_("Any additional information regarding the assignment. Orgmode syntax available."), |
361 |
373 |
) |
362 |
374 |
deadline = models.DateTimeField( |
363 |
375 |
null=False, |
364 |
376 |
help_text=_("The date and time this task is due."), |
365 |
377 |
) |
366 |
378 |
posted = models.DateField(auto_now_add=True) |
367 |
379 |
digital_task = models.BooleanField( |
368 |
380 |
default=True, |
369 |
381 |
help_text=_("This determines whether this assignment requires handing " |
370 |
382 |
"in a digital file."), |
371 |
383 |
) |
372 |
384 |
|
373 |
385 |
def __str__(self): |
374 |
386 |
return str(self.course) +" | "+ str(self.posted) |
375 |
387 |
|
376 |
388 |
class Announcement(models.Model): |
377 |
389 |
""" Courses sometimes have to make announcements for the students. """ |
378 |
390 |
course = models.ForeignKey( |
379 |
391 |
"Course", |
380 |
392 |
on_delete=models.CASCADE, |
381 |
393 |
null=False, |
382 |
394 |
editable=False, |
383 |
395 |
db_index=True, |
384 |
396 |
help_text=_("The course for which this announcement is made."), |
385 |
397 |
) |
386 |
398 |
title = models.CharField( |
387 |
399 |
max_length=20, # Keep It Short & Simple® |
388 |
400 |
help_text=_("A quick title for what this is about."), |
389 |
401 |
) |
390 |
402 |
text = models.TextField( |
391 |
403 |
blank=False, |
392 |
404 |
help_text=_("The announcement itself. Orgmode syntax available."), |
393 |
405 |
) |
394 |
406 |
posted = models.DateTimeField(auto_now_add=True) |
395 |
407 |
|
396 |
408 |
def __str__(self): |
397 |
409 |
return str(self.course) +" | "+ self.posted.strftime("%m/%d") |
398 |
410 |
|
399 |
411 |
class Upload(models.Model): |
400 |
412 |
""" For certain assignments, digital hand-ins may be required. These hand |
401 |
413 |
ins are recorded per student in this table. """ |
402 |
414 |
assignment = models.ForeignKey( |
403 |
415 |
"Assignment", |
404 |
416 |
on_delete=models.CASCADE, |
405 |
417 |
null=False, |
406 |
418 |
editable=False, |
407 |
419 |
db_index=True, |
408 |
420 |
limit_choices_to={"digital_task": True}, |
409 |
421 |
help_text=_("For which assignment this upload is."), |
410 |
422 |
) |
411 |
423 |
# TODO: Try to find a way to require that, if the upload is made, |
412 |
424 |
# only students that have this course in their curriculum can upload. |
413 |
425 |
student = models.ForeignKey( |
414 |
426 |
"joeni.User", |
415 |
- | on_delete=models.CASCADE, |
+ |
427 |
on_delete=models.CASCADE, |
416 |
428 |
null=False, |
417 |
429 |
editable=False, |
418 |
430 |
limit_choices_to={"is_student": True}, |
419 |
431 |
help_text=_("The student who handed this in."), |
420 |
432 |
) |
421 |
433 |
upload_time = models.DateTimeField(auto_now_add=True) |
422 |
434 |
comment = models.TextField( |
423 |
435 |
blank=True, |
424 |
436 |
help_text=_("If you wish to add an additional comment, state it here."), |
425 |
437 |
) |
426 |
438 |
|
+ |
439 |
upload_to="assignments/uploads/%Y/%m/", |
+ |
440 |
null=False, |
+ |
441 |
editable=False, |
+ |
442 |
help_text=_("The file you want to upload for this assignment."), |
+ |
443 |
) |
+ |
444 |
|
+ |
445 |
|
427 |
446 |
def __str__(self): |
428 |
447 |
deadline = self.assignment.deadline |
429 |
448 |
if deadline < self.upload_time |
430 |
- | return str(self.assignment.course) +" | "+ str(self.student.number) + _("(OVERDUE)") |
+ |
449 |
return str(self.assignment.course) +" | "+ str(self.student.number) + _("(OVERDUE)") |
431 |
450 |
else: |
432 |
451 |
return str(self.assignment.course) +" | "+ str(self.student.number) |
433 |
452 |
|
434 |
453 |
class StudyGroup(models.Model): |
435 |
454 |
""" It may be necessary to make study groups regarding a course. These |
436 |
455 |
are recorded here, and blend in seamlessly with the Groups from Agora. |
437 |
456 |
Groups that are recorded as a StudyGroup, are given official course status, |
438 |
457 |
and thus, cannot be removed until the status of StudyGroup is lifted. """ |
439 |
458 |
course = models.ForeignKey( |
440 |
459 |
"Course", |
441 |
460 |
on_delete=models.CASCADE, |
442 |
461 |
null=False, |
443 |
462 |
editable=False, |
444 |
463 |
db_index=True, |
445 |
464 |
help_text=_("The course for which this group is."), |
446 |
465 |
) |
447 |
466 |
group = models.ForeignKey( |
448 |
467 |
"agora.Group", |
449 |
468 |
on_delete=models.PROTECT, # See class documentation |
450 |
469 |
null=False, |
451 |
470 |
editable=False, # Keep the same group |
452 |
471 |
help_text=_("The group that will be seen as the study group."), |
453 |
472 |
) |
454 |
473 |
|
455 |
474 |
def __str__(self): |
456 |
475 |
return str(self.course) +" | "+ str(self.group) |
457 |
476 |
joeni/admin.py ¶
2 additions and 0 deletions.
joeni/apps.py ¶
5 additions and 0 deletions.
joeni/models.py ¶
0 additions and 156 deletions.
View changes Hide changes
1 |
- | from django.core.exceptions import ValidationError # For validating IBAN input |
2 |
- | from django.utils.translation import ugettext_lazy as _ |
3 |
- | from django.db import models |
4 |
- | import datetime |
5 |
- | import os |
6 |
- | |
7 |
- | class room(models.Model): |
8 |
- | """ Represents a room in the university. |
9 |
- | Rooms can have a number of properties, which are stored in the database. |
10 |
- | """ |
11 |
- | name = models.TextField() |
12 |
- | seats = models.IntegerField() |
13 |
- | wheelchair_accessible = models.BooleanField(default=True,blank=False) |
14 |
- | exams_equipped = models.BooleanField(blank=False) |
15 |
- | |
16 |
- | class User(models.Model): |
17 |
- | """ Replacement for the standard Django User model. """ |
18 |
- | number = models.PositiveIntegerField( |
19 |
- | primary_key=True, |
20 |
- | help_text=_("The number assigned to this user."), |
21 |
- | ) |
22 |
- | created = models.DateField(auto_now_add=True) |
23 |
- | passphrase = models.CharField( |
24 |
- | max_length=512, |
25 |
- | blank=False, |
26 |
- | help_text=_("The passphrase used for this account. This field must only contain hashed information."), |
27 |
- | ) |
28 |
- | first_name = models.CharField(max_length=64, blank=False) |
29 |
- | last_name = models.CharField(max_length=64, blank=False) |
30 |
- | title = models.CharField( |
31 |
- | max_length=64, |
32 |
- | blank=True, |
33 |
- | help_text=_("The academic title of this user, if applicable."), |
34 |
- | ) |
35 |
- | DOB = models.DateField( |
36 |
- | blank=False, |
37 |
- | editable=False, |
38 |
- | help_text=_("The date of birth of this user."), |
39 |
- | ) |
40 |
- | POB = models.CharField( |
41 |
- | max_length=64, |
42 |
- | blank=False, |
43 |
- | editable=False, |
44 |
- | help_text=_("The place of birth of this user."), |
45 |
- | ) |
46 |
- | nationality = models.CharField( |
47 |
- | max_length=64, |
48 |
- | blank=False, |
49 |
- | help_text=_("The current nationality of this user."), |
50 |
- | ) |
51 |
- | # XXX: What if this starts with zeros? |
52 |
- | national_registry_number = models.BigIntegerField( |
53 |
- | unique=True, |
54 |
- | editable=False, |
55 |
- | help_text=_("The assigned national registry number of this user."), |
56 |
- | ) |
57 |
- | civil_status = models.CharField( |
58 |
- | choices = ( |
59 |
- | ("Single", _("Single")), |
60 |
- | ("Married", _("Married")), |
61 |
- | ("Divorced", _("Divorced")), |
62 |
- | ("Widowed", _("Widowed")), |
63 |
- | ("Partnership", _("Partnership")), |
64 |
- | ), |
65 |
- | blank=False, |
66 |
- | # There may be more; consult http://www.aantrekkingskracht.com/trefwoord/burgerlijke-staat |
67 |
- | # for more information. |
68 |
- | help_text=_("The civil/marital status of the user."), |
69 |
- | ) |
70 |
- | |
71 |
- | is_staff = models.BooleanField( |
72 |
- | default=False, |
73 |
- | help_text=_("Determines if this user is part of the university's staff."), |
74 |
- | ) |
75 |
- | is_student = models.BooleanField( |
76 |
- | default=True, |
77 |
- | help_text=_("Indicates if this user is a student at the university."), |
78 |
- | ) |
79 |
- | |
80 |
- | # Home address |
81 |
- | home_street = models.CharField(max_length=64, blank=False) |
82 |
- | home_number = models.PositiveSmallIntegerField(blank=False) |
83 |
- | home_bus = models.PositiveSmallIntegerField() |
84 |
- | home_postal_code = models.PositiveSmallIntegerField(blank=False) |
85 |
- | home_country = models.CharField(max_length=64, blank=False) |
86 |
- | home_telephone = models.CharField( |
87 |
- | max_length=64, |
88 |
- | help_text=_("The telephone number for the house address. Prefix 0 can be presented with the national call code in the system."), |
89 |
- | ) |
90 |
- | # Study address |
91 |
- | study_street = models.CharField(max_length=64, blank=False) |
92 |
- | study_number = models.PositiveSmallIntegerField(blank=False) |
93 |
- | study_bus = models.PositiveSmallIntegerField() |
94 |
- | study_postal_code = models.PositiveSmallIntegerField(blank=False) |
95 |
- | study_country = models.CharField(max_length=64, blank=False) |
96 |
- | study_telephone = models.CharField( |
97 |
- | max_length=64, |
98 |
- | help_text=_("The telephone number for the study address. Prefix 0 can be presented with the national call code in the system."), |
99 |
- | ) |
100 |
- | study_cellphone = models.CharField( |
101 |
- | max_length=64, |
102 |
- | help_text=_("The cellphone number of the person. Prefix 0 can be presented with then national call code in the system."), |
103 |
- | ) |
104 |
- | # Titularis address |
105 |
- | # XXX: These fields are only required if this differs from the user itself. |
106 |
- | titularis_street = models.CharField(max_length=64) |
107 |
- | titularis_number = models.PositiveSmallIntegerField() |
108 |
- | titularis_bus = models.PositiveSmallIntegerField() |
109 |
- | titularis_postal_code = models.PositiveSmallIntegerField() |
110 |
- | titularis_country = models.CharField(max_length=64) |
111 |
- | titularis_telephone = models.CharField( |
112 |
- | max_length=64, |
113 |
- | help_text=_("The telephone number of the titularis. Prefix 0 can be presented with the national call code in the system."), |
114 |
- | ) |
115 |
- | |
116 |
- | # Financial details |
117 |
- | bank_account_number = models.CharField( |
118 |
- | max_length=34, # Max length of all IBAN account numbers |
119 |
- | validators=[validate_IBAN], |
120 |
- | help_text=_("The IBAN of this user. No spaces!"), |
121 |
- | ) |
122 |
- | BIC = models.CharField( |
123 |
- | max_length=11, |
124 |
- | validators=[validate_BIC], |
125 |
- | help_text=_("The BIC of this user's bank."), |
126 |
- | ) |
127 |
- | |
128 |
- | def validate_IBAN(value): |
129 |
- | """ Validates if the given value qualifies as a valid IBAN number. |
130 |
- | This validator checks if the structure is valid, and calculates the control |
131 |
- | number if the structure is correct. If the control number fails, or the |
132 |
- | structure is invalid, a ValidationError will be raised. In that case, |
133 |
- | the Error will specify whether the structure is incorrect, or the control |
134 |
- | number is not valid. |
135 |
- | """ |
136 |
- | # FIXME: This function is not complete. When there's time, implement |
137 |
- | # as specified at https://nl.wikipedia.org/wiki/International_Bank_Account_Number#Structuur |
138 |
- | if False: |
139 |
- | raise ValidationError( |
140 |
- | _('%(value)s is not a valid IBAN number.'), |
141 |
- | params={'value': value},) |
142 |
- | def validate_BIC(value): |
143 |
- | """ Same functionality as validate_IBAN, but for BIC-codes. """ |
144 |
- | # FIXME: This function is not complete. When there's time, implement |
145 |
- | # as specified at https://nl.wikipedia.org/wiki/Business_Identifier_Code |
146 |
- | pass |
147 |
- | |
148 |
- | |
149 |
- | """ NOTE: What about all the other features that should be in the administration? |
150 |
- | While there are a lot of things to cover, as of now, I have no way to know which |
151 |
- | ones are still valid, which are deprecated, and so on... |
152 |
- | Additionally, every feature may have a different set of requirements, data, |
153 |
- | and it's very likely making an abstract class won't do any good. Thus I have |
154 |
- | decided to postpone making additional tables and forms for these features until |
155 |
- | I have clearance about certain aspects. """ |
156 |
- |
joeni/settings.py ¶
7 additions and 4 deletions.
View changes Hide changes
1 |
1 |
Django settings for Joeni project. |
2 |
2 |
|
3 |
3 |
Generated by 'django-admin startproject' using Django 2.0b1. |
4 |
4 |
|
5 |
5 |
For more information on this file, see |
6 |
6 |
https://docs.djangoproject.com/en/dev/topics/settings/ |
7 |
7 |
|
8 |
8 |
For the full list of settings and their values, see |
9 |
9 |
https://docs.djangoproject.com/en/dev/ref/settings/ |
10 |
10 |
""" |
11 |
11 |
|
12 |
12 |
import os |
13 |
13 |
|
14 |
14 |
# Build paths inside the project like this: os.path.join(BASE_DIR, ...) |
15 |
15 |
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |
16 |
16 |
|
17 |
17 |
|
18 |
18 |
# Quick-start development settings - unsuitable for production |
19 |
19 |
# See https://docs.djangoproject.com/en/dev/howto/deployment/checklist/ |
20 |
20 |
|
21 |
21 |
# SECURITY WARNING: keep the secret key used in production secret! |
22 |
22 |
SECRET_KEY = '!2634qc=b*lp0=helzcmvb3+1_wcl!6z@mhzi%p(vg7odq&gfz' |
23 |
23 |
|
24 |
24 |
# SECURITY WARNING: don't run with debug turned on in production! |
25 |
25 |
DEBUG = True |
26 |
26 |
|
27 |
27 |
ALLOWED_HOSTS = [] |
28 |
28 |
|
29 |
29 |
|
30 |
30 |
# Application definition |
31 |
31 |
|
32 |
32 |
INSTALLED_APPS = [ |
33 |
33 |
'django.contrib.admin', |
34 |
34 |
'django.contrib.auth', |
35 |
35 |
'django.contrib.contenttypes', |
36 |
36 |
'django.contrib.sessions', |
37 |
37 |
'django.contrib.messages', |
38 |
38 |
'django.contrib.staticfiles', |
39 |
39 |
'administation', |
40 |
- | 'agora', |
+ |
40 |
'agora', |
41 |
41 |
'courses', |
42 |
42 |
] |
+ |
43 |
] |
43 |
44 |
|
44 |
45 |
MIDDLEWARE = [ |
45 |
46 |
'django.middleware.security.SecurityMiddleware', |
46 |
47 |
'django.middleware.locale.LocaleMiddleware', |
47 |
48 |
'django.middleware.common.CommonMiddleware', |
48 |
49 |
'django.middleware.csrf.CsrfViewMiddleware', |
49 |
50 |
'django.contrib.sessions.middleware.SessionMiddleware', |
50 |
51 |
'django.contrib.auth.middleware.AuthenticationMiddleware', |
51 |
52 |
'django.contrib.auth.middleware.SessionAuthenticationMiddleware', |
52 |
- | 'django.contrib.messages.middleware.MessageMiddleware', |
+ |
53 |
'django.contrib.messages.middleware.MessageMiddleware', |
53 |
54 |
'django.middleware.clickjacking.XFrameOptionsMiddleware', |
54 |
55 |
'django.middleware.security.SecurityMiddleware', |
55 |
56 |
# Caching middleware |
56 |
57 |
'django.middleware.cache.UpdateCacheMiddleware', |
57 |
58 |
'django.middleware.common.CommonMiddleware', |
58 |
59 |
'django.middleware.cache.FetchFromCacheMiddleware', |
59 |
60 |
] |
60 |
61 |
|
61 |
62 |
# Caching settings |
62 |
63 |
CACHE_MIDDLEWARE_ALIAS = 'dummy' |
63 |
- | CACHE_MIDDLEWARE_SECONDS = 300 |
+ |
64 |
CACHE_MIDDLEWARE_SECONDS = 300 |
64 |
65 |
CACHE_MIDDLEWARE_KEY_PREFIX = '' |
65 |
66 |
|
66 |
67 |
ROOT_URLCONF = 'joeni.urls' |
67 |
68 |
|
68 |
69 |
TEMPLATES = [ |
69 |
70 |
{ |
70 |
71 |
'BACKEND': 'django.template.backends.django.DjangoTemplates', |
71 |
72 |
'DIRS': [], |
72 |
73 |
'APP_DIRS': True, |
73 |
74 |
'OPTIONS': { |
74 |
75 |
'context_processors': [ |
75 |
76 |
'django.template.context_processors.debug', |
76 |
77 |
'django.template.context_processors.request', |
77 |
78 |
'django.contrib.auth.context_processors.auth', |
78 |
79 |
'django.contrib.messages.context_processors.messages', |
79 |
80 |
], |
80 |
81 |
}, |
81 |
82 |
}, |
82 |
83 |
] |
83 |
84 |
|
84 |
85 |
WSGI_APPLICATION = 'joeni.wsgi.application' |
85 |
- | |
+ |
86 |
|
86 |
87 |
|
87 |
88 |
# Database |
88 |
89 |
# https://docs.djangoproject.com/en/dev/ref/settings/#databases |
89 |
90 |
|
90 |
91 |
DATABASES = { |
91 |
92 |
'default': { |
92 |
93 |
'ENGINE': 'django.db.backends.sqlite3', |
93 |
94 |
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), |
94 |
95 |
} |
95 |
96 |
} |
96 |
97 |
|
97 |
98 |
|
+ |
99 |
AUTH_USER_MODEL = 'administration.User' |
+ |
100 |
|
98 |
101 |
# Password validation |
99 |
102 |
# https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators |
100 |
103 |
|
101 |
104 |
AUTH_PASSWORD_VALIDATORS = [ |
102 |
105 |
{ |
103 |
106 |
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', |
104 |
107 |
}, |
105 |
108 |
{ |
106 |
109 |
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', |
107 |
110 |
}, |
108 |
111 |
{ |
109 |
112 |
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', |
110 |
113 |
}, |
111 |
114 |
{ |
112 |
115 |
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', |
113 |
116 |
}, |
114 |
117 |
] |
115 |
118 |
|
116 |
119 |
CACHES = { |
117 |
120 |
'default': { |
118 |
121 |
'BACKEND': 'django.core.cache.backends.dummy.DummyCache', |
119 |
122 |
} |
120 |
123 |
} |
121 |
124 |
|
122 |
125 |
# Internationalization |
123 |
126 |
# https://docs.djangoproject.com/en/dev/topics/i18n/ |
124 |
127 |
|
125 |
128 |
LANGUAGE_CODE = 'en-us' |
126 |
129 |
|
127 |
130 |
TIME_ZONE = 'UTC' |
128 |
131 |
|
129 |
132 |
USE_I18N = True |
130 |
133 |
|
131 |
134 |
USE_L10N = True |
132 |
135 |
|
133 |
136 |
USE_TZ = True |
134 |
137 |
|
135 |
138 |
|
136 |
139 |
# Static files (CSS, JavaScript, Images) |
137 |
140 |
# https://docs.djangoproject.com/en/dev/howto/static-files/ |
138 |
141 |
|
139 |
142 |
STATIC_URL = '/static/' |
140 |
143 |
MEDIA_URL = '/media/' |
141 |
144 |