joeni

roster.py

1
""" This is a module dedicated to collecting all functionality regarding the
2
building of the roster. """
3
from django.shortcuts import render
4
from collections import OrderedDict
5
import datetime
6
from django.urls import reverse # Why?
7
from django.utils.translation import gettext as _
8
from .models import *
9
import administration
10
11
def same_day(datetime_a, datetime_b):
12
    """True if both a and b are on the same day, false otherwise."""
13
    return (
14
        datetime_a.day == datetime_b.day and
15
        datetime_a.month == datetime_b.month and
16
        datetime_a.year == datetime_b.year)
17
18
19
def make_quarter_buckets(events):
20
    """Returns a dict with all days that the given events take place.
21
    Every quarter between the first and last event will get a bucket in the dict,
22
    with the keys in format "dd-mm-yyyy". Also quarters without events will be
23
    included, but will simply have empty buckets."""
24
    quarters = OrderedDict()  # This is the first time I ever use an OrderedDict and I intend it to be my last one as well.
25
    current_quarter = datetime.datetime.now().replace(hour=8, minute=0)
26
    while current_quarter.hour != 20 or current_quarter.minute != 00:
27
        quarters[current_quarter.strftime("%H:%M")] = list()
28
        current_quarter += datetime.timedelta(minutes=15)
29
    for event in events:
30
        quarters[event.begin_time.strftime("%H:%M")].append(event)
31
32
    return quarters  # Yay! ^.^
33
34
35
def create_roster_rows(events):
36
    """Creates the rows for use in the roster table.
37
    None of the times in the given events may overlap, and all must start and
38
    end at a quarter of the hour (so :00, :15, :30, or :45). If you think you're above this,
39
    I'll raise you a ValueError, kind sir.
40
    Events must be of administration.models.Event type."""
41
    if len(events) == 0:
42
        return []
43
44
    for event in events:
45
        for other_event in events:
46
            if other_event is not event and (
47
                    (event.begin_time >= other_event.begin_time and event.begin_time < other_event.end_time)
48
                    or (event.end_time > other_event.begin_time and event.end_time <= other_event.end_time)
49
                    ):
50
                raise ValueError("One of the events overlaps with another event.")
51
        if event.begin_time.minute not in [0, 15, 30, 45] or event.end_time.minute not in [0, 15, 30, 45]:
52
            raise ValueError("One of the events did not begin or end on a quarter.")
53
54
    # XXX: I assume here the given queryset is sorted:
55
    first_day = events[0].begin_time
56
    last_day = events.last().begin_time
57
    # All events validated
58
    quarter_buckets = make_quarter_buckets(events)
59
    skip_row = dict()
60
    table_code = list()
61
62
    for quarter_bucket in quarter_buckets.keys():
63
        # Preparing time column
64
        quarter_line = "<tr><td style='font-size: xx-small;'>"
65
        if quarter_bucket[3:] == "00":
66
            quarter_line += quarter_bucket
67
        quarter_line += "</td>"
68
69
        current_day = first_day
70
        while not same_day(current_day, last_day+datetime.timedelta(days=1)):
71
            for event in quarter_buckets[quarter_bucket]:
72
                if same_day(current_day, event.begin_time):
73
                    quarters = (event.end_time - event.begin_time).seconds // 60 // 15
74
                    skip_row[quarter_bucket] = quarters
75
                    event_line = "<td "
76
                    if isinstance(event, CourseEvent) and (datetime.datetime.now(datetime.timezone.utc) - event.last_update).days > 1:
77
                        event_line += "style='background-color: #"+event.course.course.color+"; color: white;' "
78
                    elif (datetime.datetime.now(datetime.timezone.utc) - event.created).days <= 5 and (event.last_update - event.created).days < 1:
79
                        event_line += "class='event-new' "
80
                    elif (datetime.datetime.now(datetime.timezone.utc) - event.last_update).days <= 5:
81
                        event_line += "class='event-update' "
82
                    if event.note:
83
                        event_line +="class='event-note' title='" +event.note+ "' "
84
85
                    # FIXME: From here, I just assume the events are all CourseEvents, because
86
                    # that is the most important one to implement for the prototype.
87
                    if quarters > 1:
88
                        event_line += "rowspan='"+str(quarters)+"' "
89
90
                    event_line +=">"
91
                    event_line += str(
92
                        #"<a href=" + reverse("courses-course-index", args="")+"> "  # FIXME so that this links to the course's index page
93
                        #"<a href=""> "
94
                        str(event.course.course.name)
95
                        #+ str(event.course.course.name)
96
                        + "<br />"
97
                        + str(event.docent)
98
                        + "<br />"
99
                        + event.begin_time.strftime("%H:%M") + " - " + event.end_time.strftime("%H:%M")
100
                        + "<br />"
101
                        #+ str(event.room) + " (" + str(event.subject) + ")</a></td>")
102
                        + str(event.room) + " (" + str(event.subject) + ")</td>")
103
                    quarter_line += event_line
104
                else:
105
                    """if quarter_bucket in skip_row:
106
                        skip_row[quarter_bucket] -= 1
107
                        if skip_row[quarter_bucket] == 0:
108
                            del skip_row[quarter_bucket]
109
                            quarter_line += "<td></td>"
110
                    else:
111
                        quarter_line += "<td></td>"""
112
                    pass
113
            current_day += datetime.timedelta(days=1)
114
        quarter_line += "</tr>"
115
        table_code.append(quarter_line)
116
    return table_code
117