Start isolation process of roster template
The roster template can easily be used in other templates as well, given that the necessary variables are sent to the template. In order to achieve this, the roster functionality is being isolated to make it available for inclusion in other templates.
The prototype CSS file has had a little update for conflicting events.
- Author
- Maarten 'Vngngdn' Vangeneugden
- Date
- March 8, 2018, 1:17 p.m.
- Hash
- c4000e6b0bd9ab87a54f5bfe85b0e261c0028630
- Parent
- 8dbdd43fe6eb108520faa6b8ddf1be04ece74f0a
- Modified files
- administration/new_roster.py
- administration/templates/administration/roster.djhtml
- administration/templates/administration/roster_t.djhtml
- administration/views.py
- joeni/templates/joeni/base.djhtml
- static/css/base.css
administration/new_roster.py ¶
19 additions and 7 deletions.
View changes Hide changes
1 |
1 |
building of the roster. """ |
2 |
2 |
from django.shortcuts import render |
3 |
3 |
from collections import OrderedDict |
4 |
4 |
import datetime |
5 |
5 |
from django.urls import reverse |
6 |
6 |
from django.utils.translation import gettext as _ |
7 |
7 |
from .models import * |
8 |
8 |
import administration |
9 |
9 |
|
10 |
10 |
def same_day(datetime_a, datetime_b): |
11 |
11 |
"""True if both a and b are on the same day, false otherwise.""" |
12 |
12 |
return ( |
13 |
13 |
datetime_a.day == datetime_b.day and |
14 |
14 |
datetime_a.month == datetime_b.month and |
15 |
15 |
datetime_a.year == datetime_b.year) |
16 |
16 |
|
17 |
17 |
def same_daytime(moment, hour, minute): |
18 |
18 |
return (moment.minute == minute and moment.hour == hour) |
19 |
19 |
|
20 |
20 |
def conflicting_events(events): |
21 |
21 |
"""Finds conflicting events in the given set, and returns a list of all |
22 |
22 |
conflicting events, grouped according to their conflicts in lists.""" |
23 |
23 |
conflicts = list() |
24 |
24 |
test_conflict = list() |
25 |
25 |
#for event in events.order_by("begin_time"): |
26 |
26 |
for event in events: |
27 |
27 |
if len(test_conflict) == 0: |
28 |
28 |
test_conflict.append(event) |
29 |
29 |
else: |
30 |
30 |
possible_conflict = test_conflict.pop() |
31 |
31 |
#print("comparing" + str(possible_conflict) + " - " + str(event)) |
32 |
32 |
if possible_conflict.end_time > event.begin_time: # New conflict! |
33 |
33 |
test_conflict.append(possible_conflict) |
34 |
34 |
test_conflict.append(event) |
35 |
35 |
#print(test_conflict) |
36 |
36 |
elif len(test_conflict) == 0: # No conflict |
37 |
37 |
test_conflict.append(event) |
38 |
38 |
else: # No conflict, but previous conflicting events exist |
39 |
39 |
test_conflict.append(possible_conflict) |
40 |
40 |
conflicts.append(test_conflict.copy()) |
41 |
41 |
test_conflict.clear() |
42 |
42 |
if len(test_conflict) >= 2: |
43 |
43 |
conflicts.append(test_conflict.copy()) |
44 |
44 |
return conflicts |
45 |
45 |
|
46 |
46 |
def replace_conflict(events, conflicts): |
47 |
47 |
"""Removes the conflicts from events, and replaces them with a new |
48 |
48 |
event. Returns the key number for this conflict set. Expects the conflicts |
49 |
49 |
to be in chronological order based on begin_time.""" |
50 |
50 |
for conflict in conflicts: |
51 |
51 |
events.remove(conflict) |
52 |
52 |
conflict_number = 0 |
53 |
53 |
for event in events: |
54 |
54 |
if event.note == "Conflict " + str(conflict_number): |
55 |
55 |
conflict_number += 1 |
56 |
56 |
|
57 |
57 |
replacement = Event( |
58 |
58 |
begin_time = conflicts[0].begin_time, |
59 |
59 |
end_time = conflicts[-1].end_time, |
60 |
60 |
note = "Conflict " + str(conflict_number), |
61 |
61 |
) |
62 |
62 |
events.append(replacement) |
63 |
63 |
return conflict_number |
64 |
64 |
|
65 |
65 |
def replace_conflicts(events): |
66 |
66 |
"""Searches for all events that are in conflict regarding timespans, and |
67 |
67 |
replaces them with a general event. Returns a dictionary with keys referencing |
68 |
68 |
a specific conflict block, and the values being a list of the events that are |
69 |
69 |
in conflict with each other.""" |
70 |
70 |
all_conflicts = conflicting_events(events) |
71 |
71 |
conflict_dict = dict() |
72 |
72 |
for conflict_list in all_conflicts: |
73 |
73 |
conflict_number = replace_conflict(events, conflict_list) |
74 |
74 |
conflict_dict[conflict_number] = conflict_list |
75 |
75 |
return conflict_dict |
76 |
76 |
|
77 |
77 |
def create_roster_event(event_type, quarters, content, style="", note=""): |
78 |
78 |
return '<td class="{event_type}" style="{style}" {title} rowspan="{quarters}">{content}</td>'.format( |
79 |
79 |
event_type=event_type, |
80 |
80 |
style=style, |
81 |
81 |
quarters=quarters, |
82 |
82 |
content=content, |
83 |
83 |
title=note) |
84 |
84 |
|
85 |
85 |
def create_roster_conflict_event(event): |
86 |
86 |
quarters = (event.end_time - event.begin_time).seconds // 60 // 15 |
87 |
87 |
content = _('<strong>Conflicting events!<br />See <a href="#'+event.note+'">'+event.note+'</a> for more information.</strong>') |
88 |
88 |
return create_roster_event("event-conflict", quarters, content) |
89 |
89 |
|
90 |
90 |
def create_roster_study_event(event): |
91 |
91 |
pass # TODO |
92 |
92 |
def create_roster_university_event(event): |
93 |
93 |
pass # TODO |
94 |
94 |
def create_roster_course_event(event): |
95 |
95 |
# FIXME: Currently not all users are equipped with a user_data object. |
96 |
96 |
# Because of this, reversing the link to the docent's page may throw a |
97 |
97 |
# RelatedObjectDoesNotExist error. Until that's resolved, I've wrapped |
98 |
98 |
# creating that link in a try catch. |
99 |
99 |
docent_link = "" |
100 |
100 |
try: |
101 |
101 |
docent_link = reverse('administration-user', args=(event.docent.user_data.slug_name(),)) |
102 |
102 |
except : |
103 |
103 |
pass |
104 |
104 |
|
105 |
105 |
quarters = (event.end_time - event.begin_time).seconds // 60 // 15 |
106 |
106 |
course_link = reverse('courses-course-index', args=(event.course.course.slug_name,)) |
107 |
107 |
room_link = reverse('administration-room-detail', args=(str(event.room),)) |
108 |
108 |
event_type = "event" |
109 |
109 |
|
110 |
110 |
content = "{course}<br /> {docent}<br />{begin} - {end}<br /> {room} ({subject})".format( |
111 |
111 |
course = '<a href="'+course_link+'">'+str(event.course)+'</a>', |
112 |
112 |
docent = '<a href="'+docent_link+'">'+str(event.docent)+'</a>', |
113 |
113 |
begin = event.begin_time.strftime("%H:%M"), |
114 |
114 |
end = event.end_time.strftime("%H:%M"), |
115 |
115 |
room = '<a href="'+room_link+'">'+str(event.room)+'</a>', |
116 |
116 |
subject = event.subject, |
117 |
117 |
) |
118 |
118 |
|
119 |
119 |
style = "background-color: #"+event.course.course.color+"; color: white;" |
120 |
120 |
|
121 |
121 |
if event.recently_created(): |
122 |
122 |
event_type = "event-new" |
123 |
123 |
style = "" |
124 |
124 |
elif event.recently_updated(): |
125 |
125 |
event_type = "event-update" |
126 |
126 |
style = "" |
127 |
127 |
elif event.note != "": |
128 |
128 |
event_type = "event-note" |
129 |
129 |
|
130 |
130 |
return create_roster_event(event_type, quarters, content, style, event.note) |
131 |
131 |
|
132 |
132 |
|
133 |
133 |
|
134 |
134 |
|
135 |
135 |
|
136 |
136 |
def first_last_day(events): |
137 |
137 |
"""Returns the first and last day of the starting times of the given events.""" |
138 |
138 |
earliest = events[0].begin_time |
139 |
139 |
latest = events[0].begin_time |
140 |
140 |
for event in events: |
141 |
141 |
if event.begin_time < earliest: |
142 |
142 |
earliest = event.begin_time |
143 |
143 |
if event.begin_time > latest: |
144 |
144 |
latest = event.begin_time |
145 |
145 |
first = datetime.date(earliest.year, earliest.month, earliest.day) |
146 |
146 |
last = datetime.date(latest.year, latest.month, latest.day) |
147 |
147 |
return first, last |
148 |
148 |
|
149 |
149 |
|
150 |
150 |
|
151 |
151 |
|
152 |
152 |
def create_roster_for_event(event, conflict=False): |
153 |
153 |
"""Determines which function to call to build the roster entry for the given event.""" |
154 |
154 |
if conflict and isinstance(event, Event): |
155 |
155 |
return create_roster_conflict_event(event) |
156 |
156 |
elif isinstance(event, CourseEvent): |
157 |
157 |
return create_roster_course_event(event) |
158 |
158 |
elif isinstance(event, UniversityEvent): |
159 |
159 |
return create_roster_university_event(event) |
160 |
160 |
elif isinstance(event, StudyEvent): |
161 |
161 |
return create_roster_study_event(event) |
162 |
162 |
elif isinstance(event, Event): |
163 |
163 |
return create_roster_event(event) |
164 |
164 |
else: |
165 |
165 |
raise TypeError("Given object is not of any Event type") |
166 |
166 |
|
167 |
167 |
def make_quarter_row(events, hour, quarter): |
168 |
- | """Creates an HTML row for a quarter. Expects *NO conflicts!*""" |
169 |
- | if len(conflicting_events(events)) != 0: |
170 |
- | print(conflicting_events(events)) |
171 |
- | #raise ValueError("events contains conflicts!") |
172 |
- | |
173 |
- | quarter_line = "<tr><td style='font-size: xx-small;'>" |
174 |
- | if hour < 10: |
+ |
168 |
"""Creates and returns the first part of the quarter row, which is the |
+ |
169 |
column with the current time in it.""" |
+ |
170 |
quarter_line = "<tr><td style='font-size:" |
+ |
171 |
if quarter != 0: |
+ |
172 |
quarter_line += "xx-small; color: grey;" |
+ |
173 |
else: |
+ |
174 |
quarter_line += "x-small;" |
+ |
175 |
quarter_line += "'>" |
+ |
176 |
if hour < 10: |
175 |
177 |
quarter_line += "0" |
176 |
178 |
quarter_line += str(hour) +":" |
177 |
179 |
if quarter == 0: |
178 |
180 |
quarter_line += "0" |
179 |
181 |
quarter_line += str(quarter) |
180 |
182 |
quarter_line += "</td>" |
181 |
183 |
|
+ |
184 |
|
+ |
185 |
|
+ |
186 |
def make_quarter_row(events, hour, quarter): |
+ |
187 |
"""Creates an HTML row for a quarter. Expects *NO conflicts!*""" |
+ |
188 |
if len(conflicting_events(events)) != 0: |
+ |
189 |
print(conflicting_events(events)) |
+ |
190 |
raise ValueError("events contains conflicts!") |
+ |
191 |
|
+ |
192 |
quarter_line = make_first_quarter_column(hour, quarter) |
+ |
193 |
|
182 |
194 |
first_day, last_day = first_last_day(events) |
183 |
195 |
for i in range((first_day - last_day).days, 1): |
184 |
196 |
column_added = False |
185 |
197 |
current_day = (last_day + datetime.timedelta(days=i)) |
186 |
198 |
current_daytime = datetime.datetime( |
187 |
199 |
current_day.year, |
188 |
200 |
current_day.month, |
189 |
201 |
current_day.day, |
190 |
202 |
hour=hour, |
191 |
203 |
minute=quarter, |
192 |
204 |
tzinfo = datetime.timezone.utc) |
193 |
205 |
for event in events: |
194 |
206 |
if (same_day(event.begin_time, current_day) and |
195 |
207 |
same_daytime(event.begin_time, hour, quarter)): # Event starts on this quarter |
196 |
208 |
quarter_line += create_roster_for_event( |
197 |
209 |
event, |
198 |
210 |
conflict = (event.note.startswith("Conflict "))) |
199 |
211 |
column_added = True |
200 |
212 |
elif (same_day(event.begin_time, current_day) and |
201 |
213 |
event.begin_time < current_daytime and |
202 |
214 |
event.end_time >= current_daytime): |
203 |
215 |
column_added = True |
204 |
216 |
break |
205 |
217 |
if not column_added: |
206 |
218 |
quarter_line += "<td></td>" |
207 |
219 |
quarter_line += "</tr>" |
208 |
220 |
return quarter_line |
209 |
221 |
|
210 |
222 |
|
211 |
223 |
|
212 |
224 |
|
213 |
225 |
def create_roster_rows(events): |
214 |
226 |
if len(events) == 0: |
215 |
227 |
return {}, [] |
216 |
228 |
events = list(events.order_by("begin_time")) |
217 |
229 |
conflict_dict = replace_conflicts(events) |
218 |
230 |
print(conflict_dict) |
219 |
231 |
for event in events: |
220 |
232 |
print(event.begin_time) |
221 |
233 |
table_code = [] |
222 |
234 |
current_quarter = datetime.datetime.now().replace(hour=8, minute=0) |
223 |
235 |
while current_quarter.hour != 20 or current_quarter.minute != 00: |
224 |
236 |
table_code.append(make_quarter_row(events, current_quarter.hour, current_quarter.minute)) |
225 |
237 |
current_quarter += datetime.timedelta(minutes=15) |
226 |
238 |
return conflict_dict, table_code |
227 |
239 |
administration/templates/administration/roster.djhtml ¶
1 addition and 49 deletions.
View changes Hide changes
1 |
1 |
{% cycle "hour" "first quarter" "half" "last quarter" as hour silent %} |
2 |
2 |
{# "silent" blocks the cycle operator from printing the cycler, and in subsequent calls #} |
3 |
3 |
{% load i18n %} |
4 |
4 |
|
5 |
5 |
{% block title %} |
6 |
6 |
{% trans "Roster" %} | {{ block.super }} |
7 |
7 |
{% endblock %} |
8 |
8 |
|
9 |
9 |
{% block main %} |
10 |
10 |
{% include "administration/nav.djhtml" %} |
11 |
11 |
<h1>{% trans "Personal timetable" %}</h1> |
12 |
12 |
<h2>{% trans "Main hour roster" %}</h2> |
13 |
13 |
<style> |
14 |
- | table td { |
15 |
- | border-width: 0px 0px 1px 0px; |
16 |
- | border-bottom-style: solid; |
17 |
- | border-style: solid; |
18 |
- | border-color: red; |
19 |
- | } |
20 |
- | </style> |
21 |
- | <table> |
22 |
- | <th> |
23 |
- | {#<td></td> {# Empty row for hours #} {# Apparantly this isn't necessary with <th /> #} |
24 |
- | {% for day in days %} |
25 |
- | <td>{{ day|date:"l (d/m)" }}</td> |
26 |
- | {% endfor %} |
27 |
- | </th> |
28 |
- | {% for element in time_blocks %} |
29 |
- | {{ element|safe }} |
30 |
- | <!--<tr> |
31 |
- | {% if hour == "hour" %} |
32 |
- | <td>{{ time }}</td> |
33 |
- | {% else %} |
34 |
- | <td></td> |
35 |
- | {% endif %} |
36 |
- | {% cycle hour %} |
37 |
- | <td>{{ time }}</td> |
38 |
- | <td>{{ event }}</td> |
39 |
- | </tr>--> |
40 |
- | {% endfor %} |
41 |
- | </table> |
42 |
- | {% for number, conflict_list in conflicts.items %} |
43 |
- | <h2 id="Conflict {{ number }}">Conflict {{ number }}</h2> |
44 |
- | {% for conflict in conflict_list %} |
45 |
- | {% if conflict.recently_created %} |
46 |
- | <div class="event-new"> |
47 |
- | {% elif conflict.recently_updated %} |
48 |
- | <div class="event-update"> |
49 |
- | {% endif %} |
50 |
- | <a href="{% url "courses-course-index" conflict.course.course.slug_name %}"> |
51 |
- | {{ conflict.course }}</a><br /> |
52 |
- | {# FIXME Temporarily disabled until all users have an associated user_data #} |
53 |
- | {#<a href="{% url "administration-user" conflict.docent.user_data.slug_name %}">#} |
54 |
- | {#{{ conflict.docent }}</a><br />#} |
55 |
- | {{ conflict.docent }}<br /> |
56 |
- | {{ conflict.begin_time|date:"H:i" }} - {{ conflict.end_time|date:"H:i" }}<br /> |
57 |
- | <a href="{% url "administration-room-detail" conflict.room %}"> |
58 |
- | {{ conflict.room }}</a> ({{ conflict.subject }}) |
59 |
- | </div> |
60 |
- | {% endfor %} |
61 |
- | {% endfor %} |
62 |
- | |
63 |
14 |
|
64 |
15 |
|
+ |
16 |
|
65 |
17 |
|
66 |
18 |
<a class="btn" href="{% url "administration-roster" begin=prev_begin end=prev_end %}"> |
67 |
19 |
{% trans "Previous week" %} |
68 |
20 |
</a> |
69 |
21 |
<a class="btn" href="{% url "administration-roster" %}"> |
70 |
22 |
{% trans "Current week" %} |
71 |
23 |
</a> |
72 |
24 |
<a class="btn" href="{% url "administration-roster" begin=next_begin end=next_end %}"> |
73 |
25 |
{% trans "Next week" %} |
74 |
26 |
</a> |
75 |
27 |
{# TODO: Add links to "previous week", "next week" and "current week" with large buttons #} |
76 |
28 |
|
77 |
29 |
<h2>{% trans "Explanation" %}</h2> |
78 |
30 |
<p> |
79 |
31 |
{% trans "Personal roster from" %} {{ begin|date }} {% trans "to" %} {{ end|date }} |
80 |
32 |
</p> |
81 |
33 |
<p> |
82 |
34 |
{% blocktrans %} |
83 |
35 |
Some fields may have additional information that might be of interest |
84 |
36 |
to you. This information is shown in different ways with colour codes. |
85 |
37 |
{% endblocktrans %} |
86 |
38 |
</p> |
87 |
39 |
|
88 |
40 |
<dl> |
89 |
41 |
<dt><span class="event-update"> |
90 |
42 |
{% trans "Recent event update" %} |
91 |
43 |
</span></dt> |
92 |
44 |
<dd> |
93 |
45 |
{% blocktrans %} |
94 |
46 |
This event had one or more of its properties changed |
95 |
47 |
in the last five days. This can be the room, the hours, the subject, ... |
96 |
48 |
You're encouraged to take note of that. |
97 |
49 |
{% endblocktrans %} |
98 |
50 |
</dd> |
99 |
51 |
<dt><span class="event-new"> |
100 |
52 |
{% trans "New event" %} |
101 |
53 |
</span></dt> |
102 |
54 |
<dd> |
103 |
55 |
{% blocktrans %} |
104 |
56 |
This is a new event, added in the last five days. |
105 |
57 |
{% endblocktrans %} |
106 |
58 |
</dd> |
107 |
59 |
<dt><span class="event-note"> |
108 |
60 |
{% trans "Notification available" %} |
109 |
61 |
</span></dt> |
110 |
62 |
<dd> |
111 |
63 |
{% blocktrans %} |
112 |
64 |
This event has a note attached to it by the docent. Hover over |
113 |
65 |
the event to display the note. |
114 |
66 |
{% endblocktrans %} |
115 |
67 |
</dd> |
116 |
68 |
</dl> |
117 |
69 |
|
118 |
70 |
{% endblock main %} |
119 |
71 |
administration/templates/administration/roster_t.djhtml ¶
50 additions and 0 deletions.
View changes Hide changes
+ |
1 |
This is the roster template. You can use this to display a hourly roster, |
+ |
2 |
spread over a series of days. To function properly, this template requires |
+ |
3 |
the following variables: |
+ |
4 |
- days :: A list of all days for which a column must be made. |
+ |
5 |
- time_blocks :: A list of all rows with their respective timings, that must |
+ |
6 |
be displayed in the roster. These elements must *not* contain |
+ |
7 |
any unsafe elements! |
+ |
8 |
- conflicts :: A dictionary with all conflicting events that need to be listed. |
+ |
9 |
{% endcomment %} |
+ |
10 |
|
+ |
11 |
<style> |
+ |
12 |
table td { |
+ |
13 |
border-width: 0px 0px 1px 0px; |
+ |
14 |
border-bottom-style: solid; |
+ |
15 |
border-style: solid; |
+ |
16 |
border-color: red; |
+ |
17 |
} |
+ |
18 |
</style> |
+ |
19 |
<table> |
+ |
20 |
<th> |
+ |
21 |
{#<td></td> {# Empty row for hours #} {# Apparantly this isn't necessary with <th /> #} |
+ |
22 |
{% for day in days %} |
+ |
23 |
<td>{{ day|date:"l (d/m)" }}</td> |
+ |
24 |
{% endfor %} |
+ |
25 |
</th> |
+ |
26 |
{% for element in time_blocks %} |
+ |
27 |
{{ element|safe }} |
+ |
28 |
{% endfor %} |
+ |
29 |
</table> |
+ |
30 |
{% for number, conflict_list in conflicts.items %} |
+ |
31 |
<h2 id="Conflict {{ number }}">Conflict {{ number }}</h2> |
+ |
32 |
{% for conflict in conflict_list %} |
+ |
33 |
{% if conflict.recently_created %} |
+ |
34 |
<div class="event-new"> |
+ |
35 |
{% elif conflict.recently_updated %} |
+ |
36 |
<div class="event-update"> |
+ |
37 |
{% endif %} |
+ |
38 |
<a href="{% url "courses-course-index" conflict.course.course.slug_name %}"> |
+ |
39 |
{{ conflict.course }}</a><br /> |
+ |
40 |
{# FIXME Temporarily disabled until all users have an associated user_data #} |
+ |
41 |
{#<a href="{% url "administration-user" conflict.docent.user_data.slug_name %}">#} |
+ |
42 |
{#{{ conflict.docent }}</a><br />#} |
+ |
43 |
{{ conflict.docent }}<br /> |
+ |
44 |
{{ conflict.begin_time|date:"H:i" }} - {{ conflict.end_time|date:"H:i" }}<br /> |
+ |
45 |
<a href="{% url "administration-room-detail" conflict.room %}"> |
+ |
46 |
{{ conflict.room }}</a> ({{ conflict.subject }}) |
+ |
47 |
</div> |
+ |
48 |
{% endfor %} |
+ |
49 |
{% endfor %} |
+ |
50 |
administration/views.py ¶
18 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 |
from .pingping import update_balance |
10 |
10 |
import administration |
11 |
11 |
from django.contrib.auth.decorators import login_required |
12 |
12 |
from django.contrib.auth import authenticate |
13 |
13 |
|
14 |
14 |
@login_required |
15 |
15 |
def roster(request, begin=None, end=None): |
16 |
16 |
"""Collects and renders the data that has to be displayed in the roster. |
17 |
17 |
|
18 |
18 |
The begin and end date can be specified. Only roster points in that range |
19 |
19 |
will be included in the response. If no begin and end are specified, it will |
20 |
20 |
take the current week as begin and end point. If it's |
21 |
21 |
weekend, it will take next week.""" |
22 |
22 |
|
23 |
23 |
# TODO Handle given begin and end |
24 |
24 |
context = dict() |
25 |
25 |
context = {'money' : update_balance(None)} |
26 |
26 |
template = "administration/roster.djhtml" |
27 |
27 |
|
28 |
28 |
if begin is None or end is None: |
29 |
29 |
today = datetime.date.today() |
30 |
30 |
if today.isoweekday() in {6,7}: # Weekend |
31 |
31 |
begin = today + datetime.timedelta(days=8-today.isoweekday()) |
32 |
32 |
end = today + datetime.timedelta(days=13-today.isoweekday()) |
33 |
33 |
else: # Same week |
34 |
34 |
begin = today - datetime.timedelta(days=today.weekday()) |
35 |
35 |
end = today + datetime.timedelta(days=5-today.isoweekday()) |
36 |
36 |
else: # Changing regexes to date objects |
37 |
37 |
b = begin.split("-") |
38 |
38 |
e = end.split("-") |
39 |
39 |
begin = datetime.datetime(int(b[2]),int(b[1]),int(b[0])) |
40 |
40 |
end = datetime.datetime(int(e[2]),int(e[1]),int(e[0])) |
41 |
41 |
|
42 |
42 |
context['begin'] = begin |
43 |
43 |
context['end'] = end |
44 |
44 |
|
45 |
45 |
context['prev_begin'] = (begin - datetime.timedelta(days=7)).strftime("%d-%m-%Y") |
46 |
46 |
context['prev_end'] = (begin - datetime.timedelta(days=2)).strftime("%d-%m-%Y") |
47 |
47 |
context['next_begin'] = (end + datetime.timedelta(days=2)).strftime("%d-%m-%Y") |
48 |
48 |
context['next_end'] = (end + datetime.timedelta(days=7)).strftime("%d-%m-%Y") |
49 |
49 |
|
50 |
50 |
days = [begin] |
51 |
51 |
while (end-days[-1]).days != 0: |
52 |
52 |
# Human translation: Keep adding days until the last day in the array of |
53 |
53 |
# days is the same day as the last day the user wants to see the roster for. |
54 |
54 |
days.append(days[-1] + datetime.timedelta(days=1)) |
55 |
55 |
context['days'] = days |
56 |
56 |
|
57 |
57 |
# Collecting events |
58 |
58 |
course_events = CourseEvent.objects.filter(begin_time__gte=begin).filter(end_time__lte=end).order_by("begin_time") |
59 |
59 |
#university_events = UniversityEvent.objects.filter(begin_time__gte=begin).filter(end_time__lte=end) |
60 |
60 |
#study_events = StudyEvent.objects.filter(begin_time__gte=begin).filter(end_time__lte=end) |
61 |
61 |
#events = Event.objects.filter(begin_time__gte=begin).filter(end_time__lte=end) |
62 |
62 |
conflicts, table_code = create_roster_rows(course_events) |
63 |
63 |
|
64 |
64 |
context['time_blocks'] = table_code |
65 |
65 |
context['conflicts'] = conflicts |
66 |
66 |
#print(time_blocks) |
67 |
67 |
return render(request, template, context) |
68 |
68 |
# TODO Finish! |
69 |
69 |
|
70 |
70 |
def index(request): |
71 |
71 |
template = "administration/index.djhtml" |
72 |
72 |
context = {'money': update_balance(None)} |
73 |
73 |
return render(request, template, context) |
74 |
74 |
|
75 |
75 |
pass |
76 |
76 |
|
77 |
77 |
def pre_registration(request): |
78 |
78 |
user_data_form = UserDataForm() |
79 |
79 |
template = "administration/pre_registration.djhtml" |
80 |
80 |
context = dict() |
81 |
81 |
|
82 |
82 |
if request.method == 'POST': |
83 |
83 |
user_data_form = UserDataForm(request.POST) |
84 |
84 |
context['user_data_form'] = user_data_form |
85 |
85 |
if user_data_form.is_valid(): |
86 |
86 |
user_data_form.save() |
87 |
87 |
context['messsage'] = _("Your registration has been completed. You will receive an e-mail shortly.") |
88 |
88 |
else: |
89 |
89 |
context['messsage'] = _("The data you supplied had errors. Please review your submission.") |
90 |
90 |
else: |
91 |
91 |
context['user_data_form'] = UserDataForm(instance = user_data_form) |
92 |
92 |
|
93 |
93 |
return render(request, template, context) |
94 |
94 |
pass |
95 |
95 |
|
96 |
96 |
@login_required |
97 |
97 |
def settings(request): |
98 |
98 |
user_data = UserData.objects.get(user=request.user) |
99 |
99 |
user_data_form = UserDataForm(instance = user_data) |
100 |
100 |
template = "administration/settings.djhtml" |
101 |
101 |
context = {'money' : update_balance(None)} |
102 |
102 |
|
103 |
103 |
if request.method == 'POST': |
104 |
104 |
user_data_form = UserDataForm(request.POST, instance = user_data) |
105 |
105 |
context['user_data_form'] = user_data_form |
106 |
106 |
if user_data_form.is_valid(): |
107 |
107 |
user_data_form.save() |
108 |
108 |
context['messsage'] = _("Your settings were successfully updated.") |
109 |
109 |
else: |
110 |
110 |
context['messsage'] = _("The data you supplied had errors. Please review your submission.") |
111 |
111 |
else: |
112 |
112 |
context['user_data_form'] = UserDataForm(instance = user_data) |
113 |
113 |
|
114 |
114 |
return render(request, template, context) |
115 |
115 |
|
116 |
116 |
@login_required |
117 |
117 |
def bulletin_board(request): |
118 |
118 |
context = dict() |
119 |
119 |
context = {'money' : update_balance(None)} |
120 |
120 |
context['exam_commission_decisions'] = ExamCommissionDecision.objects.filter(user=request.user) |
121 |
121 |
context['education_department_messages'] = EducationDepartmentMessages.objects.all() |
122 |
122 |
for item in context['education_department_messages']: |
123 |
123 |
print(item.text) |
124 |
124 |
template = "administration/bulletin_board.djhtml" |
125 |
125 |
return render(request, template, context) |
126 |
126 |
|
127 |
127 |
def jobs(request): |
128 |
128 |
context = dict() |
129 |
129 |
context = {'money' : update_balance(None)} |
130 |
130 |
template = "administration/jobs.djhtml" |
131 |
131 |
#@context['decisions'] = ExamCommissionDecision.objects.filter(user=request.user) |
132 |
132 |
return render(request, template, context) |
133 |
133 |
|
134 |
134 |
|
135 |
135 |
@login_required |
136 |
136 |
def curriculum(request): |
137 |
137 |
context = dict() |
138 |
138 |
context = {'money' : update_balance(None)} |
139 |
139 |
template = "administration/curriculum.djhtml" |
140 |
140 |
context['curricula'] = Curriculum.objects.filter(student=request.user) |
141 |
141 |
context['cource_results'] = CourseResult.objects.filter(student=request.user) |
142 |
142 |
return render(request, template, context) |
143 |
143 |
|
144 |
144 |
def result(request): |
145 |
145 |
return render(request, template, context) |
146 |
146 |
|
147 |
147 |
@login_required |
148 |
148 |
def results(request): |
149 |
149 |
results = CourseResult.objects.filter(student=request.user) |
150 |
150 |
template = "administration/results.djhtml" |
151 |
151 |
# TODO |
152 |
152 |
return render(request, template, context) |
153 |
153 |
|
154 |
154 |
def forms(request): |
155 |
155 |
context = dict() |
156 |
156 |
context = {'money' : update_balance(None)} |
157 |
157 |
template = "administration/forms.djhtml" |
158 |
158 |
return render(request, template, context) |
159 |
159 |
|
160 |
160 |
def user(request, slug_name): |
161 |
161 |
pass |
162 |
162 |
|
163 |
163 |
def rooms(request): |
164 |
164 |
context = dict() |
165 |
165 |
context = {'money' : update_balance(None)} |
166 |
166 |
context['rooms'] = Room.objects.all() |
167 |
167 |
context['room_reservations'] = RoomReservation.objects.all() |
168 |
168 |
context['course_events'] = CourseEvent.objects.all() |
169 |
169 |
context['blocks'] = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'] |
170 |
170 |
|
171 |
171 |
|
172 |
172 |
|
173 |
173 |
template = "administration/rooms.djhtml" |
174 |
174 |
return render(request, template, context) |
175 |
175 |
|
176 |
176 |
def room_detail(request, room): |
177 |
177 |
template = "administration/room_detail.djhtml" |
178 |
178 |
context = dict() |
179 |
179 |
context = {'money' : update_balance(None)} |
180 |
180 |
room = Room.objects.get(name=room) |
181 |
181 |
context['room'] = room |
182 |
182 |
context['reservations'] = RoomReservation.objects.filter(room=room).filter(begin_time__gte=datetime.datetime.now()) |
183 |
183 |
context['course_events'] = CourseEvent.objects.filter(room=room).filter(begin_time__gte=datetime.datetime.now()) |
184 |
184 |
return render(request, template, context) |
+ |
185 |
today = datetime.date.today() |
+ |
186 |
if today.isoweekday() in {6,7}: # Weekend |
+ |
187 |
today = today + datetime.timedelta(days=8-today.isoweekday()) |
+ |
188 |
|
+ |
189 |
context['days'] = [today] |
+ |
190 |
|
+ |
191 |
# Collecting events |
+ |
192 |
course_events = CourseEvent.objects.filter(room=room).filter(begin_time__date=today) |
+ |
193 |
print(course_events) |
+ |
194 |
#university_events = UniversityEvent.objects.filter(begin_time__gte=begin).filter(end_time__lte=end) |
+ |
195 |
#study_events = StudyEvent.objects.filter(begin_time__gte=begin).filter(end_time__lte=end) |
+ |
196 |
#events = Event.objects.filter(begin_time__gte=begin).filter(end_time__lte=end) |
+ |
197 |
|
+ |
198 |
conflicts, table_code = create_roster_rows(course_events) |
+ |
199 |
context['time_blocks'] = table_code |
+ |
200 |
context['conflicts'] = conflicts |
+ |
201 |
print(context['time_blocks']) |
+ |
202 |
return render(request, template, context) |
185 |
203 |
|
186 |
204 |
def login(request): |
187 |
205 |
context = dict() |
188 |
206 |
context = {'money' : update_balance(None)} |
189 |
207 |
if request.method == "POST": |
190 |
208 |
name = request.POST['name'] |
191 |
209 |
passphrase = request.POST['pass'] |
192 |
210 |
user = authenticate(username=name, password=passphrase) |
193 |
211 |
if user is not None: # The user was successfully authenticated |
194 |
212 |
print("YA") |
195 |
213 |
return HttpResponseRedirect(request.POST['next']) |
196 |
214 |
else: # User credentials were wrong |
197 |
215 |
context['next'] = request.POST['next'] |
198 |
216 |
context['message'] = _("The given credentials were not correct.") |
199 |
217 |
else: |
200 |
218 |
context['next'] = request.GET.get('next', None) |
201 |
219 |
if context['next'] is None: |
202 |
220 |
context['next'] = reverse('administration-index') |
203 |
221 |
|
204 |
222 |
template = 'administration/login.djhtml' |
205 |
223 |
|
206 |
224 |
return render(request, template, context) |
207 |
225 |
joeni/templates/joeni/base.djhtml ¶
1 addition and 1 deletion.
View changes Hide changes
1 |
1 |
{% load i18n %} |
2 |
2 |
{% get_current_language as LANGUAGE_CODE %} |
3 |
3 |
{% get_language_info for LANGUAGE_CODE as lang %} |
4 |
4 |
{% load static %} |
5 |
5 |
{% get_media_prefix as media %} |
6 |
6 |
|
7 |
7 |
<!DOCTYPE html> |
8 |
8 |
<html lang="{{ lang.code }}"> |
9 |
9 |
<head> |
10 |
10 |
<title> |
11 |
11 |
{% block title %} |
12 |
12 |
{#◀ Joeni /▶ | ▶▶ UHasselt#} |
13 |
13 |
▶▶ UHasselt |
14 |
14 |
{% endblock title %} |
15 |
15 |
</title> |
16 |
16 |
|
17 |
17 |
{% block stylesheets %} |
18 |
18 |
<link href="{% static "css/header.css" %}" rel="stylesheet" media="screen, projection" /> |
19 |
19 |
<link href="{% static "css/footer.css" %}" rel="stylesheet" media="screen, projection" /> |
20 |
20 |
<link href="{% static "css/base.css" %}" rel="stylesheet" media="screen, projection" /> |
21 |
21 |
<style type="text/css"> |
22 |
22 |
header, footer { |
23 |
23 |
background-color: #{{ user.account.settings.color|default:"UHASSELT" }}; |
24 |
24 |
} |
25 |
25 |
</style> |
26 |
26 |
{% endblock stylesheets %} |
27 |
27 |
|
28 |
28 |
{% block metaflags %} |
29 |
29 |
{# This is standard for all web pages and doesn't require changing. #} |
30 |
30 |
{# UTF-8, always #} |
31 |
31 |
<meta charset="utf-8" /> |
32 |
32 |
{#<meta name="author" content="Maarten Vangeneugden">#} |
33 |
33 |
{# Indicates this page is suited for mobile devices #} |
34 |
34 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
35 |
35 |
<meta |
36 |
36 |
name="description" |
37 |
37 |
content="{% block description %} |
38 |
38 |
{% trans "The digital platform of Hasselt University" %} |
39 |
39 |
{% endblock description %}" /> |
40 |
40 |
{% endblock metaflags %} |
41 |
41 |
</head> |
42 |
42 |
|
43 |
43 |
<body> |
44 |
44 |
<header> |
45 |
45 |
{% block header %} |
46 |
46 |
{% include "joeni/header.djhtml" %} |
47 |
47 |
{% endblock header %} |
48 |
48 |
</header> |
49 |
49 |
|
50 |
50 |
<main> |
51 |
51 |
{% block main %} |
52 |
52 |
{% endblock main %} |
53 |
53 |
</main> |
54 |
54 |
|
55 |
55 |
<footer> |
56 |
56 |
{% block footer %} |
57 |
57 |
{% include "joeni/footer.djhtml" %} |
58 |
58 |
{% endblock footer %} |
59 |
59 |
</footer> |
60 |
60 |
|
61 |
61 |
</body> |
62 |
62 |
{% block JavaScript %} |
63 |
63 |
{% comment JavaScript %} |
64 |
- | My website does not require JavaScript for basic actions. However, it may be |
+ |
64 |
My website does not require JavaScript for basic actions. However, it may be |
65 |
65 |
used to make certain things look better, or to spice up some actions for the |
66 |
66 |
user. Should it be necessary, add the appropriate JavaScript code in this |
67 |
67 |
block. |
68 |
68 |
The reason this block is at the bottom? |
69 |
69 |
Remember to always put JavaScript on the bottom to reduce load time. |
70 |
70 |
Otherwise it just errors like a fucktard, which is really the only thing |
71 |
71 |
you can always expect from JavaScript. |
72 |
72 |
{% endcomment %} |
73 |
73 |
{% endblock JavaScript %} |
74 |
74 |
</html> |
75 |
75 |
static/css/base.css ¶
6 additions and 1 deletion.
View changes Hide changes
1 |
1 |
font-family: Verdana, Futura, Arial, sans-serif; |
2 |
2 |
} |
3 |
3 |
|
4 |
4 |
a.btn { |
5 |
5 |
border-style: solid; |
6 |
6 |
text-transform: uppercase; |
7 |
7 |
border-size: 3em; |
8 |
8 |
border-color: blue; |
9 |
9 |
padding: 0.5em; |
10 |
10 |
text-decoration: none; |
11 |
11 |
color: blue; |
12 |
12 |
font-weight: bold; |
13 |
13 |
} |
14 |
14 |
a.btn:hover { |
15 |
15 |
background-color: blue; |
16 |
16 |
color: white; |
17 |
17 |
} |
18 |
18 |
|
19 |
19 |
dl dt { |
20 |
20 |
margin: 5px; |
21 |
21 |
} |
22 |
22 |
|
23 |
23 |
.event { |
24 |
24 |
padding: 5px; |
25 |
25 |
} |
26 |
26 |
|
27 |
27 |
.event-update { |
28 |
28 |
padding: 5px; |
29 |
29 |
background-color: yellow; |
30 |
30 |
color: red; |
31 |
31 |
border: medium dotted red; |
32 |
32 |
} |
33 |
33 |
.event-new { |
34 |
34 |
padding: 5px; |
35 |
35 |
background-color: white; |
36 |
36 |
color: black; |
37 |
37 |
border: medium dashed black; |
38 |
38 |
} |
39 |
39 |
.event-note { |
40 |
40 |
padding: 5px; |
41 |
41 |
color: purple; |
42 |
42 |
border: medium double purple; |
43 |
43 |
} |
44 |
44 |
.event-conflict { |
45 |
45 |
padding: 5px; |
46 |
46 |
background-color: red; |
47 |
47 |
color: white; |
48 |
48 |
border: medium solid maroon; |
49 |
- | } |
+ |
49 |
border-color: inherit; |
+ |
50 |
} |
+ |
51 |
.event a { |
+ |
52 |
text-decoration: none; |
+ |
53 |
color: inherit; |
+ |
54 |
} |
50 |
55 |