15.14 Project Info

We will agree on a class project that uses PySimpleGUI. Useful information for the project will appear in this space.

15.14 Project Info

After brainstorming project ideas, we settled on an Assignment Tracker app. Assignment data will be obtained from the myPARISH calendar in the form of a webdev: feed link. Assignments (events in calendar speak) info will be gleaned from the feed and used to allow the user to view their assignments. The user will be able to filter the data by date and course.

To facilitate working with webdev: data, an Assignments API is provided. Here is Assignments.py, which defines the API.


from icalevents.icalevents import events
from datetime import datetime, timedelta, date
from pytz import utc, timezone
import html


class Assignments:
    """
    Assignments are retrieved from the myPARISH calendar webcal feed. The assumption is made that the calendar is
    first limited to only assignments, without other kinds of events. A list is created containing `Assignment` objects.
    """
    def __init__(self, start, end, url):
        """
        Constructor for `Assignments` class.
        :param start: datetime object -> start date for assignments list
        :param end: datetime object -> end date for assignments list
        :param url: str -> webcal url to myPARISH calendar
        """
        self.URL = url
        self.start_date = self.normalize_date(start)
        self.end_date = self.normalize_date(end)
        self.assignments = self.get_assignments()

    def __str__(self):
        """
        String representation for `Assignments` objects
        :return: str
        """
        output = ''

        for assignment in self.assignments:
            output += str(assignment) + '\n'

        return output

    def get_assignments(self):
        """
        Extract assignments from webcal feed and populate a list.
        :return: list -> list of `Assignment` objects [`Assignment`, `Assignment`, ...]
        """
        es = events(self.URL, start=self.start_date, end=self.end_date, fix_apple=True)
        es.sort()
        ret_list = []
        for event in es:
            start_time = self.normalize_date(event.start)
            ret_list.append(Assignment(start_time,
                                       html.unescape(self.get_course(event.summary)),
                                       html.unescape(self.get_name(event.summary))
                                       ))
        return ret_list

    @staticmethod
    def normalize_date(dt):
        """
        Convert `date` or `datetime` to `datetime` with timezone.
        :param dt: date to normalize
        :return: date as `datetime` with timezone
        """
        if type(dt) is date:
            dt = datetime(dt.year, dt.month, dt.day, 0, 0)
        elif type(dt) is datetime:
            dt = dt
        else:
            raise ValueError("unknown type %s" % type(dt))

        if not dt.tzinfo:
            dt = utc.localize(dt)

        return dt

    @staticmethod
    def get_course(summary):
        """
        Extract course name from `event.summary` property
        :param summary: `event.summary`
        :return: str -> name of course or `None`
        """
        if ':' in summary:
            course = summary[:summary.find(':')]
            return course.strip()
        return None

    @staticmethod
    def get_name(summary):
        """
        Extract assignment name from `event.summary` property
        :param summary: `event.summary`
        :return: str -> name of assignment or `None`
        """
        if ':' in summary:
            name = summary[summary.find(':') + 1:]
            return name.strip()
        return None


class Assignment:
    """
    Class for each individual assignment.
    """
    def __init__(self, d, c, n):
        """
        Constructor for `Assignment` class
        :param d: datetime -> due date of assignment
        :param c: str -> course name
        :param n: str -> assignment name
        """
        self.date = d
        self.course = c
        self.name = n

    def __str__(self):
        """
        String representation for `Assignment` objects
        :return: str
        """
        return f'{datetime.date(self.date)} | {self.course} | {self.name}'


def main():
    """
    This function executes only if this file is run directly. It is only present to show example usage.
    Code that utilizes `Assignments` should be located in other files, not this one.
    :return:
    """
    # Get URL from user. Possibly store in text file.
    url = 'webcal://parish.myschoolapp.com/podium/feed/iCal.aspx?z=PBfWPW%2b7Bf8znQAnWIawKSUMruwMfYfE8DOy5Pq3KcqL5EEpKZ3SaFZKWyirslzw8uqEHM2%2bpImt5ve%2b8ykwEg%3d%3d'

    # Also get these from user
    start_yr = 2023
    start_mth = 3
    start_day = 6

    end_yr = 2023
    end_mth = 3
    end_day = 15

    # create Assignments object
    my_assignments = Assignments(datetime(start_yr, start_mth, start_day), datetime(end_yr, end_mth, end_day), url)

    # print assignments in a loop
    # selection can be done on assignment properties
    print('\nAssignments for Coding for OOP...')
    for assignment in my_assignments.assignments:
        if 'Coding for OOP' in assignment.course:
            print(f'Date: {datetime.date(assignment.date)} | Course: {assignment.course} | Name: {assignment.name}')

    # print all assignments
    print('\nAll assignments...')
    print(my_assignments)

    print('Both printouts are limited to the date range given.')


if __name__ == '__main__':
    main()
        

The main() function at the end shows how one can work with the data.