def setup_new_class(): """Setup a new class based on a zyBooks class code""" window = ui.get_window() zy_api = Zybooks() text_input = ui.layers.TextInputLayer("Class Code") text_input.set_prompt(["Enter class code"]) window.run_layer(text_input) if text_input.canceled: return # Check if class code is valid code = text_input.get_text() valid = zy_api.check_valid_class(code) if valid: popup = ui.layers.Popup("Valid", [f"{code} is valid"]) window.run_layer(popup) else: popup = ui.layers.Popup("Invalid", [f"{code} is invalid"]) window.run_layer(popup) return # If code is valid, add it to the shared configuration SharedData.add_class(code) # Download the list of students roster = zy_api.get_roster() save_roster(roster) popup = ui.layers.Popup("Finished", ["Successfully downloaded student roster"]) window.run_layer(popup) class_section_manager()
def get_submission(lab, student, use_locks=True): """Get a submission from zyBooks given the lab and student""" window = ui.get_window() zy_api = Zybooks() # Lock student if use_locks: data.lock.lock(student, lab) submission_response = zy_api.download_assignment(student, lab) submission = data.model.Submission(student, lab, submission_response) # Report missing files if submission.flag & data.model.SubmissionFlag.BAD_ZIP_URL: msg = [ f"One or more URLs for {student.full_name}'s code submission are bad.", "Some files could not be downloaded. Please", "View the most recent submission on zyBooks.", ] popup = ui.layers("Warning", msg) window.run_layer(popup) # A student may have submissions beyond the due date, and an exception # In case that happens, always allow a normal grade, but show a message if submission.flag == data.model.SubmissionFlag.NO_SUBMISSION: pass return submission
def download_roster(silent=False): """Download the roster of students from zybooks and save to disk""" window = ui.get_window() zy_api = Zybooks() roster = zy_api.get_roster() if not silent and not roster: popup = ui.layers.Popup("Failed", ["Failed to download student roster"]) window.run_layer(popup) return if roster: save_roster(roster) if not silent: popup = ui.layers.Popup("Finished", ["Successfully downloaded student roster"]) window.run_layer(popup)
def fetch_zybooks_toc(): window = ui.get_window() zy_api = Zybooks() popup = ui.layers.WaitPopup("Table of Contents", ["Fetching TOC from zyBooks"]) popup.set_wait_fn(zy_api.get_table_of_contents) window.run_layer(popup) return popup.get_result()
def submission_search_fn(logger, lab, search_string, output_path, use_regex): students = data.get_students() zy_api = Zybooks() regex_str = search_string if use_regex else re.escape(search_string) search_pattern = re.compile(regex_str) with open(output_path, "w", newline="") as log_file: csv_log = csv.DictWriter(log_file, fieldnames=[ "Name", "Submission", (f"(Searching for {search_string})" f"{' as a regex' if use_regex else ''}") ]) csv_log.writeheader() student_num = 1 for student in students: while True: counter = f"[{student_num}/{len(students)}]" logger.log(f"{counter:12} Checking {student.full_name}") match_result = check_student_submissions( zy_api, str(student.id), lab, search_pattern) if match_result["code"] == Zybooks.DOWNLOAD_TIMEOUT: logger.log( "Download timed out... trying again after a few seconds" ) time.sleep(5) else: break if match_result["code"] == Zybooks.NO_ERROR: csv_log.writerow({ "Name": student.full_name, "Submission": match_result['time'] }) logger.append(f" found {search_string}") # Check for and log errors if "error" in match_result: csv_log.writerow({ "Name": student.full_name, "Submission": f"ERROR: {match_result['error']}" }) student_num += 1
def add_lab(): """Add a lab to the current class""" window = ui.get_window() zy_api = Zybooks() text_input = ui.layers.TextInputLayer("Lab Name") text_input.set_prompt(["Enter the Lab Name"]) window.run_layer(text_input) if text_input.canceled: return lab_name = text_input.get_text() # Get lab part(s) parts = [] section_selector = ZybookSectionSelector(allow_optional_and_hidden=True) section_numbers = section_selector.select_zybook_sections( return_just_numbers=True) for chapter, section in section_numbers: part = {} response = zy_api.get_zybook_section(chapter, section) if not response.success: popup = ui.layers.Popup("Error", ["Invalid URL"]) window.run_layer(popup) part["name"] = response.name part["id"] = response.id parts.append(part) new_lab = data.model.Lab(lab_name, parts, {}) edit_lab_options(new_lab) all_labs = data.get_labs() all_labs.append(new_lab) data.write_labs(all_labs)
def pick_submission(submission_popup: ui.layers.OptionsPopup, lab: data.model.Lab, student: data.model.Student, submission: data.model.Submission): """Allow the user to pick a submission to view""" window = ui.get_window() zy_api = Zybooks() # If the lab has multiple parts, prompt to pick a part part_index = 0 if len(lab.parts) > 1: part_index = submission.pick_part(pick_all=True) if part_index is None: return if part_index == -1: def wait_fn(): for i, part in enumerate(lab.parts): part_submissions = zy_api.get_submissions_list( part["id"], student.id) if len(part_submissions) > 0: part_response = zy_api.download_assignment_part( lab, student.id, part, len(part_submissions) - 1) submission.update_part(part_response, lab.parts.index(part)) set_submission_message(submission_popup, submission) popup = ui.layers.WaitPopup("Downloading") popup.set_message([f"Downloading latest submissions..."]) popup.set_wait_fn(wait_fn) window.run_layer(popup) return # Get list of all submissions for that part part = lab.parts[part_index] all_submissions = zy_api.get_submissions_list(part["id"], student.id) if not all_submissions: popup = ui.layers.Popup("No Submissions", ["The student did not submit this part"]) window.run_layer(popup) return # Reverse to display most recent submission first all_submissions.reverse() popup = ui.layers.ListLayer("Select Submission", popup=True) popup.set_exit_text("Cancel") for sub in all_submissions: popup.add_row_text(sub) window.run_layer(popup) if popup.canceled: return submission_index = popup.selected_index() # Modify submission index to un-reverse the index submission_index = abs(submission_index - (len(all_submissions) - 1)) # Fetch that submission part_response = zy_api.download_assignment_part(lab, student.id, part, submission_index) submission.update_part(part_response, lab.parts.index(part)) set_submission_message(submission_popup, submission)
def __init__(self, allow_optional_and_hidden=False): self.window = ui.get_window() self.zy_api = Zybooks() self.allow_optional_and_hidden = allow_optional_and_hidden
class ZybookSectionSelector: def __init__(self, allow_optional_and_hidden=False): self.window = ui.get_window() self.zy_api = Zybooks() self.allow_optional_and_hidden = allow_optional_and_hidden def is_allowed(self, section): return self.allow_optional_and_hidden or (not (section["hidden"] or section["optional"])) class _SectionToggle(ui.layers.Toggle): def __init__(self, index, data): super().__init__() self.__index = index self.__data = data self.get() def get(self): self._toggled = self.__data[self.__index] def toggle(self): self.__data[self.__index] = not self.__data[self.__index] self.get() def select_zybook_sections(self, return_just_numbers=False, title_extra=""): self.zybooks_toc = self.zy_api.get_table_of_contents() if not self.zybooks_toc: return None self.zybooks_sections = {(chapter["number"], section["number"]): section for chapter in self.zybooks_toc for section in chapter["sections"]} selected_sections = {(chapter["number"], section["number"]): False for chapter in self.zybooks_toc for section in chapter["sections"]} title = ("Select zyBooks Sections" if not title_extra else f"{title_extra} - Select Sections") chapter_pad_width = len(str(len(self.zybooks_toc))) section_pad_width = max([ len(str(len(chapter["sections"]))) for chapter in self.zybooks_toc ]) popup = ui.layers.ListLayer(title, popup=True) popup.set_exit_text("Done") for i, chapter in enumerate(self.zybooks_toc, 1): row = popup.add_row_parent( f"{str(chapter['number']):>{chapter_pad_width}} - {chapter['title']}" ) for j, section in enumerate(chapter["sections"], 1): section_string = (f"{chapter['number']}" f".{section['number']:<{section_pad_width}}" f" - {section['title']}") if not self.is_allowed(section): section_string += " (Optional)" subrow = row.add_row_toggle( section_string, ZybookSectionSelector._SectionToggle((i, j), selected_sections)) # Disable selection of optional sections if not self.is_allowed(section): subrow.set_disabled() self.window.run_layer(popup) res = [] for section_numbers, selected in selected_sections.items(): if selected: if return_just_numbers: res.append(section_numbers) else: res.append(self.zybooks_sections[section_numbers]) return res