Esempio n. 1
0
def cmd_md(db, args):
    if not args.components:
        args.components = ["syllabus.md", "assignment_groups", "assignments",
                "files", "pages", "quizzes", "modules"]

    for component_filepath in args.components:
        if component_filepath.endswith("*"):
            component_filepath = component_filepath[:-1]
        if component_filepath.endswith("/"):
            component_filepath = component_filepath[:-1]

        if os.path.isdir(component_filepath) and not component_filepath.startswith("files"):
            if component_filepath not in helpers.DIRS:
                logging.error("Invalid directory: "+component_filepath)
                continue

            for child_path in os.listdir(component_filepath):
                full_child_path = component_filepath + '/' + child_path
                component = helpers_yaml.read(full_child_path)
                print(component.md())
        else:
            if component_filepath == "syllabus.md":
                f = open("syllabus.md")
                print(helpers.md2html(f.read()))
                f.close()
            else:
                component = helpers_yaml.read(component_filepath)
                if isinstance(component, list):
                    for obj in component:
                        print("\n\n*****\n", obj.md())
                else:
                    print(component.md())
Esempio n. 2
0
def load_questions_file(filename):
    # I don't want to require users to put a single question in a list so we
    # detect if it's a list and if not, put it in a list
    contents = helpers_yaml.read(filename)
    if not isinstance(contents, list):
        contents = [contents]
    return contents
Esempio n. 3
0
def push(db, course_, dry_run):
    remote = NavigationTabs().pull(db, course_, dry_run)
    local = helpers_yaml.read(FILENAME)
    hidden = []
    for remote_tab in remote.tabs:
        # Per Canvas docs: "Home and Settings tabs are not manageable, and
        # can't be hidden or moved"
        if remote_tab.id in ['home', 'settings']:
            continue
        found = False
        for local_tab in local.tabs:
            # we want to update a tab if 1. we have it locally in a different
            # position than what's currently in canvas, OR 2. the remote tab is
            # hidden but it's specified locally (i.e., we want it not hidden)
            if (remote_tab.label == local_tab.label
                    and (remote_tab.position != local_tab.position
                         or remote_tab.hidden != local_tab.hidden)):
                # local tabs are from the yaml and only have label, position,
                # and hidden fields, but we need the id
                local_tab.id = remote_tab.id
                local_tab.push(db, course_, dry_run)
                found = True
                break
        if not found:
            hidden.append(remote_tab)

    print("The following navigation tabs are or will be made hidden:")
    for tab in hidden:
        print("\t-", tab.label)
        # we also want to push a tab if it's not hidden in Canvas but it wasn't
        # included in the local yaml
        if not tab.hidden:
            tab.hidden = True
            tab.push(db, course_, dry_run)
Esempio n. 4
0
def cmd_remove(db, args):
    if not args.components:
        # remove everything
        args.components = ["modules", "assignments", "files", "pages",
                "quizzes", "assignment_groups"]

    if not args.course:
        args.course = course.find_all(db)
    else:
        args.course = course.match_courses(db, args.course)

    for component_filepath in args.components:
        if component_filepath.endswith("*"):
            component_filepath = component_filepath[:-1]
        if component_filepath.endswith("/"):
            component_filepath = component_filepath[:-1]

        if os.path.isdir(component_filepath) and not component_filepath.startswith("files"):
            if component_filepath not in helpers.DIRS:
                logging.error("Invalid directory: "+component_filepath)
                continue

            for child_path in os.listdir(component_filepath):
                full_child_path = component_filepath + '/' + child_path
                component = helpers_yaml.read(full_child_path)
                if component and not isinstance(component, str):
                    component.filename = full_child_path
                    for course_ in args.course:
                        print(f"removing {component} from {course_.name} ({course_.canvas_id})")
                        component.remove(db, course_, args.dry_run)
        else:
            for course_ in args.course:
                if component_filepath == "syllabus.md":
                    logging.error("Don't remove your syllabus!")
                else:
                    component = helpers_yaml.read(component_filepath)
                    if component and not isinstance(component, str):
                        component.filename = component_filepath
                        print(f"removing {component} from {course_.name} ({course_.canvas_id})")
                        component.remove(db, course_, args.dry_run)
                    else:
                        # not a yaml file so assume it's a file/dir to remove
                        files.remove(db, course_, component_filepath, args.dry_run)
