{"repo": "billjr99/ursinus-cs475-spring2021", "files": {"ursinus_canvas.py": {"ed65fe2311ee14cabf4bcc55f509c3ca8938661a": {"t": 1785595829.8285322, "n": 35196, "h": "bf25b43d778ea331", "c": "# https://canvasapi.readthedocs.io/en/stable/\n# https://canvas.instructure.com/doc/api/\n\n# canvasapi on pip is out of date, might need to install from https://github.com/ucfopen/canvasapi (git+https://github.com/ucfopen/canvasapi.git)\n# pip install python-frontmatter\n\nfrom canvasapi import Canvas, exceptions\nimport getopt\nimport sys\nimport frontmatter\nfrom datetime import datetime, timedelta\nimport threading\nimport time\nimport random\n\n# https://github.com/ucfopen/canvasapi/blob/develop/canvasapi/course.py\n# https://github.com/ucfopen/canvasapi/blob/develop/canvasapi/canvas.py\n\n# CONSIDERATION\n# Add assignment rubrics and point values (course.create_rubric) from assignment pages to facilitate grading (also to update point values)\n## https://canvas.instructure.com/doc/api/rubrics.html\n# course.create_course_section - separate calendar? duplicate assignments, etc?\n# Change course calendar entries to timetables: which can possibly be done on a per-section basis\n## https://canvas.instructure.com/doc/api/calendar_events.html#method.calendar_events_api.set_course_timetable\n# Tie to learning outcomes\n# Hide unused links\n\nAPI_URL = \"https://ursinus.instructure.com/\"\n# Generate key at API_URL + profile/settings\n# Obtain User ID from API_URL + /api/v1/users/self\n\nCANVAS_TIME_ZONE = \"America/New_York\"\nDUE_TIME = \"T045959Z\" # this time is no earlier than 11:59PM Eastern Time during EST or EDT\nDUE_DATE_OFFSET = 1 # add 1 day to make things due the next morning per the due time above\n\nTABS_TO_HIDE = [\"Quizzes\", \"Outcomes\", \"Collaborations\", \"Files\", \"Pages\", \"Announcements\", \"Rubrics\", \"Conferences\", \"Chat\", \"New Analytics\"] # which navigation pane items to hide if they are visible\nTABS_TO_SHOW = [\"Assignments\", \"Discussions\", \"Grades\", \"People\", \"Syllabus\", \"Modules\", \"Grizzly Gateway\", \"SPTQ\", \"Attendance\", \"Panopto Video\" ] # which navigation pane items to force show if they are already hidden\n\nchild_threads = []\n\nskipdiscussions = False\nskipassignments = False\nskipofficehours = True\n \ndef makelink(base, url):\n if url.startswith(\"http\"):\n return url\n else:\n return base + url\n\ndef addslash(str):\n if not (str.endswith(\"/\")):\n return str + \"/\"\n else:\n return str\n\ndef printlog(msg, output=True):\n if output:\n print(msg)\n \n# https://stackoverflow.com/questions/3663450/remove-substring-only-at-the-end-of-string\ndef rchop(s, suffix):\n if suffix and s.endswith(suffix):\n return s[:-len(suffix)]\n return s\n \ndef stripnobool(val):\n if type(val) is bool:\n result = \"\"\n else:\n result = str(val)\n \n return result.strip()\n\ndef dodelete(item, dosleep=True):\n repeat = True\n \n while repeat:\n if dosleep: # for rate limiting\n sleeptime = random.randint(5, 20)\n time.sleep(sleeptime)\n \n try:\n item.delete()\n printlog(\"Delete: Successful\")\n repeat = False\n except exceptions.ResourceDoesNotExist:\n print(\"Deleting: Resource Does Not Exist\")\n repeat = False\n except exceptions.Unauthorized:\n print(\"Deleting: Unauthorized\")\n repeat = False\n except exceptions.Forbidden:\n print(\"Deleting: Forbidden - it is possible that the rate limit is exceeded\")\n repeat = True\n except exceptions.CanvasException:\n print(\"Deleting: Canvas Error\")\n repeat = True\n except:\n print(\"Deleting: Unknown Error\")\n repeat = True\n \n \ndef delete_all_events(canvas, coursecontext):\n events = canvas.get_calendar_events(all_events = True, context_codes = [coursecontext])\n \n for event in events:\n t = threading.Thread(target=dodelete, args=(event,))\n child_threads.append(t)\n t.start()\n\ndef delete_all_assignments(course):\n if skipassignments:\n return\n \n assignments = course.get_assignments()\n\n for assignment in assignments: \n t = threading.Thread(target=dodelete, args=(assignment,))\n child_threads.append(t)\n t.start() \n \ndef delete_all_modules(course):\n modules = course.get_modules()\n \n itemthreads = []\n \n for module in modules:\n items = module.get_module_items()\n \n for item in items:\n t = threading.Thread(target=dodelete, args=(item,))\n child_threads.append(t)\n itemthreads.append(t)\n t.start() \n \n for t in itemthreads:\n t.join()\n \n t = threading.Thread(target=dodelete, args=(module,))\n child_threads.append(t)\n t.start() \n \ndef delete_all_assignment_groups(course):\n if skipdiscussions:\n return\n \n groups = course.get_assignment_groups()\n \n for group in groups:\n t = threading.Thread(target=dodelete, args=(group,))\n child_threads.append(t)\n t.start() \n \ndef delete_all_discussion_topics(course):\n if skipdiscussions:\n return\n \n topics = course.get_discussion_topics()\n \n itemthreads = []\n for topic in topics:\n entries = topic.get_topic_entries()\n \n for entry in entries:\n t = threading.Thread(target=dodelete, args=(entry,))\n child_threads.append(t)\n itemthreads.append(t)\n t.start() \n\n for t in itemthreads:\n t.join()\n \n t = threading.Thread(target=dodelete, args=(topic,))\n child_threads.append(t)\n t.start() \n\ndef delete_assignment_group_by_name(course, name):\n groups = course.get_assignment_groups()\n \n for group in groups:\n if group.name == name:\n t = threading.Thread(target=dodelete, args=(group,))\n child_threads.append(t)\n t.start() \n \ndef delete_old_data(course, canvas, coursecontext):\n t1 = threading.Thread(target=delete_all_assignments, args=(course,))\n t2 = threading.Thread(target=delete_all_events, args=(canvas,coursecontext,))\n t3 = threading.Thread(target=delete_all_modules, args=(course,))\n t4 = threading.Thread(target=delete_all_assignment_groups, args=(course,))\n t5 = threading.Thread(target=delete_all_discussion_topics, args=(course,))\n \n child_threads.append(t1)\n child_threads.append(t2)\n child_threads.append(t3)\n child_threads.append(t4)\n child_threads.append(t5)\n \n t1.start()\n t2.start()\n t3.start()\n t4.start()\n t5.start()\n \n # Avoid a race condition in which newly added items are deleted when gathered by these threads\n for t in child_threads:\n t.join()\n\n# https://canvas.instructure.com/doc/api/tabs.html#method.tabs.update\n# https://canvas.instructure.com/doc/api/tabs.html#method.tabs.index\ndef arrange_tabs(course):\n tabs = course.get_tabs()\n \n for tab in tabs:\n if tab.label in TABS_TO_HIDE:\n tab.update(hidden=True)\n if tab.label in TABS_TO_SHOW:\n tab.update(hidden=False)\n \n# https://canvas.instructure.com/doc/api/discussion_topics.html\n# https://canvas.instructure.com/doc/api/discussion_topics.html#method.discussion_topics.create\n# https://canvasapi.readthedocs.io/en/stable/course-ref.html\ndef add_discussion_topic(course, inputdict):\n if skipdiscussions:\n return\n \n course.create_discussion_topic(**inputdict)\n \ndef add_grading_standard(course, inputdict):\n course.add_grading_standards(inputdict)\n \ndef countWeeks(d1, d2):\n # https://stackoverflow.com/questions/14191832/how-to-calculate-difference-between-two-dates-in-weeks-in-python\n monday1 = (d1 - timedelta(days=d1.weekday()))\n monday2 = (d2 - timedelta(days=d2.weekday()))\n return (monday2 - monday1).days / 7\n\ndef getDayCodeNum(daycode):\n if daycode == 'M':\n return 0\n elif daycode == 'T':\n return 1\n elif daycode == 'W':\n return 2\n elif daycode == 'R':\n return 3\n elif daycode == 'F':\n return 4\n elif daycode == 'S':\n return 5\n elif daycode == 'U':\n return 6\n else:\n return -1\n\ndef getDayNum(dayidx, M, T, W, R, F, S, U):\n result = 0\n \n if M:\n dayidx = dayidx - 1\n \n if dayidx == -1:\n result = 0\n \n if T:\n dayidx = dayidx - 1\n \n if dayidx == -1:\n result = 1\n\n if W:\n dayidx = dayidx - 1\n \n if dayidx == -1:\n result = 2\n\n if R:\n dayidx = dayidx - 1\n \n if dayidx == -1:\n result = 3\n\n if F:\n dayidx = dayidx - 1\n \n if dayidx == -1:\n result = 4\n\n if S:\n dayidx = dayidx - 1\n \n if dayidx == -1:\n result = 5\n\n if U:\n dayidx = dayidx - 1\n \n if dayidx == -1:\n result = 6\n\n return result\n \ndef getTimeString(t): \n return t.strftime('%H%M%S') \n \ndef parseDate(dt):\n return datetime.strptime(dt, '%Y/%m/%d')\n \ndef parseTime(t):\n return datetime.strptime(t, '%I:%M %p')\n \ndef parseDateTimeCanvas(dt):\n return datetime.strftime(dt, '%Y-%m-%dT%H:%M:%SZ')\n \ndef adddays(dt, n):\n return dt + timedelta(days=n)\n \ndef addweeks(dt, n):\n return dt + timedelta(days=7*n)\n \ndef getDateString(dt):\n return dt.strftime('%Y%m%d') \n \ndef getCourseDate(startdate, weeknum, dayidx, M, T, W, R, F, S, U, tostring=True):\n dt = parseDate(startdate)\n weeknum = int(weeknum)\n dayidx = int(dayidx)\n \n dt = addweeks(dt, weeknum)\n daynum = getDayNum(dayidx, M, T, W, R, F, S, U)\n dt = adddays(dt, daynum)\n \n if tostring:\n return getDateString(dt)\n else:\n return dt\n \n# Create Assignment Shells: https://canvasapi.readthedocs.io/en/stable/examples.html#create-an-assignment\ndef create_assignment(course, inputdict):\n if skipassignments:\n return\n\n asmt = course.create_assignment(inputdict)\n return asmt\n \n# Create Assignment Group: https://canvas.instructure.com/doc/api/assignment_groups.html#method.assignment_groups_api.create\ndef create_assignmentgroup(course, inputdict):\n asmtgroup = course.create_assignment_group(**inputdict)\n return asmtgroup\n\n# Create a Module: https://canvas.instructure.com/doc/api/modules.html#method.context_modules_api.create\ndef create_module(course, inputdict, position=-1):\n module = course.create_module(inputdict)\n if position >= 1:\n module.edit(module={'position': position})\n return module\n \n# Add an item to an existing module: https://canvas.instructure.com/doc/api/modules.html#method.context_module_items_api.create\ndef add_module_item(module, inputdict):\n moduleitem = module.create_module_item(inputdict)\n module.edit(module={'published': True})\n return moduleitem\n \ndef get_assignment_group_containing_label(groups, label):\n for group in groups:\n name = group.name\n \n if label in name:\n return group\n \n return None\n\ndef getposidxandinc(map, key):\n if not (key in map):\n map[key] = 1 # positions are 1 indexed\n \n pos = map[key]\n map[key] = map[key] + 1 # increment the position for the next call\n \n return pos\n \ndef add_assignments_to_groups(course, postdict):\n # Track positions for each group to order them in the Canvas view\n posidx = {}\n \n # Get all the assignments\n assignments = course.get_assignments()\n \n # Get all the assignment groups\n groups = course.get_assignment_groups()\n \n # If Lab, Project, Assignment (etc...) is in the name, add it to the weight column with Lab, Project, or Assignment (etc...) in the name (you can prefix a deliverable name with the name of a grade breakdown column and it will add to that as well)\n for assignment in assignments:\n name = assignment.name\n asmtid = assignment.id\n \n if 'Lab:' in name:\n group = get_assignment_group_containing_label(groups, 'Lab')\n elif 'Programming Assignment:' in name:\n group = get_assignment_group_containing_label(groups, 'Programming Assignment')\n elif 'Written Assignment:' in name:\n group = get_assignment_group_containing_label(groups, 'Written Assignment') \n elif 'Project:' in name:\n group = get_assignment_group_containing_label(groups, 'Project')\n elif 'Exercise:' in name:\n group = get_assignment_group_containing_label(groups, 'Exercise')\n elif 'Participation:' in name:\n group = get_assignment_group_containing_label(groups, 'Participation') \n elif 'Quiz:' in name:\n group = get_assignment_group_containing_label(groups, 'Quiz') \n else:\n if 'grade_breakdown' in postdict:\n for breakdown in postdict['grade_breakdown']: \n category = breakdown['category']\n \n if category in name:\n group = get_assignment_group_containing_label(groups, category)\n break\n \n if not (group is None):\n pos = getposidxandinc(posidx, group)\n groupid = group.id\n \n assignment.edit(assignment={'assignment_group_id': groupid, 'position': pos})\n \n # Enable assignment group weighted grading\n course.update(course={'apply_assignment_group_weights': True})\n \n# Create Calendar: https://canvasapi.readthedocs.io/en/stable/canvas-ref.html (canvas.create_calendar_event, dict from https://canvas.instructure.com/doc/api/calendar_events.html)\ndef create_calendar_event(canvas, inputdict):\n try:\n canvas.create_calendar_event(inputdict)\n except exceptions.ResourceDoesNotExist:\n print(\"Calendar Event Creation: Resource Does Not Exist\")\n\ndef create_late_policy(course, inputdict):\n course.create_late_policy(**inputdict)\n \ndef process_markdown(fname, canvas, course, courseid, homepage):\n f = open(fname, 'r')\n mdcontents = f.read()\n \n post = frontmatter.loads(mdcontents)\n postdict = post.to_dict()\n \n coursecontext = 'course_' + str(courseid)\n\n coursenum = postdict['info']['course_number']\n coursename = postdict['info']['course_title']\n startdate = postdict['info']['course_start_date']\n enddate = postdict['info']['course_end_date'] \n isM = postdict['info']['class_meets_days']['isM']\n isT = postdict['info']['class_meets_days']['isT']\n isW = postdict['info']['class_meets_days']['isW']\n isR = postdict['info']['class_meets_days']['isR']\n isF = postdict['info']['class_meets_days']['isF']\n isS = postdict['info']['class_meets_days']['isS']\n isU = postdict['info']['class_meets_days']['isU']\n \n late_penalty_per_period = float(postdict['info']['late_penalty_per_period'])\n late_penalty_period = postdict['info']['late_penalty_period']\n \n printlog(\"Replacing Syllabus Page with Course Homepage...\")\n \n course.update(course={'time_zone': CANVAS_TIME_ZONE}) # Set time zone to Eastern Time\n course.update(course={'syllabus_body': \"\"}) # Set Syllabus to Course Webpage\n \n printlog(\"Deleting Old Data...\")\n \n # Delete All Assignments, Events, etc.; Re-Initialize Here\n delete_old_data(course, canvas, coursecontext)\n \n printlog(\"Creating Discussion Board Topics...\")\n \n # Create Discussion Topics\n inputdict = {}\n inputdict['title'] = \"Introductions\"\n inputdict['message'] = \"Welcome! Please use this space to introduce yourself. Feel free to say anything about yourself that you are comfortable sharing, like a word on why you are taking this course and what you hope to get from it.\"\n inputdict['discussion_type'] = \"threaded\"\n inputdict['pinned'] = True\n inputdict['published'] = True\n add_discussion_topic(course, inputdict)\n \n inputdict = {}\n inputdict['title'] = \"Class Activity Questions\"\n inputdict['message'] = \"This space will be used to answer class activity questions.\"\n inputdict['discussion_type'] = \"threaded\"\n inputdict['pinned'] = True\n inputdict['published'] = True\n add_discussion_topic(course, inputdict) \n \n inputdict = {}\n inputdict['title'] = \"Water Cooler\"\n inputdict['message'] = \"This is an open space - feel free to socialize here, post items that are on-topic or off-topic. I do ask that you adhere to the classroom etiquitte and standards.\"\n inputdict['discussion_type'] = \"threaded\"\n inputdict['pinned'] = True\n inputdict['published'] = True\n add_discussion_topic(course, inputdict)\n \n printlog(\"Writing Lecture Schedule...\")\n \n moduleidx = 1 # module positions are 1-indexed\n asmtidx = 1 # assignment index position as well\n \n # Write the lecture schedule as a recurring event\n for i in range(len(postdict['info']['class_meets_locations'])):\n section = postdict['info']['course_sections'][i]['section']\n for meeting in postdict['info']['class_meets_locations'][i]['section']:\n day = meeting['day']\n daynum = getDayCodeNum(meeting['day'])\n \n dt = parseDate(startdate)\n dt = adddays(dt, daynum)\n \n dtstart = getDateString(dt)\n dtstart = dtstart + \"T\"\n dtstart = dtstart + getTimeString(parseTime(meeting['starttime'])) \n \n dtend = getDateString(dt) # Assume event ends on the same day\n dtend = dtend + \"T\"\n dtend = dtend + getTimeString(parseTime(meeting['endtime'])) # leave in local time\n\n location = meeting['place']\n summary = coursenum + \" \" + coursename + \" Section \" + section + \" Class Meeting\"\n \n inputdict = {}\n inputdict['context_code'] = coursecontext\n inputdict['title'] = summary.strip()\n inputdict['description'] = summary.strip()\n inputdict['location_name'] = location.strip()\n inputdict['start_at'] = dtstart\n inputdict['end_at'] = dtend \n inputdict['time_zone_edited'] = CANVAS_TIME_ZONE \n inputdict['all_day'] = False\n inputdict['duplicate'] = {}\n inputdict['duplicate']['frequency'] = \"weekly\"\n inputdict['duplicate']['count'] = countWeeks(parseDate(startdate), parseDate(enddate))\n \n create_calendar_event(canvas, inputdict)\n\n printlog(\"Writing Assignments...\")\n scheduleitems = 0\n for item in postdict['schedule']: \n weekidx = item['week']\n dayidx = item['date']\n if 'title' in item:\n title = item['title']\n else:\n title = \"N/A\"\n if 'link' in item:\n link = item['link']\n else:\n link = \"\"\n \n startd = getCourseDate(startdate, weekidx, dayidx, isM, isT, isW, isR, isF, isS, isU)\n coursedt = getCourseDate(startdate, weekidx, dayidx, isM, isT, isW, isR, isF, isS, isU, tostring=False)\n coursedtstr = coursedt.strftime('%a, %b %d, %Y')\n if 'reschedule' in item:\n coursedtstr = item['reschedule']\n \n weekdayidx = \"(Week \" + str(int(weekidx)+1) + \" Day \" + str(int(dayidx)+1) + \")\"\n \n # Create a module for this entry\n inputdict = {}\n inputdict['name'] = coursedtstr + \" - \" + title \n inputdict['published'] = True\n module = create_module(course, inputdict, moduleidx)\n moduleidx = moduleidx + 1 # for positioning\n \n # Add course resources to first day entry\n if scheduleitems == 0:\n if 'class_notebook' in postdict['info']:\n inputdict = {}\n inputdict['title'] = \"Access the Class Notebook\"\n inputdict['type'] = \"ExternalUrl\"\n inputdict['external_url'] = postdict['info']['class_notebook']\n inputdict['new_tab'] = True\n inputdict['published'] = True\n add_module_item(module, inputdict)\n \n if 'chatlink' in postdict['info']:\n inputdict = {}\n inputdict['title'] = \"Access the Class Group Chat\"\n inputdict['type'] = \"ExternalUrl\"\n inputdict['external_url'] = postdict['info']['chatlink']\n inputdict['new_tab'] = True\n inputdict['published'] = True\n add_module_item(module, inputdict) \n \n scheduleitems = scheduleitems + 1\n \n # Create a Module Entry for Class Notes Link\n if 'link' in item:\n inputdict = {}\n inputdict['title'] = title\n inputdict['type'] = \"ExternalUrl\"\n inputdict['external_url'] = makelink(addslash(homepage), stripnobool(link))\n inputdict['new_tab'] = True\n inputdict['published'] = True\n add_module_item(module, inputdict)\n \n if 'deliverables' in item:\n for deliverable in item['deliverables']: \n dtitle = deliverable['dtitle']\n if 'dlink' in deliverable:\n dlink = deliverable['dlink']\n else:\n dlink = \"\"\n \n if 'points' in deliverable:\n points = int(deliverable['points'])\n else:\n points = 100 \n \n description = dtitle.strip() \n\n # Create an Assignment Shell\n if not (' handed out' in description.lower()):\n description = rchop(description, \" Due\")\n \n duedate = getCourseDate(startdate, weekidx, dayidx, isM, isT, isW, isR, isF, isS, isU, tostring=False)\n duedate = getDateString(adddays(duedate, DUE_DATE_OFFSET)) # offset the due date as needed for the due time which is in UTC\n \n inputdict = {}\n inputdict['name'] = description\n inputdict['submission_types'] = []\n inputdict['submission_types'].append('online_upload')\n inputdict['allowed_extensions'] = []\n inputdict['allowed_extensions'].append('zip')\n inputdict['allowed_extensions'].append('bz2')\n inputdict['allowed_extensions'].append('tar')\n inputdict['allowed_extensions'].append('gz')\n inputdict['allowed_extensions'].append('rar')\n inputdict['allowed_extensions'].append('7z')\n if not ('Programming Assignment:' in description.lower()):\n inputdict['allowed_extensions'].append('pdf')\n inputdict['allowed_extensions'].append('doc')\n inputdict['allowed_extensions'].append('docx')\n inputdict['allowed_extensions'].append('txt')\n inputdict['notify_of_update'] = True\n inputdict['published'] = True\n inputdict['points_possible'] = points\n inputdict['description'] = description + \" (\" + makelink(addslash(homepage), stripnobool(dlink)) + \")\"\n inputdict['due_at'] = parseDateTimeCanvas(datetime.strptime(duedate + DUE_TIME, \"%Y%m%dT%H%M%SZ\")) \n inputdict['position'] = asmtidx\n \n assignment = create_assignment(course, inputdict)\n asmtidx = asmtidx + 1\n \n # Create a Module Entry for the Assignment\n inputdict = {}\n inputdict['title'] = description\n inputdict['type'] = 'Assignment'\n inputdict['content_id'] = assignment.id\n inputdict['published'] = True\n add_module_item(module, inputdict)\n else:\n # Create a Module Entry for the Deliverable\n inputdict = {}\n inputdict['title'] = dtitle\n inputdict['type'] = \"ExternalUrl\"\n inputdict['external_url'] = makelink(addslash(homepage), stripnobool(dlink))\n inputdict['new_tab'] = True \n inputdict['published'] = True\n add_module_item(module, inputdict) \n \n if 'readings' in item:\n for reading in item['readings']: \n rtitle = reading['rtitle']\n if 'rlink' in reading:\n rlink = reading['rlink']\n else:\n rlink = \"\" \n \n # Create a Module Entry for the Reading Activity\n inputdict = {}\n inputdict['title'] = rtitle\n inputdict['type'] = \"ExternalUrl\"\n inputdict['external_url'] = makelink(addslash(homepage), stripnobool(rlink))\n inputdict['new_tab'] = True \n inputdict['published'] = True\n add_module_item(module, inputdict) \n \n # https://canvas.instructure.com/doc/api/late_policy.html\n printlog(\"Writing Late Policy...\")\n inputdict = {}\n inputdict['late_policy'] = {}\n inputdict['late_policy']['late_submission_deduction_enabled'] = True\n inputdict['late_policy']['missing_submission_deduction_enabled'] = False\n #inputdict['late_policy']['missing_submission_deduction'] = 100\n inputdict['late_policy']['late_submission_deduction'] = late_penalty_per_period\n inputdict['late_policy']['late_submission_interval'] = late_penalty_period\n create_late_policy(course, inputdict)\n \n if skipofficehours == False:\n printlog(\"Writing Office Hours...\")\n \n # Write Office Hours as a Recurring Event\n for instructor in postdict['instructors']:\n instructorname = instructor['name']\n \n for officehour in instructor['officehours']: \n day = officehour['day']\n daynum = getDayCodeNum(officehour['day'])\n \n dt = parseDate(startdate)\n dt = adddays(dt, daynum)\n \n dtstart = getDateString(dt)\n dtstart = dtstart + \"T\"\n dtstart = dtstart + getTimeString(parseTime(officehour['starttime'])) \n\n dtend = getDateString(dt) # assume no event overlaps a day boundary, ends on start date\n dtend = dtend + \"T\"\n dtend = dtend + getTimeString(parseTime(officehour['endtime'])) # leave in local time\n\n location = officehour['location']\n \n summary = coursenum + \" \" + coursename + \" Drop-In / Office Hours with \" + instructorname\n \n inputdict = {}\n inputdict['context_code'] = coursecontext\n inputdict['title'] = summary.strip()\n inputdict['description'] = summary.strip()\n inputdict['location_name'] = location.strip()\n inputdict['start_at'] = dtstart\n inputdict['end_at'] = dtend\n inputdict['time_zone_edited'] = CANVAS_TIME_ZONE \n inputdict['all_day'] = False\n inputdict['duplicate'] = {}\n inputdict['duplicate']['frequency'] = \"weekly\"\n inputdict['duplicate']['count'] = countWeeks(parseDate(startdate), parseDate(enddate))\n \n create_calendar_event(canvas, inputdict) \n\n printlog(\"Writing Exams...\")\n \n # Write Exam Dates \n for i in range(len(postdict['info']['class_meets_locations'])):\n section = postdict['info']['course_sections'][i]['section'] \n\n if not (postdict['info']['midtermexam'][i]['mdate'] == \"TBD\"):\n startd = getDateString(parseDate(postdict['info']['midtermexam'][i]['mdate']))\n startd = startd + \"T\"\n startd = startd + getTimeString(parseTime(postdict['info']['midtermexam'][i]['mstarttime'])) # leave in local time\n \n endd = getDateString(parseDate(postdict['info']['midtermexam'][i]['mdate']))\n endd = endd + \"T\"\n endd = endd + getTimeString(parseTime(postdict['info']['midtermexam'][i]['mendtime'])) # leave in local time\n \n dtitle = \"Midterm Exam\"\n location = postdict['info']['midtermexam'][i]['mroom']\n \n # Write the exam:\n inputdict = {}\n inputdict['context_code'] = coursecontext\n inputdict['title'] = dtitle.strip()\n inputdict['description'] = dtitle.strip()\n inputdict['location_name'] = location.strip()\n inputdict['start_at'] = dtstart\n inputdict['end_at'] = dtend \n inputdict['time_zone_edited'] = CANVAS_TIME_ZONE \n inputdict['all_day'] = False\n \n create_calendar_event(canvas, inputdict) \n\n if not (postdict['info']['finalexam'][i]['fdate'] == \"TBD\"):\n startd = getDateString(parseDate(postdict['info']['finalexam'][i]['fdate']))\n startd = startd + \"T\"\n startd = startd + getTimeString(parseTime(postdict['info']['finalexam'][i]['fstarttime'])) # leave in local time, timezone info given above assuming Eastern Time\n \n endd = getDateString(parseDate(postdict['info']['finalexam'][i]['fdate']))\n endd = endd + \"T\"\n endd = endd + getTimeString(parseTime(postdict['info']['finalexam'][i]['fendtime'])) # leave in local time, timezone info given above assuming Eastern Time\n \n dtitle = \"Final Exam\"\n location = postdict['info']['finalexam'][i]['froom']\n \n # Write the exam:\n inputdict = {}\n inputdict['context_code'] = coursecontext\n inputdict['title'] = dtitle.strip()\n inputdict['description'] = dtitle.strip()\n inputdict['location_name'] = location.strip()\n inputdict['start_at'] = dtstart\n inputdict['end_at'] = dtend \n inputdict['time_zone_edited'] = CANVAS_TIME_ZONE \n inputdict['all_day'] = False\n \n create_calendar_event(canvas, inputdict) \n \n printlog(\"Creating Assignment Groups...\")\n \n # Write Out Assignment Groups \n if 'grade_breakdown' in postdict:\n for breakdown in postdict['grade_breakdown']:\n inputdict = {} \n \n inputdict['name'] = breakdown['category']\n inputdict['group_weight'] = float(rchop(breakdown['weight'], '%'))\n \n # The Assignments group might exist by default - don't call anything that group name as an assignment category or grade breakdown just in case\n create_assignmentgroup(course, inputdict)\n \n add_assignments_to_groups(course, postdict)\n \n # Delete the default Assignments gradebook group\n delete_assignment_group_by_name(course, \"Assignments\") \n\ndef get_courseid(canvas, user):\n courses = user.get_courses()\n \n for course in courses:\n print(course)\n \n courseid = input(\"Which Course ID? \")\n\n return int(courseid)\n\ndef usage():\n print(\"Usage:\")\n print(\"\\t[-h | --help]\\tUsage Documentation\")\n print(\"\\t[-c | --courseid]\\tCanvas Course ID number (can be found using canvas link after logging in); omit for a course listing here\")\n print(\"\\t[-m | --markdown]\\tPath to course syllabus markdown file\") \n print(\"\\t[-w | --webpage]\\tURL of hosted course homepage (https://www.yourhomepage.com/course)\")\n print(\"\\t[-a | --apikey]\\tAPI Key (get from API_URL + /profile/settings)\")\n print(\"\\t[-u | --userid]\\tUser ID Number (get from API_URL + /api/v1/users/self)\")\n print(\"\\t[-t | --timezone]\\tTime Zone (i.e. America/New_York)\")\n print(\"\\t[-e | --duetime]\\t Latest Due Time in UTC for Your Time Zone (i.e., T045959Z for Eastern Time)\")\n print(\"\\t[-d | --discussions]\\tDo not delete or re-create discussion topics and entries\")\n print(\"\\t[-s | --assignments]\\tDo not delete or re-create assignments (but still re-arrange existing ones in modules view)\")\n print(\"\\t[-o | --noofficehours]\\tDo not delete or re-create office hours\")\n print(\"\\nDo not create an assignment group called Assignments, and do prefix assignment names with the desired Assignment Group Name: Deliverable\")\n \n# Parse user options\n# https://docs.python.org/3/library/getopt.html\ntry:\n opts, args = getopt.getopt(sys.argv[1:], \"hc:m:w:a:u:t:e:dso\", [\"help\", \"courseid=\", \"markdown=\", \"webpage=\", \"apikey=\", \"userid=\", \"timezone=\", \"duetime=\", \"discussions\", \"assignments\", \"noofficehours\"])\nexcept getopt.GetoptError as err:\n # print help information and exit:\n print(err) # will print something like \"option -z not recognized\"\n usage()\n sys.exit(2)\n\ncourseid = -1\nmarkdownfile = None\ncoursehomepage = None\nUSER_ID = None\nAPI_KEY = None\n\nfor o, a in opts:\n if o in (\"-h\", \"--help\"):\n usage()\n sys.exit()\n elif o in (\"-c\", \"--courseid\"):\n courseid = int(a)\n elif o in (\"-m\", \"--markdown\"):\n markdownfile = a\n elif o in (\"-w\", \"--webpage\"):\n coursehomepage = a\n elif o in (\"-a\", \"--apikey\"):\n API_KEY = a\n elif o in (\"-u\", \"--userid\"):\n USER_ID = a\n elif o in (\"-t\", \"--timezone\"):\n CANVAS_TIME_ZONE = a\n elif o in (\"-e\", \"--duetime\"):\n DUE_TIME = a\n elif o in (\"-d\", \"--discussions\"):\n skipdiscussions = True\n elif o in (\"-s\", \"--assignments\"):\n skipassignments = True\n elif o in (\"-o\", \"--noofficehours\"):\n skipofficehours = True \n\nif USER_ID is None:\n USER_ID = input(\"Enter User ID (get from API_URL + /api/v1/users/self): \")\nif API_KEY is None:\n API_KEY = input(\"Enter API Key (get from API_URL + /profile/settings): \")\n \nprintlog(\"Instantiating Canvas...\")\n# Instantiate Canvas and Course\ncanvas = Canvas(API_URL, API_KEY)\nuser = canvas.get_user(USER_ID)\n\nif courseid == -1:\n courseid = get_courseid(canvas, user)\nif markdownfile is None:\n markdownfile = input(\"Enter path to course syllabus markdown file: \")\nif coursehomepage is None:\n coursehomepage = input(\"Enter course website (https://www.yourhomepage.com/course): \")\n \ncourse = canvas.get_course(courseid)\n\nprintlog(\"Reading Markdown...\")\n# Read Course Markdown File\nprocess_markdown(markdownfile, canvas, course, courseid, coursehomepage)\n\nprintlog(\"Hiding/Showing Tabs...\")\n# Hide Navigation Tabs\narrange_tabs(course)\n\nprintlog(\"Finished: Waiting for Child Threads to Terminate\")\n# Clean Up\nfor t in child_threads:\n t.join()"}}}}