diff --git a/server/models.py b/server/models.py index dd1c514..f4580b8 100644 --- a/server/models.py +++ b/server/models.py @@ -11,6 +11,7 @@ from sqlalchemy.ext.associationproxy import association_proxy from server import app +from server.utils.date import parse_ISO8601 db = SQLAlchemy(app=app) @@ -43,11 +44,16 @@ class Offering(db.Model): canvas_id = db.Column(db.String(255), nullable=False, index=True, unique=True) name = db.Column(db.String(255), nullable=False) code = db.Column(db.String(255), nullable=False) + start_at = db.Column(db.String(255), nullable=False) exams = db.relationship('Exam', uselist=True, cascade='all, delete-orphan', order_by='Exam.display_name', backref=backref('offering', uselist=False, single_parent=True)) + @property + def start_at_date(self): + return parse_ISO8601(self.start_at) + def __repr__(self): return ''.format(self.name) diff --git a/server/services/canvas/__init__.py b/server/services/canvas/__init__.py index 65d5492..1756184 100644 --- a/server/services/canvas/__init__.py +++ b/server/services/canvas/__init__.py @@ -2,7 +2,6 @@ from canvasapi import Canvas from canvasapi.user import User from canvasapi.course import Course -from canvasapi.enrollment import Enrollment from server import app from server.models import Offering @@ -38,7 +37,9 @@ def is_course_valid(c) -> bool: return not (not c) and \ hasattr(c, 'id') and \ hasattr(c, 'name') and \ - hasattr(c, 'course_code') + hasattr(c, 'course_code') and \ + hasattr(c, 'start_at') and \ + hasattr(c, 'start_at_date') def get_user_courses_categorized(user: FakeUser | User) \ @@ -55,13 +56,27 @@ def get_user_courses_categorized(user: FakeUser | User) \ student_courses.add(c) else: other.add(c) + # a course should not appear in more than one category student_courses -= set(staff_courses) other = other - set(staff_courses) - set(student_courses) - # sorted by course name - staff_courses: list[FakeCourse | Course] = sorted(staff_courses, key=lambda c: c.name) - student_courses: list[FakeCourse | Course] = sorted(student_courses, key=lambda c: c.name) - other: list[FakeCourse | Course] = sorted(other, key=lambda c: c.name) + + # convert to list because order matters + staff_courses: list[FakeCourse | Course] = list(staff_courses) + student_courses: list[FakeCourse | Course] = list(student_courses) + other: list[FakeCourse | Course] = list(other) + + # sorted by start_at_date DESC and then by name ASC + def _sort_courses(courses: list[FakeCourse | Course]): + # Cannot do courses.sort(key=lambda c: (c.start_at_date, c.name)) + # String or Datetime object cannot be negated to reverse the order + courses.sort(key=lambda c: c.name) + courses.sort(key=lambda c: c.start_at_date, reverse=True) + + _sort_courses(staff_courses) + _sort_courses(student_courses) + _sort_courses(other) + return list(staff_courses), list(student_courses), list(other) @@ -69,5 +84,6 @@ def api_course_to_model(course: Course | FakeCourse) -> Offering: return Offering( canvas_id=course.id, name=course.name, - code=course.course_code + code=course.course_code, + start_at=course.start_at, ) diff --git a/server/services/canvas/fake_canvas.py b/server/services/canvas/fake_canvas.py index 0223bf5..b5e5d0e 100644 --- a/server/services/canvas/fake_canvas.py +++ b/server/services/canvas/fake_canvas.py @@ -1,4 +1,6 @@ from __future__ import annotations +from datetime import datetime + from server.services.canvas.fake_data import FAKE_USERS, FAKE_COURSES, FAKE_ENROLLMENTS @@ -37,6 +39,11 @@ def __init__(self, canvas_id, enrollments=[]): self.course_code = FAKE_COURSES[str(canvas_id)]['course_code'] self.sis_course_id = FAKE_COURSES[str(canvas_id)]['sis_course_id'] self.enrollments = enrollments + # canvasapi.course.Course.start_at is a n ISO8601 date string + # canvasapi.course.Course.start_at_date is a datetime.datetime object + # so, we do the same here + self.start_at = FAKE_COURSES[str(canvas_id)]['start_at'] + self.start_at_date = datetime.strptime(self.start_at, '%Y-%m-%dT%H:%M:%SZ') def get_users(self, *, enrollment_type) -> list[FakeUser]: users = [] diff --git a/server/services/canvas/fake_data/fake_courses.json b/server/services/canvas/fake_data/fake_courses.json index 37199ea..873a041 100644 --- a/server/services/canvas/fake_data/fake_courses.json +++ b/server/services/canvas/fake_data/fake_courses.json @@ -3,24 +3,28 @@ "id": 1234567, "name": "Introduction to Software Engineering (Fall 2023)", "sis_course_id": "CRS:COMPSCI-169A-2023-D", - "course_code": "COMPSCI 169A" + "course_code": "COMPSCI 169A", + "start_at": "2023-08-16T07:00:00Z" }, "2345678": { "id": 2345678, "name": "Introduction to the Internet: Architecture and Protocols (Fall 2022)", "sis_course_id": "", - "course_code": "COMPSCI 168" + "course_code": "COMPSCI 168", + "start_at": "2022-08-16T07:00:00Z" }, "3456789": { "id": 3456789, "name": "Introduction to Computer Security (Fall 2022)", "sis_course_id": "", - "course_code": "COMPSCI 161" + "course_code": "COMPSCI 161", + "start_at": "2022-08-16T07:00:00Z" }, "4567890": { "id": 4567890, "name": "Computer Architecture (Fall 2022)", "sis_course_id": "", - "course_code": "COMPSCI 150" + "course_code": "COMPSCI 150", + "start_at": "2022-08-16T07:00:00Z" } } diff --git a/server/templates/select_offering.html.j2 b/server/templates/select_offering.html.j2 index 5bad4da..32bd01a 100644 --- a/server/templates/select_offering.html.j2 +++ b/server/templates/select_offering.html.j2 @@ -8,6 +8,7 @@