You Can Find the Front-End Repo Here
Welcome to the back-end that powers Schedule Storm. The back-end is written entirely in Python using Falcon, Beautiful Soup, ldap3, requests, MongoDB, and pymongo.
As you might expect, Schedule Storm is reliant upon class data for numerous universities. Since many universities don't have APIs to query, the vast majority of scraping is done on HTML using Beautiful Soup and Requests. Here you'll be able to find documentation on how to add your University to Schedule Storm.
- Requests
- Beautiful Soup 4
- ldap 3
- pymongo
- falcon
- html5lib
- lxml
Rename settings.example.json
to settings.json
and set the enabled
and scrape
settings to true for every university you'd like to enable.
The default port is 3000, you can change this in the settings file
Simply execute (tested on Python 3.4+): python index.py
You can browse the API by going to http//localhost:{port}/v1/unis
or http://localhost:{port}/v1/unis/{uni}/{term}/all
If you'd like to use the front-end with your local API, clone it and change the URLs at the top of ClassList.js
and Welcome.js
Using the settings file, you can tell Schedule Storm your University's rmpid, api key, username, password, etc...
Every University Must Have An Entry in settings.json
{
"Universities": {
...
"<uniID>": {
"fullname": "<University Name>"
"enabled": true,
"scrape": true,
"scrapeinterval": 7200,
"rmpid": <rmpid>,
...
},
...
key | Type | Optional | Notes |
---|---|---|---|
uniID | string | No | ID/Abbreviation of the University (ex. UCalgary, MTRoyal) |
fullname | string | No | Full name of the university shown to the user (ex. University of Calgary) |
enabled | bool | No | Boolean as to whether this university is enabled or not |
scrape | bool | No | If true, starts the university thread to fetch updated course info |
scrapeinterval | int | No | Amount of seconds to sleep between subsequent scrapes |
rmpid | int | Yes | RMP ID of the University to fetch professor data from |
lastUpdated | int | Yes | (Auto-generated) UNIX timestamp of the last successful scrape in seconds |
Within the university's JSON block, you can have as many more attributes as you'd like. Here, you can specify usernames, passwords, api keys, and they'll all be passed to your University thread upon creation.
Simply go to Rate My Professors and search for your university in the search bar. Make sure you click on your university in the bottom "Schools" section.
Afterwards, you will be forwarded to a URL such as: http://www.ratemyprofessors.com/campusRatings.jsp?sid=1416
The sid parameter is the rmpid, so this rmpid is 1416.
All of the universities are located in the "uni" folder with their names being <uniID>.py
For example: University of Calgary has a uniID of "UCalgary" in the settings file, so its file is UCalgary.py.
Each university inherits the University class, which inherits the threading.Thread class.
Within your Uni file, you must import the University superclass at the top: from .University import University
. The University
superclass contains the API handlers and DB interaction methods.
Next, you'll want to create a class that inherits University
and is named your uniID along with instantiating the University superclass in your __init__
method.
ex. If your uniID is "UCalgary"
from .University import University
class UCalgary(University):
def __init__(self, settings):
super().__init__(settings)
def scrape(self):
"""
Scraping function that obtains updated course info
:return:
"""
self.log.info("Obtain course info here!")
Within the scrape method, you should fetch updated course data
Since each university inherits the University
superclass, all interaction with the MongoDB database is done through it. You can use the following methods to interface with the terms.
Term Objects are dictionaries with the specified keys
key | Type | Optional | Unique | Notes |
---|---|---|---|---|
id | string | No | Yes | ID of the Term (ex. "20235") |
name | string | No | No | Name of the term (ex. "Fall 2016") |
Within the DB, it also contains an enabled flag that specifies whether it is shown to users or not. The Term methods abstract this from you.
Arguments:
name | Type | Optional | Notes |
---|---|---|---|
terms | list | No | List of term objects to update in the DB |
NOTE: This sets the only enabled terms to be the specified terms in the list
Updates the terms specified into the DB. If a term doesn't exist in the DB yet, it is inserted.
Arguments:
name | Type | Optional | Notes |
---|---|---|---|
term | dict | No | Term object to update in the DB |
Updates the term specified into the DB and sets its enabled flag to True
(shows it to users). If the term doesn't exist in the DB yet, it is inserted.
Arguments:
name | Type | Optional | Notes |
---|---|---|---|
termid | string | No | ID of the term for fetch the term obj for |
Returns: Term object for the specified termid if succesful, False if not
Sets every term within the DB to have a False
enabled flag (isn't shown to users).
Subject Objects are dictionaries with the specified keys
key | Type | Optional | Unique | Notes |
---|---|---|---|---|
subject | string | No | Yes | Subject abbreviation (ex. CPSC) |
name | string | Yes | No | Subject name (ex. Computer Science) |
faculty | string | Yes | No | Faculty that this subject belongs to (ex. Faculty of Science) |
Arguments:
name | Type | Optional | Notes |
---|---|---|---|
subjects | list | No | List of subject objects to update in the DB |
Updates the subjects specified into the DB. If a subject doesn't exist in the DB yet, it is inserted.
Arguments:
name | Type | Optional | Notes |
---|---|---|---|
subject | dict | No | Subject object to update in the DB |
Updates the subject specified into the DB. If the subject doesn't exist in the DB yet, it is inserted.
Course Description Objects are dictionaries with the specified keys
key | Type | Optional | Unique | Notes |
---|---|---|---|---|
coursenum | string | No | No | Course number (ex. "300") |
subject | string | No | No | Subject abbreviation (ex. "CPSC") |
name | string | Yes | No | Name/title of the course (ex. "Introduction to Computer Science") |
desc | string | Yes | No | Description of the course (ex. "You'll learn about computers in this course") |
units | int | Yes | No | How many units this course is worth (ex. 3) |
hours | string | Yes | No | Distribution of hours between types of classes (ex. "H(3-3)") |
prereq | string | Yes | No | Human-readable course prerequisites (ex. "Must take CPSC 299 or CPSC 256") |
coreq | string | Yes | No | Human-readable course corequisites (ex. "Must take CPSC 301 with this course") |
antireq | string | Yes | No | Human-readable course antirequisites (ex. "Student must not have taken CPSC 302") |
notes | string | Yes | No | Any further human-readable notes for this class (ex. "You might learn too much!") |
aka | string | Yes | No | Equivalent/old names for this class (ex. "Formally CPSC 234") |
The coursenum and subject fields together form a unique constraint.
Arguments:
name | Type | Optional | Notes |
---|---|---|---|
coursedesc | dict | No | Course description object to update in the DB |
Updates the course description specified into the DB. If the course description doesn't exist in the DB yet, it is inserted.
Arguments:
name | Type | Optional | Notes |
---|---|---|---|
coursenum | string | No | Course number of the description to obtain (ex. "300") |
subject | string | No | Subject abbreviation of the description to obtain (ex. "CPSC") |
Returns: Course Description object of the specified coursenum and subject if successful, False if not
Class Objects are dictionaries with the specified keys
key | Type | Optional | Unique | Notes |
---|---|---|---|---|
id | int | No | Yes | ID of the class (ex. "34534") |
subject | string | No | No | Subject abbreviation (ex. "CPSC") |
term | string | No | No | Term ID that this class belongs to (ex. "2342") |
coursenum | string | No | No | Course number of the description to obtain (ex. "300") |
rooms | list | No | No | List of strings that contain the rooms that this class is situated in (ex. ["MFH 164", "HEH 101"]). If there are no rooms, set it to ["TBA"] or ["N/A"] |
teachers | list | No | No | List of strings that contain the teachers teaching this class (ex. ["Jack Shepard", "Hugo 'Hurley' Reyes"]). If there are no teachers, set it to ["TBA"] or ["N/A"] |
type | string | No | No | Type of the class (ex. "LEC") |
times | list | No | No | List of strings that contain the times in which this class is (ex. ["MWF 12:00PM - 2:00PM"]). If there are no times, set it to ["TBA"] or ["N/A"]. See below for time formatting. |
group | list | No | No | Group(s) of this class (ex. ["1", "2"]). See below for further details. |
status | string | No | No | If the class is open, set to "Open", otherwise, set the enrollment status to "Closed" or "Wait List" |
location | string | No | No | Location of the class (ex. "Main Campus") |
section | string | Yes | No | Shows this value instead of group to the user when applicable |
restriction | bool | Yes | No | True if this class has a restriction to some students |
curEnroll | int | Yes | No | Amount of students currently enrolled in the class |
capEnroll | int | Yes | No | Maximum amount of students that are able to be enrolled in the class |
waitEnroll | int | Yes | No | Amount of students waitlisted to take the course |
capwaitEnroll | int | Yes | No | Total amount of students that can waitlist the course |
Most universities have a system where you must take some specific classes together (and they are incompatible with others). In order to resolve this, the group
attribute defines the classes that can be taken together. Two classes that have at least one match in their group lists can be taken together.
The generator ensures that it only matches compatible classes with each other and if a user tries to manually specify two incompatible classes, it doesn't generate any possibilities.
If a specific course doesn't care whether two classes are compatible, just set each class' group
to ["1"]
.
Ex.
- CPSC 301 LEC
group: ["1", "2"]
- CPSC 301 LEC2
group: ["2", "3"]
- CPSC 301 TUT
group: ["2"]
- CPSC 301 TUT2
group: ["3"]
LEC and TUT are compatible, LEC2 is compatible with TUT and TUT2
Each time for a given class must be in the specified format:
-
Days of the Week
- Concatenated series of days in which this time is applicable
- Possible Days
- M/Mo - Monday
- T/Tu - Tuesday
- W/We - Wednesday
- R/Th - Thursday
- F/Fr - Friday
- S/Sa - Saturday
- U/Su - Sunday
- Examples
- "M"
- "MTR"
- "FWM"
- Order of the days does not matter
-
StartTime/EndTime
- 12-hour Start/End time
- Format:
<Hour>:<Minutes>
- Examples
- "12:00"
- "1:23"
-
Examples
- "TR 12:00PM - 1:20PM"
- "MWF 9:50AM - 10:30AM"
- "MoWeFr 9:00AM - 11:00AM"
- "MoTWe 2:00PM - 3:00PM"
Arguments:
name | Type | Optional | Notes |
---|---|---|---|
classes | list | No | List of class objects to update |
Updates the classes specified into the DB. If a class doesn't exist in the DB yet, it is inserted.
Arguments:
name | Type | Optional | Notes |
---|---|---|---|
class | dict | No | Class object to update |
Updates the class specified into the DB. If the class doesn't exist in the DB yet, it is inserted.
- Don't use print statements, the
University
superclass contains alogger
, you can use it withself.log
- Examples
self.log.error("You can't do this")
self.log.info("Scraping courses")
- Examples
- You can access your uni settings object with
self.settings
within the uni class - Look at
Example.py
for a starting point, make sure you edit the settings file though! - If you want to add more attributes to a class/subject/term/coursedesc object, go ahead! They won't be used by the front-end, but we can add support for it later on!
- If you have any questions/concerns, feel free to file an issue or talk to us!