Course List Manager

Pick a list of classes from above or paste your own URL from the CSULB website

Course Search List

Wishlist

Schedule Generator

Select preferences:

0
0
import asyncio import micropip from pyodide.http import pyfetch from datetime import datetime from js import prefferedDays # Import prefferedDays from JS async def setup(): await micropip.install("beautifulsoup4") from bs4 import BeautifulSoup global courses, original_courses, wishlist, department courses = [] original_courses = [] wishlist = [] department = "" class Course: def __init__(self): self.code = "" self.num = "" self.name = "" self.sec = "" self.units = "" self.type = "" self.days = [] self.time = "" self.open = True self.startTime = "" self.endTime = "" self.loc = "" self.inst = "" def setSec(self, sec): self.sec = sec def setCode(self, code): self.code = code def setName(self, name): self.name = name def setUnits(self, units): self.units = units def setNum(self, num): self.num = num def setType(self, type): self.type = type def setDays(self, days): self.days = days def setTime(self, time): self.time = time self.setMillTimes(time) def setMillTimes(self, time): if time == "NA": self.startTime = time self.endTime = time else: for i, char in enumerate(time): if time[i+1] == '-': begin = time[:i+1] end = time[i+2:] hr1, hr2 = '', '' for char in begin: if char == ':': break hr1 += char for char in end: if char == ':': break hr2 += char hr1, hr2 = int(hr1), int(hr2) if hr2 == 12: begin += "AM" elif time[-2:] == "AM": begin += "AM" else: begin += "PM" break def milTime(time_str): try: return datetime.strptime(time_str, "%I:%M%p").strftime("%H:%M") except ValueError: pass try: return datetime.strptime(time_str, "%I%p").strftime("%H:%M") except ValueError: pass raise ValueError(f"Time format error: '{time_str}' does not match any recognized format.") self.startTime = milTime(begin) self.endTime = milTime(end) def closed(self): self.open = False def isOpen(self): return self.open def setLoc(self, loc): self.loc = loc def setInst(self, inst): self.inst = inst def getCodeNum(self): s = self.code digits = "" for char in s: if char.isdigit(): digits = s[s.index(char):] break if digits[-1:] == "H": digits = digits[:-1] return digits def getNum(self): return self.num def getStartTime(self): return self.startTime def getEndTime(self): return self.endTime def getDays(self): return self.days def __repr__(self): status = "OPEN" if self.open else "CLOSED" return (f"{self.code} -- '{self.name}', Class# {self.num}, section {self.sec}, {self.units}, {self.type}, " f"{''.join(self.days)}, {self.time}, {status}, {self.loc}, {self.inst}") def daysExtractor(s): if s in ["NA", "TBA"]: return [s] days = [] for day in ["M", "Tu", "W", "Th", "F", "Sa", "Su"]: if day in s: days.append(day) return days def printList(lst, element_id): course_list_element = Element(element_id) course_list_html = "
".join(f"{i+1}. {str(course)}" for i, course in enumerate(lst)) course_list_element.element.innerHTML = course_list_html async def scrapeSite(url): response = await pyfetch(url) text = await response.string() soup = BeautifulSoup(text, "html.parser") allClasses = soup.find_all(class_="courseBlock") courses = [] for course_html in allClasses: header = course_html.find(class_="courseHeader") table = course_html.find(class_="sectionTable") if header: code = header.find("span", class_="courseCode").text.strip() title = header.find("span", "courseTitle").text.strip() units = header.find("span", "units").text.strip() if table: rows = table.find_all("tr") for row in rows[1:]: temp = Course() # Create a new Course object for each row data = row.find_all("td") temp.setSec(row.find_all("th")[0].text) temp.setCode(code) temp.setName(title) temp.setUnits(units) temp.setNum(data[0].text) temp.setType(data[4].text) temp.setDays(daysExtractor(data[5].text)) temp.setTime(data[6].text) temp.setMillTimes(data[6].text) temp.setLoc(data[8].text) temp.setInst(data[9].text) if row.find(title="Seats available") is None: temp.closed() courses.append(temp) return courses async def fetch_and_display_courses(url, event=None): global courses, original_courses, department courses = await scrapeSite(url) if not courses: Element('message').element.innerHTML = "No courses articulated, please try a different link" return original_courses = courses.copy() course = courses[0] courseCode = course.code.split()[0] printList(courses, 'course-list') department = courseCode Element('message').element.innerHTML = f"Displaying courses for {courseCode}" def openSite(event=None): index = "https://web.csulb.edu/depts/enrollment/registration/class_schedule/Fall_2024/By_College/index.html" webbrowser.open(index) def show_wishlist(event=None): printList(wishlist, 'wish-list') def tester(event=None): for day in prefferedDays: Element('message').element.innerHTML += f"{day}" def filter_by_code(event=None): global courses, department code = Element('course-code-input').element.value.strip() filtered_courses = [course for course in courses if code.lower() in course.getCodeNum().lower()] courses = filtered_courses printList(filtered_courses, 'course-list') if not courses: Element('message').element.innerHTML = "No courses articulated, please try a different code" return if code: Element('message').element.innerHTML = f"Showing all {department} {code} classes" def show_open_courses(event=None): global courses open_courses = [course for course in courses if course.isOpen()] printList(open_courses, 'course-list') if open_courses: Element('message').element.innerHTML = "Showing only open courses" else: Element('message').element.innerHTML = "No open courses available" courses = open_courses def show_all_courses(event=None): printList(courses, 'course-list') def revert_to_original(event=None): global courses, original_courses courses = original_courses.copy() printList(courses, 'course-list') def add_course(event=None): global wishlist pick = Element('course-num-input').element.value.strip() if not pick: Element('message').element.innerHTML = "No course number entered" return Element('course-num-input').element.value = "" if any(course.getNum() == pick for course in wishlist): Element('message').element.innerHTML = "Course is already in the schedule." return for course in courses: if course.getNum() == pick: wishlist.append(course) Element('message').element.innerHTML = f"Course #{course.getNum()} added to the schedule." printList(wishlist, 'wish-list') return Element('message').element.innerHTML = "Course not found." def add_All(event=None): global wishlist global courses flag = False Element('message').element.innerHTML = "Adding" pick = Element('course-code-input-wish').element.value.strip() if not pick: Element('message').element.innerHTML = "No course code entered" return Element('course-code-input-wish').element.value = "" for course in courses: if course.getCodeNum() == pick and not any(course.getNum() == member.getNum() for member in wishlist): wishlist.append(course) Element('message').element.innerHTML += f"
Class #{course.getNum()} added to the schedule." flag = True if flag: printList(wishlist,'wish-list') else: Element('message').element.innerHTML = f"All {pick} classes already in wish list" def generate_schedules(event=None): from js import prefferedDays # Import prefferedDays from JS global wishlist original = wishlist schedulesFinal = [] count = 1 classCount = 0 if not wishlist: Element('message').element.innerHTML = "Wishlist empty, add classes to generate a schedule" return Element('message').element.innerHTML = "Creating Schedules...
" def backtrack(currentSchedule, index): nonlocal count nonlocal classCount if index == len(original): if currentSchedule not in schedulesFinal and len(currentSchedule): schedulesFinal.append(currentSchedule.copy()) schedule_html = f"
----- Schedule {count} -----

