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())