Esempio n. 5
0
def pull(db, course_, quiz_, dry_run):
    course_id = course_.canvas_id
    quiz_id = quiz_.get('id')
    if not quiz_id:
        logging.error(f"Quiz {quiz_id} does not exist for course {course_id}")
        return None, None

    cid = canvas_id.find_by_id(db, course_id, quiz_id)
    if cid:
        quiz_['filename'] = cid.filename
    else:
        quiz_['filename'] = component.gen_filename(QUIZZES_DIR,
                                                   quiz_.get('title', ''))
        cid = canvas_id.CanvasID(quiz_['filename'], course_id)
        cid.canvas_id = quiz_.get('id')
        cid.save(db)

    # check assignment_group_id to fill in assignment_group by name
    agid = quiz_.get('assignment_group_id')
    if agid:
        # first check if we have a cid for the assignment group
        agcid = canvas_id.find_by_id(db, course_id, agid)
        if agcid:
            ag = helpers_yaml.read(agcid.filename)
            if ag:
                quiz_['assignment_group'] = ag.name
            else:
                logging.error("failed to find the assignment group for "
                              f"the assignment group with id {agid}. Your "
                              ".easeldb may be out of sync")
        else:
            # we could look at all the local assignment group files if we
            # don't have a cid for it but chances are there isn't a file.
            # so might as well just go back to canvas and ask for it
            agpath = assignment_group.ASSIGN_GROUP_PATH.format(course_id, agid)
            r = helpers.get(agpath, dry_run=dry_run)
            if 'name' in r:
                quiz_['assignment_group'] = r['name']
            else:
                logging.error("TODO: invalid response from canvas for "
                              "the assignment group: " +
                              json.dumps(r, indent=4))

    # quiz questions
    quiz_questions_path = QUIZ_PATH.format(course_.canvas_id,
                                           quiz_id) + "/questions"
    quiz_questions = helpers.get(quiz_questions_path)
    quiz_['quiz_questions'] = quiz_questions

    return Quiz.build(quiz_), cid
Esempio n. 6
0
 def preprocess(self, db, course_id, dry_run):
     item = helpers_yaml.read(self.item)
     item.filename = self.item
     if not self.title:
         self.title = getattr(item, "name", getattr(item, "title",
                                                    self.item))
     type_ = type(item).__name__
     if type_ not in VALID_TYPES:
         raise ValueError(f"Cannot add an item of type {type_} to a module."
                          " Can be one of {VALID_TYPES}")
     self.type = type_
     cid = item.get_canvas_id(db, course_id)
     if self.type == "Page":
         self.page_url = cid
     else:
         self.content_id = cid
Esempio n. 7
0
def pull(db, course_, assignment_id, dry_run):
    course_id = course_.canvas_id
    a = helpers.get(ASSIGNMENT_PATH.format(course_id,
        assignment_id), dry_run=dry_run)
    if not a.get('id'):
        logging.error(f"Assignment {assignment_id} does not exist for course {course_id}")
        return None, None
    cid = canvas_id.find_by_id(db, course_id, a.get('id'))
    if cid:
        a['filename'] = cid.filename
    else:
        a['filename'] = component.gen_filename(ASSIGNMENTS_DIR, a.get('name',''))
        cid = canvas_id.CanvasID(a['filename'], course_id)
        cid.canvas_id = a.get('id')
        cid.save(db)

    # check assignment_group_id to fill in assignment_group by name
    agid = a.get('assignment_group_id')
    if agid:
        # first check if we have a cid for the assignment group
        agcid = canvas_id.find_by_id(db, course_id, agid)
        if agcid:
            ag = helpers_yaml.read(agcid.filename)
            if ag:
                a['assignment_group'] = ag.name
            else:
                logging.error("failed to find the assignment group for "
                        f"the assignment group with id {agid}. Your "
                        ".easeldb may be out of sync")
        else:
            # we could look at all the local assignment group files if we
            # don't have a cid for it but chances are there isn't a file.
            # so might as well just go back to canvas and ask for it
            agpath = assignment_group.ASSIGN_GROUP_PATH.format(course_id, agid)
            r = helpers.get(agpath, dry_run=dry_run)
            if 'name' in r:
                a['assignment_group'] = r['name']
            else:
                logging.error("TODO: invalid response from canvas for "
                        "the assignment group: " + json.dumps(r, indent=4))

    return Assignment.build(a), cid
