123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133 |
- """
- Schoolloop backend
- This file is part of polyglot, etc.
- """
- import datetime
- import requests
- import re
- import hashlib
- from lxml.html import parse
- from io import StringIO
- from backends.support import Backend, Course, AssignmentGrade, CategoryGrade, AuthTypes, Task
- class SchoolloopCourse(Course):
- def __init__(self, backend, row):
- self.row = row
- self.backend = backend
- self.period = row.cssselect(".period")[0].text_content().strip()
- self.title = row.cssselect(".course")[0].text_content().strip()
- self.teacher = row.cssselect(".teacher")[0].text_content().strip()
- if len(row.cssselect(".percent")):
- self.grade_summary = row.cssselect(".percent")[0].text_content().strip()
- self.id = hashlib.md5((self.period + self.title + self.teacher).encode('utf-8')).hexdigest()
- @property
- def grades(self):
- href = self.row.cssselect(".pr_link > a")[0].attrib["href"]
- report = self.backend.session.get(self.backend.base_url + href)
- score_extractor = re.compile("([0-9\.]+) / ([0-9\.]+)")
- pr_root = parse(StringIO(report.text)).getroot()
- categories = {}
- for row in pr_root.cssselect('.module:contains("Scores per Category")')[0].cssselect("tr")[1:]:
- r = row.getchildren()
- category = r[0].text_content().strip()
- weight = r[1].text_content().strip()
- categories[category] = CategoryGrade(category, weight, [])
- for row in pr_root.cssselect(".general_body")[0].getchildren():
- r = row.getchildren()
- category = r[0].getchildren()[0].text.strip()
- assignment = r[0].cssselect("a")[0].text_content().strip()
- due = r[2].text_content().strip()
- (score_numerator, score_denominator) = score_extractor.search(r[3].text_content().strip()).groups()
- comment = r[4].text_content().strip()
- if category in categories:
- categories[category].grades += [AssignmentGrade(assignment, float(score_numerator) / float(score_denominator))]
- return list(categories.values())
- @property
- def tasks(self):
- try:
- return self.backend.tasks_by_course[self.title]
- except KeyError:
- return []
- class SchoolloopBackend(Backend):
- Config = ["Username", "Password", "School"]
- def login(self, config):
- # First, fetch the login portal itself
- self.base_url = "https://" + config["School"] + ".schoolloop.com"
- self.session = requests.Session()
- form = self.session.get(self.base_url + "/portal/login")
- # Extract data id
- data_extractor = re.compile('<input type="hidden" name="form_data_id" id="form_data_id" value="(\\d+)">')
- data_id = data_extractor.search(form.text).group(1)
- credentials = {
- "login_name": config["Username"],
- "password": config["Password"],
- "redirect": "",
- "forward": "",
- "login_form_reverse": "",
- "form_data_id": str(data_id),
- "sort": "",
- "reverse": "",
- "login_form_sort": "",
- "event_override": "login",
- "login_form_filter": "",
- "login_form_letter": "",
- "return_url": "",
- "login_form_page_index": "",
- "login_form_page_item_count": ""
- }
- portal = self.session.post(self.base_url + "/portal/login?etarget=login_form", data=credentials)
- # Save session
- self.portal = portal.text
- self.root_element = parse(StringIO(self.portal)).getroot()
- return True
- @property
- def courses(self):
- self.fetch_tasks()
- rows = self.root_element.cssselect(".student_row")
- ret = []
- for row in rows:
- ret += [SchoolloopCourse(self, row)]
- return ret
- def fetch_tasks(self):
- task_list = self.root_element.cssselect('[data-track-container="Active Assignments"]')[0].cssselect('tr')
- self.tasks_by_course = {}
- for rows in task_list:
- r = rows.getchildren()
- title = r[3].text_content().strip()
- course = r[4].text_content().strip()
- due = r[5].text_content().replace("Due:", "").strip() # Help! Internationalisation >_<
- if course not in self.tasks_by_course:
- self.tasks_by_course[course] = []
- self.tasks_by_course[course] += [Task(title, due)]
|