" + "
".join(str(course) for course in currentSchedule)+"
" Element('message').element.innerHTML += schedule_html count += 1 classCount += 1 return course = original[index] can_add = True # Check if all course days are in preferred days for day in course.getDays(): if day not in prefferedDays: #Element('message').element.innerHTML += f"Course {course.getCodeNum()} not added: {day} is not in preferred days
" can_add = False break # If course days are in preferred days, check for time conflicts and duplicates if can_add: for classInSchedule in currentSchedule: # Check for the same course already added or time conflict if classInSchedule.getCodeNum() == course.getCodeNum(): Element('message').element.innerHTML += f"Course {course.getCodeNum()} not added: duplicate course
" can_add = False break if any(day in classInSchedule.getDays() for day in course.getDays()) and \ classInSchedule.getStartTime() < course.getEndTime() and \ classInSchedule.getEndTime() > course.getStartTime(): Element('message').element.innerHTML += f"Course {course.getCodeNum()} not added: time conflict with {classInSchedule.getCodeNum()}
" can_add = False break # If the course can be added, add it to the current schedule and proceed if can_add: currentSchedule.append(course) backtrack(currentSchedule, index + 1) currentSchedule.pop() # Continue generating schedules with the next course backtrack(currentSchedule, index + 1) backtrack([], 0) # Event Listeners Element("load-fall24").element.onclick = lambda event: fetch_and_display_courses("https://web.csulb.edu/depts/enrollment/registration/class_schedule/Fall_2024/By_College/CECS.html", event) Element("load-spring24").element.onclick = lambda event: fetch_and_display_courses("https://web.csulb.edu/depts/enrollment/registration/class_schedule/Spring_2024/By_Subject/CECS.html", event) Element("load-courses").element.onclick = lambda event: fetch_and_display_courses(Element("url-input").element.value, event) Element("filter-code").element.onclick = filter_by_code Element("add-course").element.onclick = add_course Element("add-course-code").element.onclick = add_All Element("show-open").element.onclick = show_open_courses Element("show-wishlist").element.onclick = show_wishlist Element("show-all").element.onclick = show_all_courses Element("revert").element.onclick = revert_to_original Element("generate-schedules").element.onclick = generate_schedules Element("index").element.onclick = openSite Element("test").element.onclick = tester asyncio.ensure_future(setup())