Esempio n. 8
0
 def preprocess(self, db, course_, dry_run):
     if not self.item:
         # it should be a url or SubHeader so we only have to set the type
         # and filename
         if self.external_url:
             self.type = "ExternalUrl"
         else:
             self.type = "SubHeader"
         return
     cid = canvas_id.CanvasID(self.item, course_.canvas_id)
     cid.find_id(db)
     if self.item.startswith("files"):
         # TODO: need a better way to detect that this is a literal File to
         # link to and not just a yaml file containing item info such as for
         # a Page or Assignment
         self.type = "File"
         if not cid.canvas_id:
             # the file probably doesn't exist in the course so we need to
             # push it first
             files.push(db, course_, self.item, True, dry_run)
         return
     item = helpers_yaml.read(self.item)
     item.filename = self.item
     if not cid.canvas_id:
         # the component probably doesn't exist in the course so we need to
         # push it first
         item.push(db, course_, dry_run)
     if not self.title:
         self.title = getattr(item, "name", getattr(item, "title",
                                                    self.item))
     type_ = type(item).__name__
     if type_ not in VALID_TYPES:
         raise ValueError(f"Cannot add an item of type {type_} to a module."
                          " Can be one of {VALID_TYPES}")
     self.type = type_
     cid = item.get_canvas_id(db, course_.canvas_id)
     if self.type == "Page":
         self.page_url = cid
     else:
         self.content_id = cid
Esempio n. 9
0
 def load_module(self, db, course_id):
     if self.filename:
         module_filename = self.filename.split('--')[0]
         m = helpers_yaml.read(module_filename)
         m.filename = module_filename
         return m
Esempio n. 10
0
def cmd_push(db, args):
    if not args.components:
        # push everything
        args.components = ["syllabus.md", "grading_scheme.yaml",
                "navigation.yaml", "assignment_groups", "assignments", "files",
                "pages", "quizzes", "modules"]

    if not args.course:
        args.course = course.find_all(db)
    else:
        args.course = course.match_courses(db, args.course)

    for component_filepath in args.components:
        if component_filepath.endswith("*"):
            component_filepath = component_filepath[:-1]
        if component_filepath.endswith("/"):
            component_filepath = component_filepath[:-1]

        if os.path.isdir(component_filepath) and not component_filepath.startswith("files"):
            if component_filepath not in helpers.DIRS:
                logging.error("Invalid directory: "+component_filepath)
                continue

            for child_path in os.listdir(component_filepath):
                full_child_path = component_filepath + '/' + child_path
                component = helpers_yaml.read(full_child_path)
                if component and not isinstance(component, str):
                    component.filename = full_child_path
                    for course_ in args.course:
                        print(f"pushing {component} to {course_.name} ({course_.canvas_id})")
                        component.push(db, course_, args.dry_run)

        elif component_filepath.startswith("files"):
            for course_ in args.course:
                files.push(db, course_, component_filepath, args.hidden,
                        args.dry_run)
        else:
            if not os.path.isfile(component_filepath):
                logging.error("Cannot find file: " + component_filepath)
                continue

            for course_ in args.course:
                if component_filepath == "syllabus.md":
                    print(f"pushing syllabus to {course_.name} ({course_.canvas_id})")
                    course.push_syllabus(db, course_.canvas_id, args.dry_run)
                elif component_filepath == "grading_scheme.yaml":
                    print(f"pushing grading scheme to {course_.name} ({course_.canvas_id})")
                    cid = canvas_id.CanvasID(component_filepath, course_.canvas_id)
                    cid.find_id(db)
                    if cid.canvas_id == "":
                        # Don't try to create a scheme if it doesn't already
                        # exist. Ideally, we update the scheme but Canvas
                        # apparently doesn't allow for that.
                        component = helpers_yaml.read(component_filepath)
                        component.push(db, course_, args.dry_run)
                        cid.find_id(db)
                    course.update_grading_scheme(db, course_.canvas_id,
                            cid.canvas_id, args.dry_run)
                elif component_filepath == "navigation.yaml":
                    print(f"pushing navigation tabs to {course_.name} ({course_.canvas_id})")
                    navigation_tab.push(db, course_, args.dry_run)
                else:
                    component = helpers_yaml.read(component_filepath)
                    if component and not isinstance(component, str):
                        component.filename = component_filepath
                        print(f"pushing {component} to {course_.name} ({course_.canvas_id})")
                        component.push(db, course_, args.dry_run)
                    else:
                        # not a yaml file so assume it's a file/dir to upload
                        files.push(db, course_, component_filepath,
                                args.hidden, args.dry_run)
