Debug login screen, add new thesis files
- Author
- Maarten Vangeneugden
- Date
- July 26, 2018, 8:50 p.m.
- Hash
- 8c5913dc86daa60acef075b2b04036bd6ad46a5e
- Parent
- 02c51992c9908cc62f4bc5da1fee6bf1516ac055
- Modified files
- administration/views.py
- docs/thesis/BlackBoard.org
- docs/thesis/aandachtspunten.org
- docs/thesis/beveiliging.org
- docs/thesis/huisstijl.org
- docs/thesis/master.org
- docs/thesis/softwarekeuzes.org
- docs/thesis/voorwoord.org
- joeni/settings.py
- joeni/urls.py
administration/views.py ¶
2 additions and 0 deletions.
View changes Hide changes
1 |
1 |
from collections import OrderedDict |
2 |
2 |
from django.http import HttpResponseRedirect |
3 |
3 |
import datetime |
4 |
4 |
from django.urls import reverse # Why? |
5 |
5 |
from django.utils.translation import gettext as _ |
6 |
6 |
from .models import * |
7 |
7 |
from .forms import UserDataForm |
8 |
8 |
from .new_roster import create_roster_rows |
9 |
9 |
import administration |
10 |
10 |
from django.contrib.auth.decorators import login_required |
11 |
11 |
from django.contrib.auth import authenticate |
12 |
12 |
|
+ |
13 |
|
13 |
14 |
@login_required |
14 |
15 |
def roster(request, begin=None, end=None): |
15 |
16 |
"""Collects and renders the data that has to be displayed in the roster. |
16 |
17 |
|
17 |
18 |
The begin and end date can be specified. Only roster points in that range |
18 |
19 |
will be included in the response. If no begin and end are specified, it will |
19 |
20 |
take the current week as begin and end point. If it's |
20 |
21 |
weekend, it will take next week.""" |
21 |
22 |
|
22 |
23 |
# TODO Handle given begin and end |
23 |
24 |
context = dict() |
24 |
25 |
#context = {'money' : update_balance(None)} |
25 |
26 |
template = "administration/roster.djhtml" |
26 |
27 |
|
27 |
28 |
if begin is None or end is None: |
28 |
29 |
today = datetime.date.today() |
29 |
30 |
if today.isoweekday() in {6,7}: # Weekend |
30 |
31 |
begin = today + datetime.timedelta(days=8-today.isoweekday()) |
31 |
32 |
end = today + datetime.timedelta(days=13-today.isoweekday()) |
32 |
33 |
else: # Same week |
33 |
34 |
begin = today - datetime.timedelta(days=today.weekday()) |
34 |
35 |
end = today + datetime.timedelta(days=5-today.isoweekday()) |
35 |
36 |
else: # Changing regexes to date objects |
36 |
37 |
b = begin.split("-") |
37 |
38 |
e = end.split("-") |
38 |
39 |
begin = datetime.datetime(int(b[2]),int(b[1]),int(b[0])) |
39 |
40 |
end = datetime.datetime(int(e[2]),int(e[1]),int(e[0])) |
40 |
41 |
|
41 |
42 |
context['begin'] = begin |
42 |
43 |
context['end'] = end |
43 |
44 |
|
44 |
45 |
context['prev_begin'] = (begin - datetime.timedelta(days=7)).strftime("%d-%m-%Y") |
45 |
46 |
context['prev_end'] = (begin - datetime.timedelta(days=2)).strftime("%d-%m-%Y") |
46 |
47 |
context['next_begin'] = (end + datetime.timedelta(days=2)).strftime("%d-%m-%Y") |
47 |
48 |
context['next_end'] = (end + datetime.timedelta(days=7)).strftime("%d-%m-%Y") |
48 |
49 |
|
49 |
50 |
days = [begin] |
50 |
51 |
while (end-days[-1]).days != 0: |
51 |
52 |
# Human translation: Keep adding days until the last day in the array of |
52 |
53 |
# days is the same day as the last day the user wants to see the roster for. |
53 |
54 |
days.append(days[-1] + datetime.timedelta(days=1)) |
54 |
55 |
context['days'] = days |
55 |
56 |
|
56 |
57 |
# Collecting events |
57 |
58 |
course_events = CourseEvent.objects.filter(begin_time__gte=begin).filter(end_time__lte=end).order_by("begin_time") |
58 |
59 |
#university_events = UniversityEvent.objects.filter(begin_time__gte=begin).filter(end_time__lte=end) |
59 |
60 |
#study_events = StudyEvent.objects.filter(begin_time__gte=begin).filter(end_time__lte=end) |
60 |
61 |
#events = Event.objects.filter(begin_time__gte=begin).filter(end_time__lte=end) |
61 |
62 |
conflicts, table_code = create_roster_rows(course_events) |
62 |
63 |
|
63 |
64 |
context['time_blocks'] = table_code |
64 |
65 |
context['conflicts'] = conflicts |
65 |
66 |
#print(time_blocks) |
66 |
67 |
return render(request, template, context) |
67 |
68 |
# TODO Finish! |
68 |
69 |
|
69 |
70 |
def roster_ics(request, user_slug): |
70 |
71 |
template = "administration/roster.ics" |
71 |
72 |
context = dict() |
72 |
73 |
context['events'] = CourseEvent.objects.all() # FIXME: Filter to personal calendar items! |
73 |
74 |
return render(request, template, context) |
74 |
75 |
|
75 |
76 |
def index(request): |
76 |
77 |
template = "administration/index.djhtml" |
77 |
78 |
#context = {'money': update_balance(None)} |
78 |
79 |
context = dict() |
79 |
80 |
context['links'] = [ |
80 |
81 |
("administration-settings", |
81 |
82 |
_("Personal settings"), |
82 |
83 |
_("Edit your personal information, billing address, home address, and so on.")), |
83 |
84 |
("administration-curriculum", |
84 |
85 |
_("Curricula"), |
85 |
86 |
_("View all information related to your curricula, including exam results.<br />" |
86 |
87 |
"You can also change your current curriculum here, or request a change.")), |
87 |
88 |
("administration-forms", |
88 |
89 |
_("Forms"), |
89 |
90 |
_("All forms for special services can be found on this page.")), |
90 |
91 |
("administration-rooms", |
91 |
92 |
_("Rooms"), |
92 |
93 |
_("Room occupancy, free rooms, properties, ... <br />" |
93 |
94 |
"All this and much more is available on this page.")), |
94 |
95 |
("administration-roster", |
95 |
96 |
_("Personal roster"), |
96 |
97 |
_("Everything about your roster and events at Hasselt University is available here.")), |
97 |
98 |
("administration-bulletin-board", |
98 |
99 |
_("Bulletin board"), |
99 |
100 |
_("From time to time, UHasselt publishes announcements regarding changes, events, ..." |
100 |
101 |
"<br />All publications are neatly organized here for easy reference.")), |
101 |
102 |
] |
102 |
103 |
|
103 |
104 |
return render(request, template, context) |
104 |
105 |
|
105 |
106 |
pass |
106 |
107 |
|
107 |
108 |
def pre_registration(request): |
108 |
109 |
user_data_form = UserDataForm() |
109 |
110 |
template = "administration/pre_registration.djhtml" |
110 |
111 |
context = dict() |
111 |
112 |
|
112 |
113 |
if request.method == 'POST': |
113 |
114 |
user_data_form = UserDataForm(request.POST) |
114 |
115 |
context['user_data_form'] = user_data_form |
115 |
116 |
if user_data_form.is_valid(): |
116 |
117 |
user_data_form.save() |
117 |
118 |
context['messsage'] = _("Your registration has been completed. You will receive an e-mail shortly.") |
118 |
119 |
else: |
119 |
120 |
context['messsage'] = _("The data you supplied had errors. Please review your submission.") |
120 |
121 |
else: |
121 |
122 |
context['user_data_form'] = UserDataForm(instance = user_data_form) |
122 |
123 |
|
123 |
124 |
return render(request, template, context) |
124 |
125 |
pass |
125 |
126 |
|
126 |
127 |
@login_required |
127 |
128 |
def settings(request): |
128 |
129 |
user_data = UserData.objects.get(user=request.user) |
129 |
130 |
user_data_form = UserDataForm(instance = user_data) |
130 |
131 |
template = "administration/settings.djhtml" |
131 |
132 |
context = dict() |
132 |
133 |
#context = {'money' : update_balance(None)} |
133 |
134 |
|
134 |
135 |
if request.method == 'POST': |
135 |
136 |
user_data_form = UserDataForm(request.POST, instance = user_data) |
136 |
137 |
context['user_data_form'] = user_data_form |
137 |
138 |
if user_data_form.is_valid(): |
138 |
139 |
user_data_form.save() |
139 |
140 |
context['messsage'] = _("Your settings were successfully updated.") |
140 |
141 |
else: |
141 |
142 |
context['messsage'] = _("The data you supplied had errors. Please review your submission.") |
142 |
143 |
else: |
143 |
144 |
context['user_data_form'] = UserDataForm(instance = user_data) |
144 |
145 |
|
145 |
146 |
return render(request, template, context) |
146 |
147 |
|
147 |
148 |
@login_required |
148 |
149 |
def bulletin_board(request): |
149 |
150 |
context = dict() |
150 |
151 |
#context = {'money' : update_balance(None)} |
151 |
152 |
context['exam_commission_decisions'] = ExamCommissionDecision.objects.filter(user=request.user) |
152 |
153 |
context['education_department_messages'] = EducationDepartmentMessages.objects.all() |
153 |
154 |
for item in context['education_department_messages']: |
154 |
155 |
print(item.text) |
155 |
156 |
template = "administration/bulletin_board.djhtml" |
156 |
157 |
return render(request, template, context) |
157 |
158 |
|
158 |
159 |
def jobs(request): |
159 |
160 |
context = dict() |
160 |
161 |
#context = {'money' : update_balance(None)} |
161 |
162 |
template = "administration/jobs.djhtml" |
162 |
163 |
#@context['decisions'] = ExamCommissionDecision.objects.filter(user=request.user) |
163 |
164 |
return render(request, template, context) |
164 |
165 |
|
165 |
166 |
|
166 |
167 |
@login_required |
167 |
168 |
def curriculum(request): |
168 |
169 |
context = dict() |
169 |
170 |
#context = {'money' : update_balance(None)} |
170 |
171 |
template = "administration/curriculum.djhtml" |
171 |
172 |
context['curricula'] = Curriculum.objects.filter(student=request.user) |
172 |
173 |
for item in context['curricula']: |
173 |
174 |
for co in item.course_programmes_results(): |
174 |
175 |
print(co) |
175 |
176 |
return render(request, template, context) |
176 |
177 |
|
177 |
178 |
def result(request): |
178 |
179 |
return render(request, template, context) |
179 |
180 |
|
180 |
181 |
@login_required |
181 |
182 |
def results(request): |
182 |
183 |
results = CourseResult.objects.filter(student=request.user) |
183 |
184 |
template = "administration/results.djhtml" |
184 |
185 |
# TODO |
185 |
186 |
return render(request, template, context) |
186 |
187 |
|
187 |
188 |
def forms(request): |
188 |
189 |
context = dict() |
189 |
190 |
#context = {'money' : update_balance(None)} |
190 |
191 |
template = "administration/forms.djhtml" |
191 |
192 |
return render(request, template, context) |
192 |
193 |
|
193 |
194 |
def user(request, slug_name): |
194 |
195 |
pass |
195 |
196 |
|
196 |
197 |
def rooms(request): |
197 |
198 |
context = dict() |
198 |
199 |
#context = {'money' : update_balance(None)} |
199 |
200 |
context['rooms'] = Room.objects.all() |
200 |
201 |
context['room_reservations'] = RoomReservation.objects.all() |
201 |
202 |
context['course_events'] = CourseEvent.objects.all() |
202 |
203 |
context['blocks'] = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'] |
203 |
204 |
|
204 |
205 |
# Collecting all rooms that are free for at least one two hours from now |
205 |
206 |
now = datetime.datetime.now(datetime.timezone.utc) |
206 |
207 |
end = now + datetime.timedelta(hours=2) |
207 |
208 |
free_rooms = dict() |
208 |
209 |
for room in context['rooms']: |
209 |
210 |
if room.reservation_possible(now, end): |
210 |
211 |
event = room.next_event(end) |
211 |
212 |
reservation = room.next_reservation(end) |
212 |
213 |
if event is None and reservation is None: |
213 |
214 |
free_rooms[room] = None |
214 |
215 |
elif reservation is not None: |
215 |
216 |
free_rooms[room] = event.begin_time |
216 |
217 |
elif event is not None: |
217 |
218 |
free_rooms[room] = reservation.begin_time |
218 |
219 |
elif event.begin_time < reservation.begin_time: |
219 |
220 |
free_rooms[room] = event.begin_time |
220 |
221 |
else: |
221 |
222 |
free_rooms[room] = reservation.begin_time |
222 |
223 |
context['free_rooms'] = free_rooms |
223 |
224 |
|
224 |
225 |
template = "administration/rooms.djhtml" |
225 |
226 |
return render(request, template, context) |
226 |
227 |
|
227 |
228 |
def room_detail(request, room): |
228 |
229 |
template = "administration/room_detail.djhtml" |
229 |
230 |
context = dict() |
230 |
231 |
#context = {'money' : update_balance(None)} |
231 |
232 |
room = Room.objects.get(name=room) |
232 |
233 |
context['room'] = room |
233 |
234 |
context['reservations'] = RoomReservation.objects.filter(room=room).filter(begin_time__gte=datetime.datetime.now()) |
234 |
235 |
context['course_events'] = CourseEvent.objects.filter(room=room).filter(begin_time__gte=datetime.datetime.now()) |
235 |
236 |
# Building the room occupancy of today: |
236 |
237 |
today = datetime.date.today() |
237 |
238 |
if today.isoweekday() in {6,7}: # Weekend |
238 |
239 |
today = today + datetime.timedelta(days=8-today.isoweekday()) |
239 |
240 |
|
240 |
241 |
context['days'] = [today] |
241 |
242 |
|
242 |
243 |
# Collecting events |
243 |
244 |
course_events = CourseEvent.objects.filter(room=room).filter(begin_time__date=today) |
244 |
245 |
print(course_events) |
245 |
246 |
#university_events = UniversityEvent.objects.filter(begin_time__gte=begin).filter(end_time__lte=end) |
246 |
247 |
#study_events = StudyEvent.objects.filter(begin_time__gte=begin).filter(end_time__lte=end) |
247 |
248 |
#events = Event.objects.filter(begin_time__gte=begin).filter(end_time__lte=end) |
248 |
249 |
|
249 |
250 |
conflicts, table_code = create_roster_rows(course_events) |
250 |
251 |
context['time_blocks'] = table_code |
251 |
252 |
context['conflicts'] = conflicts |
252 |
253 |
print(context['time_blocks']) |
253 |
254 |
return render(request, template, context) |
254 |
255 |
|
255 |
256 |
def login(request): |
256 |
257 |
context = dict() |
257 |
258 |
#context = {'money' : update_balance(None)} |
258 |
259 |
if request.method == "POST": |
259 |
260 |
name = request.POST['name'] |
260 |
261 |
passphrase = request.POST['pass'] |
261 |
262 |
user = authenticate(username=name, password=passphrase) |
262 |
263 |
if user is not None: # The user was successfully authenticated |
263 |
264 |
print("YA") |
264 |
265 |
return HttpResponseRedirect(request.POST['next']) |
+ |
266 |
return HttpResponseRedirect(request.POST['next']) |
265 |
267 |
else: # User credentials were wrong |
266 |
268 |
context['next'] = request.POST['next'] |
267 |
269 |
context['message'] = _("The given credentials were not correct.") |
268 |
270 |
else: |
269 |
271 |
context['next'] = request.GET.get('next', None) |
270 |
272 |
if context['next'] is None: |
271 |
273 |
context['next'] = reverse('administration-index') |
272 |
274 |
|
273 |
275 |
template = 'administration/login.djhtml' |
274 |
276 |
|
275 |
277 |
return render(request, template, context) |
276 |
278 |
docs/thesis/BlackBoard.org ¶
11 additions and 0 deletions.
View changes Hide changes
+ |
1 |
De UHasselt gebruikt al jaren BlackBoard als digitaal platform voor het |
+ |
2 |
uitwisselen van studiemateriaal tussen docenten en studenten.\\ |
+ |
3 |
** Propriëtaire software |
+ |
4 |
*** Service as a software substitute (SaaSS) |
+ |
5 |
** Kostelijk |
+ |
6 |
|
+ |
7 |
~€100.000 euro kost.>.... |
+ |
8 |
** Geen huisstijl mogelijk |
+ |
9 |
** Onstabiel |
+ |
10 |
** Slechte reputatie |
+ |
11 |
docs/thesis/aandachtspunten.org ¶
10 additions and 1 deletion.
View changes Hide changes
1 |
1 |
Fundamenteel is deze bachelorproef nog altijd een werk dat moet bijdragen aan |
2 |
2 |
het ALIPA-project van de UHasselt.\\ |
3 |
3 |
Voor het stroomlijnen van de ICT-infrastructuur is het ook nodig om kleine |
4 |
4 |
problemen en mankementen op te lossen als ze gevonden worden. Een afgewerkt |
5 |
5 |
geheel voedt het gevoel dat alles goed en orderlijk samenwerkt met elkaar, een |
6 |
6 |
belangrijke doelstelling om vertrouwen te creëren in de gebruikers. |
7 |
7 |
|
8 |
8 |
Tijdens het maken van dit proefwerk ben ik op enkele kleine punten gestoten die |
9 |
9 |
niet direct van invloed waren op mijn proef, maar die ik hier toch wens te |
10 |
10 |
vermelden. Het gaat dan om kleine slordigheden, schoonheidsfoutjes, verouderde |
11 |
11 |
informatie, ... |
12 |
12 |
|
13 |
13 |
Alle benodigde informatie, alsook een redenering wordt per punt uitgeschreven. |
14 |
14 |
|
15 |
15 |
** Verouderde informatie |
16 |
16 |
*** Campushopper afgeschaft |
17 |
17 |
Op de pagina https://www.uhasselt.be/Contact-en-ligging onder de rubriek "Campus |
18 |
18 |
Hasselt/Gevangenis" wordt verteld dat men de |
19 |
19 |
Campushopper kan nemen, maar deze werd al in 2017 afgeschaft. Beter is om Boulevardpendel |
20 |
20 |
te nemen, of elke bus die het Dusartplein aandoet.[fn::Dit zijn de volgende |
21 |
21 |
lijnen: 1, 3, 5, 11, 13, 16, 18a, 20a, 22, 23, 35, 36, 45, 46, 48, 51, 52, 180, |
22 |
22 |
182, AL, BP, H13, H14, H41, H51. (https://www.delijn.be/nl/haltes/halte/403040/Hasselt_Dusartplein)] |
23 |
23 |
|
24 |
24 |
** Onnodige informatie |
25 |
25 |
*** Spartacusplan |
26 |
26 |
Op https://www.uhasselt.be/Contact-en-ligging staat een link naar een |
27 |
27 |
PDF-bestand omtrent het Spartacusplan. Dit heeft inderdaad te maken met het |
28 |
28 |
openbaar vervoer, maar voegt weinig toe aan het eigenlijke doel van deze pagina: |
29 |
29 |
Bezoekers informeren over hoe ze de UHasselt kunnen bereiken.\\ |
30 |
30 |
Ik stel voor om de vermelding hierover de verwijderen. |
31 |
31 |
|
32 |
32 |
** Slecht te vinden informatie |
33 |
33 |
*** Plattegronden |
34 |
34 |
Er is weinig te vinden van plattegronden op de website; waar zijn welke lokalen |
35 |
35 |
te vinden, plattegronden van de 1ste verdieping, ... |
36 |
36 |
|
37 |
37 |
Het kan ook zijn dat dit niet per sé nodig is; indien op de campus zelf adequate |
38 |
38 |
wegbewijzering wordt aangebracht, wordt een plattegrond minder belangrijk. |
39 |
39 |
Waar vind ik alle plattegronden van de campussen? Het zijn er maar 2... |
40 |
40 |
Ik heb er eentje gevonden van de campus Diepenbeek op https://www.uhasselt.be/Contact-en-ligging: |
41 |
41 |
https://www.uhasselt.be/images/UHasselt/maps/2015/campus-diepenbeek/campus-diepenbeek-gebouwDE.jpg |
42 |
42 |
Maar dat is ook enkel gelijkvloers, en niet up-to-date met nieuwe aanbouw. |
43 |
43 |
|
44 |
44 |
** Verbeteringspunten |
+ |
45 |
*** Speciale logo's voor Apple |
+ |
46 |
In de broncode van de websites binnen de UHasselt zijn in de header links te |
+ |
47 |
vinden naar speciale logo's voor Apple-browsers.\\ |
+ |
48 |
Binnen Joeni worden er geen |
+ |
49 |
** Verbeteringspunten |
45 |
50 |
*** Video's op YouTube |
46 |
51 |
Op https://www.uhasselt.be/Videos zijn er veel filmpjes te vinden over het |
47 |
52 |
reilen en zeilen in de universiteit. Maar eigenlijk zijn het links naar het |
48 |
53 |
videoplatform YouTube, onderdeel van Google Inc. Eerder werd in deze thesis |
49 |
54 |
[[google-privacy][de integratie met diensten van Google]] op de korrel genomen.\\ |
50 |
- | Dit is makkelijk op te lossen door de video's simpelweg via de eigen website aan |
+ |
55 |
Dit is makkelijk op te lossen door de video's simpelweg via de eigen website aan |
51 |
56 |
te bieden, middels het [[https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Video][~ |
52 |
57 |
**** Licensiëring video's |
53 |
58 |
De aangegeven licentie van de video's is de "Standaard YouTube-licentie", deze |
54 |
59 |
licentie voldoet niet aan de vereisten voor een "Vrij cultureel werk". |
55 |
60 |
|
56 |
61 |
De aard van deze video's is duidelijk: Ze dienen om toekomstige studenten te |
57 |
62 |
informeren over de UHasselt en al haar richtingen, een blik te werpen op |
58 |
63 |
studentenfaciliteiten (zoals verenigingen), en ook als een advertentie om voor |
59 |
64 |
UHasselt te kiezen.\\ |
60 |
65 |
De doelstelling bereiken wordt met deze licentiekeuze actief tegengewerkt; |
61 |
66 |
mensen mogen deze video's niet delen, of aanpassen voor nieuwe werken. Er zijn |
62 |
67 |
ook voorwaarden op het privégebruik ervan.\\ |
63 |
68 |
Creatieve werken met deze doelstellingen dienen zo vrij mogelijk uitgegeven te |
64 |
69 |
worden om de zichtbaarheid en verspreidingskansen ervan te maximaliseren. Door de huidige wetgeving |
65 |
70 |
omtrent kopieerrechten is dit niet automatisch mogelijk, maar er bestaan wel |
66 |
71 |
licenties die hiervoor een zeer toereikend soelaas bieden. |
67 |
72 |
|
68 |
73 |
Ik raad aan om de licenties op de video's te vervangen met de |
69 |
74 |
[[https://creativecommons.org/licenses/by/4.0/deed.nl][CC-BY 4.0]]-licentie, die deze problemen direct oplost. Dit kan ook gedaan worden als beslist |
70 |
75 |
wordt de video's op YouTube te laten staan. |
71 |
76 |
|
+ |
77 |
*** Google & privacy |
+ |
78 |
|
+ |
79 |
|
+ |
80 |
docs/thesis/beveiliging.org ¶
50 additions and 0 deletions.
View changes Hide changes
+ |
1 |
Web-applicaties worden blootgesteld aan tal van mogelijke beveiligingsproblemen. |
+ |
2 |
Een beetje /cracker/ kan een onbeveiligde website in weinig tijd |
+ |
3 |
binnendringen.\\ |
+ |
4 |
Mede dankzij het gebruik van Django zijn de meeste mogelijke veiligheidslekken |
+ |
5 |
gedicht. De belangrijkste onderwerpen (en gemaakte afwegingen) worden hier uitgelegd. |
+ |
6 |
** Cross site scripting |
+ |
7 |
Cross site scripting (XSS) laat toe om scripts te injecteren in de browsers van andere |
+ |
8 |
gebruikers door bv. scripts op te slaan in de databank, die dan later worden |
+ |
9 |
uitgelezen en op de computer van andere gebruikers wordt uitgevoerd. |
+ |
10 |
|
+ |
11 |
Binnen Django wordt XSS opgelost door middel van |
+ |
12 |
/[[https://docs.djangoproject.com/en/2.0/ref/templates/language/#automatic-html-escaping][automatic HTML escaping]]/; de data die gebruikers ingeven, wordt behandeld als "onveilig". |
+ |
13 |
*** Orgmode |
+ |
14 |
Binnen Joeni kan gebruik worden gemaakt van Orgmode-syntax om teksten van |
+ |
15 |
opmaak te voorzien, zoals vette tekst, hyperlinks, lijsten enz. |
+ |
16 |
|
+ |
17 |
Het is technisch gezien ook mogelijk om broncode in Org te markeren, zelfs om |
+ |
18 |
deze uit te laten voeren, met alle [[https://orgmode.org/org.html#Code-evaluation-security][veiligheidsrisico's]] die daaruit volgen. |
+ |
19 |
|
+ |
20 |
Ik heb toch geopteerd om Orgmode-syntax toe te laten voor data die door |
+ |
21 |
gebruikers wordt verstuurd, maar *enkel* voor personeel van de UHasselt. |
+ |
22 |
Personeelsleden kunnen makkelijker aansprakelijk gehouden worden voor misbruik |
+ |
23 |
van de hen aangeleverde voorrechten, en de mogelijke gevolgen van dergelijk misbruik |
+ |
24 |
zullen volgens mij afdoende werken om XSS via Orgmode tegen te gaan. Moest dat |
+ |
25 |
niet kloppen, dan kan Orgmode altijd gemakkelijk uitgeschakeld worden, slechts |
+ |
26 |
met een tolereerbaar verlies aan opmaak binnen Joeni. |
+ |
27 |
|
+ |
28 |
** Externe malafide programmatuur |
+ |
29 |
Bij het opvragen van scripts buiten de eigen server is er steeds een risico dat |
+ |
30 |
er malafide code wordt binnengehaald; men weet niet welke acties ondernomen |
+ |
31 |
worden op die andere servers om kraken tegen te gaan.\\ |
+ |
32 |
Dit is al verscheidene keren voorgevallen |
+ |
33 |
toe nog altijd niet opgelost, omdat het niet kán worden opgelost: Downloaden van |
+ |
34 |
ongecontroleerde scripts houdt een inherent veiligheidsrisico in. |
+ |
35 |
|
+ |
36 |
Joeni lost dit op door simpelweg _geen_ externe scripts in te laden. Dit is |
+ |
37 |
namelijk ook een reden waarom /client-side scripting/ overal vermeden wordt; |
+ |
38 |
het opent een deur tot problemen waarover geen interne controle mogelijk is. |
+ |
39 |
|
+ |
40 |
Alle software, media, /stylesheets/, bibliotheken, ... worden vanaf de server |
+ |
41 |
gedraaid. |
+ |
42 |
** Clickjacking |
+ |
43 |
|
+ |
44 |
** Cross site request forgery |
+ |
45 |
Cross site request forgery (CSRF) |
+ |
46 |
|
+ |
47 |
** Kraken van wachtwoorden en -zinnen |
+ |
48 |
*** Geen verplichte wijzigingen |
+ |
49 |
|
+ |
50 |
docs/thesis/huisstijl.org ¶
6 additions and 0 deletions.
View changes Hide changes
1 |
1 |
Een mooi uitgewerkte huisstijl is essentieel voor het creëren van een gevoel van |
2 |
2 |
samenhorigheid met verschillende entiteiten: Ze dienen ter onderscheiding van de |
3 |
3 |
rest, en |
4 |
4 |
In Joeni wordt de huisstijl van de UHasselt verder uitgewerkt en gebruikt. In |
5 |
5 |
dit hoofdstuk wordt dit in detail uitgelegd, zodat het als basis kan dienen voor |
6 |
6 |
andere software. |
7 |
7 |
** Logo |
+ |
8 |
Ik vermoed dat een deel van de huisstijl simpelweg niet met de studenten mede |
+ |
9 |
gedeeld wordt; op de website wordt doorgaans gelinkt naar een "Huisstijl voor |
+ |
10 |
studenten", wat doet vermoeden dat er ook een "Huisstijl voor personeel" moet |
+ |
11 |
zijn, of iets in die aard. |
+ |
12 |
|
+ |
13 |
** Logo |
8 |
14 |
Joeni heeft tot doel als vervanging te dienen voor het Studentendossier en |
9 |
15 |
BlackBoard. In combinatie met de relatie tot de UHasselt, wordt voor Joeni het |
10 |
16 |
volgende logo gebruikt: |
11 |
17 |
|
12 |
18 |
#+CAPTION: Logo van Joeni |
13 |
19 |
#+NAME: fig:joeni-logo |
14 |
20 |
[[./images/joeni-logo.png]] |
15 |
21 |
|
16 |
22 |
Het logo is duidelijk afgeleid van het logo van de UHasselt. De driehoekjes zijn |
17 |
23 |
zo geplaatst dat het geheel lijkt op een HTML-element, om het verband als |
18 |
24 |
webapplicatie aan te duiden. |
19 |
25 |
|
20 |
26 |
*** UHasselt |
21 |
27 |
Het logo van de UHasselt is op verschillende plaatsen te vinden op de website. |
22 |
28 |
De meest prominente locatie is de titelbalk. |
23 |
29 |
|
24 |
30 |
#+CAPTION: Afbeelding van hoe het logo van de UHasselt in de titelbalk verwerkt is |
25 |
31 |
#+NAME: fig:uhasselt-titlebar |
26 |
32 |
[[./img/a.jpg]] |
27 |
33 |
|
28 |
34 |
Alhoewel de vorm van de driehoekjes lichtjes afwijkt van het echte logo, valt |
29 |
35 |
dit nauwelijks op. Het logo verwerken in de titel is mogelijk, omdat dit gewoon |
30 |
36 |
tekst is, meer specifiek, ▶ ~U+25B6 BLACK RIGHT-POINTING TRIANGLE~. |
31 |
37 |
|
32 |
38 |
Het voordeel hieraan is niet louter een besparing op bytes.[fn:utf-8] Omdat dit |
33 |
39 |
tekst is, zal de betere webbrowser in staat zijn om, afhankelijk van de |
34 |
40 |
achtergrond, het logo de juiste kleur te geven. Op de volgende afbeelding is dit |
35 |
41 |
effect duidelijk te zien, zeker in contrast met het logo dat de |
36 |
42 |
UHasselt-bibliotheek gebruikt. |
37 |
43 |
|
38 |
44 |
[fn:utf-8] Meestal wordt een logo meegestuurd die langs de titel wordt |
39 |
45 |
weergegeven in de titelbalk. Dat is niet fout, maar ze zijn wel een stuk groter in het |
40 |
46 |
aantal bytes. [[https://bibliotheek.uhasselt.be/sites/default/files/favicon.ico][Het logo dat UHasselt gebruikt op haar eigen website]] is 1,150 |
41 |
47 |
bytes groot, [[https://www.fileformat.info/info/unicode/char/25b6/index.htm][▶(×2) is slechts 3(×2) bytes groot]], dus een aanzienlijke verkleining van de |
42 |
48 |
benodigde ruimte. |
43 |
49 |
|
44 |
50 |
#+CAPTION: Vergelijking tussen logo via UTF-8/tekst en als afbeelding. Merk op hoe het logo van de bibliotheek praktisch onzichtbaar is bij de zwarte achtergrond. |
45 |
51 |
#+NAME: fig:utf8-better |
46 |
52 |
[[./img/a.jpg]] |
47 |
53 |
|
48 |
54 |
** Lettertype |
49 |
55 |
Op de website wordt als [[https://www.uhasselt.be/UH/Help-Studenten-Algemene_Help-Huisstijl/Lettertype.html][lettertype]] Verdana aangereikt. Dit strookt echter niet |
50 |
56 |
met het lettertype dat wordt gebruikt in de vele documenten die de UHasselt |
51 |
57 |
verspreidt, gaande van informatieboekjes over de studierichtingen tot de |
52 |
58 |
spandoeken die buiten aan het universiteitsgebouw worden opgehangen. |
53 |
59 |
|
54 |
60 |
Het wordt nergens vermeld, maar dit lettertype gelijkt volledig op |
55 |
61 |
[[https://design.ubuntu.com/font/][het Ubuntu-lettertype]], zoals de volgende vergelijking duidelijk aantoont. |
56 |
62 |
|
57 |
63 |
#+CAPTION: Vergelijking tussen lettertype gebruikt door UHasselt en Ubuntu-lettertype op voorbeeldtekst |
58 |
64 |
#+NAME: fig:font-comparison |
59 |
65 |
[[./images/font-comparison.png]] |
60 |
66 |
|
61 |
67 |
Dit lettertype wordt doorheen Joeni gebruikt voor het aanduide van belangrijke |
62 |
68 |
(kop)teksten. |
63 |
69 |
|
64 |
70 |
** Kleuren |
65 |
71 |
De UHasselt gebruikt in de informatiebrochures wel een kleurenpalet dat eigen |
66 |
72 |
lijkt te zijn aan de bepaalde faculteiten, maar verder wordt het haast nergens |
67 |
73 |
anders gebruikt. Vermoedelijk komt dit omdat nergens in de huisstijl wordt |
68 |
74 |
vermeld welke kleuren gebruikt kunnen worden voor wat.\\ |
69 |
75 |
Hieronder worden de verschillende kleurwaarden voor elke faculteit uiteengezet. |
70 |
76 |
Ze geven een eigen gezicht aan de documentatie binnen elke faculteit, en kunnen |
71 |
77 |
makkelijk gebruikt worden voor zowel voor- als achtergrondkleuren. |
72 |
78 |
|
73 |
79 |
#+CAPTION: Een kleurenpalet dat gebruikt kan worden voor de huisstijl van de UHasselt verder uit te bouwen |
74 |
80 |
#+NAME: fig:color-swatches |
75 |
81 |
[[./images/color-swatches.png]] |
docs/thesis/master.org ¶
3 additions and 0 deletions.
View changes Hide changes
1 |
1 |
#+language: nl |
2 |
2 |
#+latex_class: article |
3 |
3 |
#+latex_class_options: [a4paper] |
4 |
4 |
#+latex_header: \usepackage{pdfpages} |
5 |
5 |
#+latex_header: \usepackage[dutch]{babel} |
6 |
6 |
#+OPTIONS: toc:nil |
7 |
7 |
#+author: Maarten Vangeneugden @@latex:\\@@ prof. dr. Wim Lamotte |
8 |
8 |
#+date: 2017-2018 |
9 |
9 |
#+title: Joeni |
10 |
10 |
#+subtitle: Bachelorproef voorgedragen tot het behalen van de graad van bachelor in de informatica |
11 |
11 |
|
12 |
12 |
* COMMENT Structuur |
13 |
13 |
1. Samenvatting |
14 |
14 |
2. Voorwoord |
15 |
15 |
3. Inhoudsopgave |
16 |
16 |
|
17 |
17 |
|
18 |
18 |
* Samenvatting |
19 |
19 |
|
20 |
20 |
* Voorwoord |
21 |
21 |
|
22 |
22 |
* Inhoudsopgave |
23 |
23 |
#+TOC: headlines 6 |
24 |
24 |
|
25 |
25 |
#+INCLUDE: "./aandachtspunten.org" |
+ |
26 |
#+INCLUDE: "./softwarekeuzes.org" |
+ |
27 |
|
+ |
28 |
docs/thesis/softwarekeuzes.org ¶
8 additions and 2 deletions.
View changes Hide changes
1 |
1 |
In dit hoofdstuk wordt dieper ingegaan op de verantwoording voor de gekozen |
2 |
2 |
software, en worden de mogelijke alternatieven besproken. |
3 |
3 |
** Softwareframework |
4 |
4 |
*** Django |
5 |
5 |
Voor het schrijven van Joeni heb ik uitgesproken gebruik gemaakt van |
6 |
6 |
[[https://www.djangoproject.com/][het Django-framework]]. |
7 |
7 |
**** Webframework |
8 |
8 |
Django is een framework gericht op het ontwikkelen van websites. Omdat Joeni |
9 |
9 |
hoort te dienen als een directe vervanging voor het Studentendossier en |
10 |
10 |
BlackBoard, zou een website bouwen de overgang het gemakkelijkst laten gebeuren. |
11 |
11 |
|
+ |
12 |
Het was vanaf het begin al duidelijk dat Joeni een databank zou gaan bevatten, |
+ |
13 |
compleet met tabellen, vreemde sleutels en voorwaarden. |
+ |
14 |
|
12 |
15 |
*** Python (3) |
13 |
16 |
Alhoewel er wel voor elke programmeertaal één of meerdere frameworks voor het |
14 |
17 |
maken van websites bestaan, ging de voorkeur uit naar Python. |
15 |
18 |
|
16 |
19 |
*** UTF-8 |
17 |
20 |
Joeni maakt uitsluitend gebruik van UTF-8 voor encodering van tekst.\\ |
18 |
21 |
Niet alleen is dit standaard voor strings in Python 3, het laat ook toe om het |
19 |
22 |
logo van de UHasselt in de titelbalk te verwerken, en zorgt ervoor dat alle |
20 |
23 |
teksten correct gespeld en opgeslagen kunnen worden. |
21 |
- | |
+ |
24 |
Dit is ook handig voor Erasmusstudenten met vreemde namen zoals bv. |
+ |
25 |
"Kürt Pðovski", die hun echte naam kunnen gebruiken, zonder substituten te |
+ |
26 |
moeten gebruiken voor bepaalde tekens. |
+ |
27 |
|
22 |
28 |
* Andere mogelijkheden |
23 |
29 |
Tegenover de gemaakte keuzes staan ook een hoop alternatieven die niet werden |
24 |
30 |
gebruikt. |
25 |
31 |
** Ruby (on Rails) |
26 |
32 |
Mijn ervaring met Ruby is simpelweg onbestaande; ik heb nooit in deze taal |
27 |
33 |
geprogrammeerd. De beperkte tijd die ik had voor de bachelorproef stond niet toe |
28 |
34 |
dat ik mij nog zou verdiepen in een nieuwe programmeertaal. |
29 |
35 |
** PHP |
30 |
36 |
Ongetwijfeld een van de meest gebruikte talen voor het schrijven van dynamische |
31 |
37 |
websites.\\ |
32 |
38 |
Het probleem is compleet het tegenovergestelde van [[Ruby (on Rails][Ruby]]; PHP ken ik zeer goed. |
33 |
- | Het is daarom dat ik dit niet beschouw als een goede keuze; de taal zit vol met |
+ |
39 |
Het is daarom dat ik dit niet beschouw als een goede keuze; de taal zit vol met |
34 |
40 |
beveiligingslekken, vertoont extreem raar gedrag dat niet voorkomt in Python, |
35 |
41 |
docs/thesis/voorwoord.org ¶
11 additions and 0 deletions.
View changes Hide changes
+ |
1 |
|
+ |
2 |
* Voorwoord |
+ |
3 |
In deze bachelorthesis komen verschillende onderwerpen aan bod, op te delen in |
+ |
4 |
die omvattende elementen: |
+ |
5 |
- Bespreking van Joeni :: Joeni is de praktische uitwerking van deze |
+ |
6 |
bachelorproef. Het stelt een vervanging voor voor zowel het |
+ |
7 |
Studentendossier als BlackBoard, onderdelen die duidelijk een functionele |
+ |
8 |
en visuele /overhaul/ nodig hebben. |
+ |
9 |
- Voorstellen voor ALIPA :: Deze bachelorproef staat in het teken van ALIPA, om |
+ |
10 |
mogelijke oplossingen uit te werken, en deze dan voor te stellen. |
+ |
11 |
joeni/settings.py ¶
1 addition and 1 deletion.
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 |
# TEMP Password: adminadmin | admin |
24 |
24 |
|
25 |
25 |
# SECURITY WARNING: don't run with debug turned on in production! |
26 |
26 |
DEBUG = True |
27 |
27 |
|
28 |
28 |
ALLOWED_HOSTS = [] |
29 |
29 |
|
30 |
30 |
|
31 |
31 |
# Application definition |
32 |
32 |
|
33 |
33 |
INSTALLED_APPS = [ |
34 |
34 |
'django.contrib.admin', |
35 |
35 |
'django.contrib.auth', |
36 |
36 |
'django.contrib.contenttypes', |
37 |
37 |
'django.contrib.humanize', |
38 |
38 |
'django.contrib.sessions', |
39 |
39 |
'django.contrib.messages', |
40 |
40 |
'django.contrib.staticfiles', |
41 |
41 |
'administration', |
42 |
42 |
'agora', |
43 |
43 |
'courses', |
44 |
44 |
'joeni', |
45 |
45 |
] |
46 |
46 |
|
47 |
47 |
MIDDLEWARE = [ |
48 |
48 |
'django.middleware.security.SecurityMiddleware', |
49 |
49 |
'django.middleware.locale.LocaleMiddleware', |
50 |
50 |
'django.middleware.common.CommonMiddleware', |
51 |
51 |
'django.middleware.csrf.CsrfViewMiddleware', |
52 |
52 |
'django.contrib.sessions.middleware.SessionMiddleware', |
53 |
53 |
'django.contrib.auth.middleware.AuthenticationMiddleware', |
54 |
54 |
'django.contrib.sessions.middleware.SessionMiddleware', |
55 |
55 |
'django.contrib.messages.middleware.MessageMiddleware', |
56 |
56 |
'django.middleware.clickjacking.XFrameOptionsMiddleware', |
57 |
57 |
'django.middleware.security.SecurityMiddleware', |
58 |
58 |
# Caching middleware |
59 |
59 |
'django.middleware.cache.UpdateCacheMiddleware', |
60 |
60 |
'django.middleware.common.CommonMiddleware', |
61 |
61 |
'django.middleware.cache.FetchFromCacheMiddleware', |
62 |
62 |
] |
63 |
63 |
|
64 |
64 |
# Caching settings |
65 |
65 |
CACHE_MIDDLEWARE_ALIAS = 'default' |
66 |
66 |
CACHE_MIDDLEWARE_SECONDS = 300 |
67 |
67 |
CACHE_MIDDLEWARE_KEY_PREFIX = '' |
68 |
68 |
|
69 |
69 |
ROOT_URLCONF = 'joeni.urls' |
70 |
70 |
|
71 |
71 |
TEMPLATES = [ |
72 |
72 |
{ |
73 |
73 |
'BACKEND': 'django.template.backends.django.DjangoTemplates', |
74 |
74 |
'DIRS': [os.path.join(BASE_DIR, 'templates')], |
75 |
75 |
'APP_DIRS': True, |
76 |
76 |
'OPTIONS': { |
77 |
77 |
'context_processors': [ |
78 |
78 |
'django.template.context_processors.debug', |
79 |
79 |
'django.template.context_processors.request', |
80 |
80 |
'django.contrib.auth.context_processors.auth', |
81 |
81 |
'django.contrib.messages.context_processors.messages', |
82 |
82 |
], |
83 |
83 |
}, |
84 |
84 |
}, |
85 |
85 |
] |
86 |
86 |
|
87 |
87 |
#WSGI_APPLICATION = 'joeni.wsgi.application' |
88 |
88 |
|
89 |
89 |
|
90 |
90 |
# Database |
91 |
91 |
# https://docs.djangoproject.com/en/dev/ref/settings/#databases |
92 |
92 |
|
93 |
93 |
DATABASES = { |
94 |
94 |
'default': { |
95 |
95 |
'ENGINE': 'django.db.backends.sqlite3', |
96 |
96 |
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), |
97 |
97 |
} |
98 |
98 |
} |
99 |
99 |
|
100 |
100 |
# Page to display when a user needs / wants to log in |
101 |
101 |
LOGIN_URL = '/administration/login' |
102 |
- | |
+ |
102 |
|
103 |
103 |
# Custom User model |
104 |
104 |
AUTH_USER_MODEL = 'administration.User' |
105 |
105 |
|
106 |
106 |
# Password validation |
107 |
107 |
# https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators |
108 |
108 |
|
109 |
109 |
AUTH_PASSWORD_VALIDATORS = [] |
110 |
110 |
if not DEBUG: |
111 |
111 |
# XXX: For testing and easily creating new accounts, I've disabled the |
112 |
112 |
# validators to make it easier to work with. |
113 |
113 |
AUTH_PASSWORD_VALIDATORS = [ |
114 |
114 |
{ |
115 |
115 |
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', |
116 |
116 |
}, |
117 |
117 |
{ |
118 |
118 |
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', |
119 |
119 |
}, |
120 |
120 |
{ |
121 |
121 |
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', |
122 |
122 |
}, |
123 |
123 |
{ |
124 |
124 |
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', |
125 |
125 |
}, |
126 |
126 |
] |
127 |
127 |
|
128 |
128 |
CACHES = { |
129 |
129 |
'default': { |
130 |
130 |
'BACKEND': 'django.core.cache.backends.dummy.DummyCache', |
131 |
131 |
} |
132 |
132 |
} |
133 |
133 |
|
134 |
134 |
# Internationalization |
135 |
135 |
# https://docs.djangoproject.com/en/dev/topics/i18n/ |
136 |
136 |
|
137 |
137 |
LANGUAGE_CODE = 'en-us' |
138 |
138 |
|
139 |
139 |
TIME_ZONE = 'UTC' |
140 |
140 |
|
141 |
141 |
USE_I18N = True |
142 |
142 |
|
143 |
143 |
USE_L10N = True |
144 |
144 |
|
145 |
145 |
USE_TZ = True |
146 |
146 |
|
147 |
147 |
|
148 |
148 |
# Static files (CSS, JavaScript, Images) |
149 |
149 |
# https://docs.djangoproject.com/en/dev/howto/static-files/ |
150 |
150 |
|
151 |
151 |
STATICFILES_DIRS = [ |
152 |
152 |
BASE_DIR + "/static", |
153 |
153 |
] |
154 |
154 |
#STATIC_ROOT = BASE_DIR + '/static/' |
155 |
155 |
STATIC_URL = '/static/' |
156 |
156 |
MEDIA_URL = '/media/' |
157 |
157 |
joeni/urls.py ¶
1 addition and 1 deletion.
View changes Hide changes
1 |
1 |
|
2 |
2 |
The `urlpatterns` list routes URLs to views. For more information please see: |
3 |
3 |
https://docs.djangoproject.com/en/dev/topics/http/urls/ |
4 |
4 |
Examples: |
5 |
5 |
Function views |
6 |
6 |
1. Add an import: from my_app import views |
7 |
7 |
2. Add a URL to urlpatterns: path('', views.home, name='home') |
8 |
8 |
Class-based views |
9 |
9 |
1. Add an import: from other_app.views import Home |
10 |
10 |
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') |
11 |
11 |
Including another URLconf |
12 |
12 |
1. Import the include() function: from django.urls import include, path |
13 |
13 |
2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) |
14 |
14 |
""" |
15 |
15 |
from django.contrib import admin |
16 |
16 |
from django.urls import path, include |
17 |
17 |
from django.conf.urls.i18n import i18n_patterns |
18 |
18 |
from django.conf.urls.static import static |
19 |
19 |
from django.utils.translation import ugettext_lazy as _ |
20 |
20 |
from . import settings |
21 |
21 |
import agora |
22 |
22 |
import administration |
23 |
23 |
|
24 |
24 |
urlpatterns = [ |
25 |
25 |
path('admin/', admin.site.urls), |
26 |
26 |
path('agora/', include('agora.urls')), |
27 |
27 |
path('administration/', include('administration.urls')), |
28 |
- | path('courses/', include('courses.urls')), |
+ |
28 |
path('courses/', include('courses.urls')), |
29 |
29 |
# path(_('administration/'), include('administration.urls')), |
30 |
30 |
# path(_('courses/'), include('courses.urls')), |
31 |
31 |
|
32 |
32 |
] + static(settings.STATIC_URL)#, document_root=settings.STATIC_ROOT) |
33 |
33 |
#urlpatterns += i18n_patterns( |
34 |
34 |
#) |
35 |
35 |