Esempio n. 11
0
def cmd_pull(db, args):
    if not args.components:
        # pull everything
        args.components = ["assignment_groups", "assignments", "files",
                "pages", "quizzes", "modules"]

    if not args.course:
        args.course = course.find_all(db)
    else:
        args.course = course.match_courses(db, args.course)

    for component_filepath in args.components:
        local = {}
        remote = {}

        if component_filepath.endswith("*"):
            component_filepath = component_filepath[:-1]
        if component_filepath.endswith("/"):
            component_filepath = component_filepath[:-1]

        if os.path.isdir(component_filepath):
            if component_filepath not in helpers.DIRS:
                logging.error("Invalid directory: "+component_filepath)
                break

            # local versions
            for child_path in os.listdir(component_filepath):
                if not component_filepath.startswith("files"):
                    component = helpers_yaml.read(component_filepath + '/' +
                            child_path)
                    local[component.filename] = component

            # request remote versions
            for course_ in args.course:
                m = importlib.import_module("easel."+helpers.DIRS[component_filepath])
                print(f"pulling all {component_filepath} from {course_.name} ({course_.canvas_id})")
                for remote_comp in m.pull_all(db, course_, args.dry_run):
                    if remote_comp.filename in remote:
                        remote[remote_comp.filename].append(remote_comp)
                    else:
                        remote[remote_comp.filename] = [remote_comp]

        elif os.path.isfile(component_filepath):
            # local version
            component = helpers_yaml.read(component_filepath)
            component.filename = component_filepath
            local[component.filename] = component

            # request remote version(s)
            for course_ in args.course:
                print(f"pulling {component} from {course_.name} ({course_.canvas_id})")
                remote_comp = component.pull(db, course_, args.dry_run)
                if remote_comp.filename in remote:
                    remote[remote_comp.filename].append(remote_comp)
                else:
                    remote[remote_comp.filename] = [remote_comp]

        elif component_filepath in ["navigation", "navigation.yaml"]:
            # We don't have a local version at this point so only request
            # remote version(s). If we have a local version, the previous if
            # clause will take care of it.
            for course_ in args.course:
                print(f"pulling navigation tabs from {course_.name} ({course_.canvas_id})")
                component = navigation_tab.NavigationTabs()
                remote_comp = component.pull(db, course_, args.dry_run)
                if remote_comp.filename in remote:
                    remote[remote_comp.filename].append(remote_comp)
                else:
                    remote[remote_comp.filename] = [remote_comp]

        else:
            logging.error("Cannot find file: " + component_filepath)

        # TODO: merge remote into local
        for remote_comp in remote:
            components = remote[remote_comp]
            if remote_comp in local:
                logging.warn("Overwriting local copy of "
                        "{components[0].filename}. In the future, we'll "
                        "implement a merge workflow")
            if len(remote[remote_comp]) == 1:
                print(f"writing {components[0]} to {components[0].filename}")
                helpers_yaml.write(components[0].filename, components[0])
            else:
                logging.error("Too many remote options to handle right now..."
                        "Please manually specify a single course to pull from "
                        "with the -c flag and later we'll implement a merge "
                        "workflow")