예제 #1
0
    def test_write_string(self):
        d = OrderedDict([("the", "a"), ("order", "z"), ("is", "b"), ("important", "y")])
        string = yaml.dump(d)

        loaded = yaml.load(string)
        assert type(loaded) == OrderedDict
        assert loaded.keys() == ["the", "order", "is", "important"]
예제 #2
0
파일: utils.py 프로젝트: UCL-INGI/INGInious
    def show_page(self, page):
        static_directory = self.app.static_directory
        language = self.user_manager.session_language()

        # Check for the file
        filename = None
        filepaths = [os.path.join(static_directory, page + ".yaml"),
                     os.path.join(static_directory, page + "." + language + ".yaml")]

        for filepath in filepaths:
            if os.path.exists(filepath):
                filename = filepath
                mtime = os.stat(filepath).st_mtime

        if not filename:
            raise web.notfound()

        # Check and update cache
        if INGIniousStaticPage.cache.get(filepath, (0, None))[0] < mtime:
            with open(filename, "r") as f:
                INGIniousStaticPage.cache[filepath] = mtime, custom_yaml.load(f)

        filecontent = INGIniousStaticPage.cache[filepath][1]
        title = filecontent["title"]
        content = ParsableText.rst(filecontent["content"], initial_header_level=2)

        return self.template_helper.get_renderer().static(title, content)
예제 #3
0
    def test_write_ordereddict(self):
        d = OrderedDict([("the", "a"), ("order", "z"), ("is", "b"), ("important", "y")])
        yaml.dump(d, open(os.path.join(self.dir_path, "output.yaml"), "w"))

        loaded = yaml.load(open(os.path.join(self.dir_path, "output.yaml"), "r"))
        assert type(loaded) == OrderedDict
        assert loaded.keys() == ["the", "order", "is", "important"]
예제 #4
0
    def POST_AUTH(self, courseid, classroomid):  # pylint: disable=arguments-differ
        """ Edit a classroom """
        course, __ = self.get_course_and_check_rights(courseid, allow_all_staff=True)

        if course.is_lti():
            raise web.notfound()

        error = False
        data = web.input(tutors=[], groups=[], classroomfile={})
        if "delete" in data:
            # Get the classroom
            classroom = self.database.classrooms.find_one({"_id": ObjectId(classroomid), "courseid": courseid})

            if classroom is None:
                msg = _("Classroom not found.")
                error = True
            elif classroom['default']:
                msg = _("You can't remove your default classroom.")
                error = True
            else:
                self.database.classrooms.find_one_and_update({"courseid": courseid, "default": True},
                                                             {"$push": {
                                                                 "students": {"$each": classroom["students"]}
                                                             }})

                self.database.classrooms.delete_one({"_id": ObjectId(classroomid)})
                raise web.seeother(self.app.get_homepath() + "/admin/" + courseid + "/classrooms")
        else:
            try:
                if "upload" in data:
                    new_data = custom_yaml.load(data["classroomfile"].file)
                else:
                    # Prepare classroom-like data structure from input
                    new_data = {"description": data["description"], "tutors": data["tutors"], "students": [], "groups": []}
                    for index, groupstr in enumerate(data["groups"]):
                        group = json.loads(groupstr)
                        new_data["students"].extend(group["students"])
                        if index != 0:
                            new_data["groups"].append(group)

                classroom, errored_students = self.update_classroom(course, classroomid, new_data)
                student_list, tutor_list, other_students, users_info = self.get_user_lists(course, classroom["_id"])

                if len(errored_students) > 0:
                    msg = _("Changes couldn't be applied for following students :") + "<ul>"
                    for student in errored_students:
                        msg += "<li>" + student + "</li>"
                    msg += "</ul>"
                    error = True
                else:
                    msg = _("Classroom updated.")
            except:
                classroom = self.database.classrooms.find_one({"_id": ObjectId(classroomid), "courseid": courseid})
                student_list, tutor_list, other_students, users_info = self.get_user_lists(course, classroom["_id"])
                msg = _('An error occurred while parsing the data.')
                error = True

        return self.template_helper.get_renderer().course_admin.edit_classroom(course, student_list, tutor_list, other_students, users_info,
                                                                               classroom, msg, error)
예제 #5
0
 def test_load_string(self):
     loaded = yaml.load("""
     the: a
     order: z
     of: b
     the_: y
     keys: c
     is: x
     important: d
     """)
     assert type(loaded) == OrderedDict
     assert loaded.keys() == ["the", "order", "of", "the_", "keys", "is", "important"]
예제 #6
0
 def test_load_ordereddict(self):
     open(os.path.join(self.dir_path, "input.yaml"), "w").write("""
     the: a
     order: z
     of: b
     the_: y
     keys: c
     is: x
     important: d
     """)
     loaded = yaml.load(open(os.path.join(self.dir_path, "input.yaml"), "r"))
     assert type(loaded) == OrderedDict
     assert loaded.keys() == ["the", "order", "of", "the_", "keys", "is", "important"]
예제 #7
0
파일: babel.py 프로젝트: UCL-INGI/INGInious
def extract_yaml(fileobj, keywords, comment_tags, options):
    source = fileobj.read().decode(options.get('encoding', 'utf-8'))
    content = custom_yaml.load(source)

    if "task.yaml" in fileobj.name:
        keys = ["author", "context", "name"]
        for key in keys:
            yield 0, "", content.get(key, ""), [key]

        for problem_id, problem_content in content.get("problems").items():
            task_problem_types = {"code": CodeProblem, "code_single_line": CodeSingleLineProblem,
                                  "file": FileProblem, "multiple_choice": MultipleChoiceProblem,
                                  "match": MatchProblem}

            fields = task_problem_types.get(problem_content.get('type', "")).get_text_fields()

            for string, strkey in get_strings(content.get("problems").get(problem_id), fields):
                yield 0, "", string, [key + ", " + problem_id + ", " + strkey]

    elif "course.yaml" in fileobj.name:
        yield 0, "", content.get("name", ""), ["name"]
예제 #8
0
    def post_groups(self, course, data, active_tab, msg, error):
        if course.is_lti():
            return active_tab

        audience_list = self.user_manager.get_course_audiences(course)
        audience_students = {}
        for audience in audience_list:
            for stud in audience["students"]:
                audience_students.setdefault(stud, []).append(audience["_id"])

        errored_students = []
        if len(data["delete"]):

            for classid in data["delete"]:
                # Get the group
                group = self.database.groups.find_one(
                    {
                        "_id": ObjectId(classid),
                        "courseid": course.get_id()
                    }) if ObjectId.is_valid(classid) else None

                if group is None:
                    msg["groups"] = (
                        "group with id {} not found.").format(classid)
                    error["groups"] = True
                else:
                    self.database.groups.find_one_and_update(
                        {"courseid": course.get_id()},
                        {"$push": {
                            "students": {
                                "$each": group["students"]
                            }
                        }})

                    self.database.groups.delete_one({"_id": ObjectId(classid)})
                    msg["groups"] = _("Audience updated.")
            active_tab = "tab_groups"

        if "upload_groups" in data or "groups" in data:
            try:
                if "upload_groups" in data:
                    self.database.groups.delete_many(
                        {"courseid": course.get_id()})
                    groups = custom_yaml.load(data["groupfile"].file)
                else:
                    groups = json.loads(data["groups"])

                for index, new_group in enumerate(groups):
                    # In case of file upload, no id specified
                    new_group['_id'] = new_group[
                        '_id'] if '_id' in new_group else 'None'

                    # Update the group
                    group, errors = self.update_group(course, new_group['_id'],
                                                      new_group,
                                                      audience_students)
                    errored_students += errors

                if len(errored_students) > 0:
                    msg["groups"] = _(
                        "Changes couldn't be applied for following students :"
                    ) + "<ul>"
                    for student in errored_students:
                        msg["groups"] += "<li>" + student + "</li>"
                    msg["groups"] += "</ul>"
                    error["groups"] = True
                elif not error:
                    msg["groups"] = _("Groups updated.")
            except:
                msg["groups"] = _('An error occurred while parsing the data.')
                error["groups"] = True
            active_tab = "tab_groups"
        return active_tab
예제 #9
0
    def POST_AUTH(self, courseid, audienceid=''):  # pylint: disable=arguments-differ
        """ Edit a audience """
        course, __ = self.get_course_and_check_rights(courseid,
                                                      allow_all_staff=True)

        msg = ''
        error = False
        errored_students = []
        data = web.input(delete=[], tutors=[], audiencefile={})
        if len(data["delete"]):

            for classid in data["delete"]:
                # Get the audience
                audience = self.database.audiences.find_one(
                    {
                        "_id": ObjectId(classid),
                        "courseid": courseid
                    }) if ObjectId.is_valid(classid) else None

                if audience is None:
                    msg = _("Audience with id {} not found.").format(classid)
                    error = True
                else:
                    self.database.audiences.find_one_and_update(
                        {"courseid": courseid}, {
                            "$push": {
                                "students": {
                                    "$each": audience["students"]
                                }
                            }
                        })

                    self.database.audiences.delete_one(
                        {"_id": ObjectId(classid)})
                    msg = _("Audience updated.")

            if audienceid and audienceid in data["delete"]:
                raise web.seeother(self.app.get_homepath() + "/admin/" +
                                   courseid + "/audiences")

        try:
            if "upload" in data:
                self.database.audiences.delete_many(
                    {"courseid": course.get_id()})
                audiences = custom_yaml.load(data["audiencefile"].file)
            else:
                audiences = json.loads(data["audiences"])

            for index, new_audience in enumerate(audiences):
                # In case of file upload, no id specified
                new_audience['_id'] = new_audience[
                    '_id'] if '_id' in new_audience else 'None'

                # Update the audience
                audience, errors = self.update_audience(
                    course, new_audience['_id'], new_audience)

                # If file upload was done, get the default audience id
                audienceid = audience['_id']
                errored_students += errors

            if len(errored_students) > 0:
                msg = _("Changes couldn't be applied for following students :"
                        ) + "<ul>"
                for student in errored_students:
                    msg += "<li>" + student + "</li>"
                msg += "</ul>"
                error = True
            elif not error:
                msg = _("Audience updated.")
        except:
            msg = _('An error occurred while parsing the data.')
            error = True

        # Display the page
        return self.display_page(course, audienceid, msg, error)
예제 #10
0
    def POST_AUTH(self, courseid, aggregationid=''):  # pylint: disable=arguments-differ
        """ Edit a aggregation """
        course, _ = self.get_course_and_check_rights(courseid,
                                                     allow_all_staff=True)

        if course.is_lti():
            raise web.notfound()

        msg = ''
        error = False
        errored_students = []
        data = web.input(delete=[], tutors=[], groups=[], aggregationfile={})
        if len(data["delete"]):

            for classid in data["delete"]:
                # Get the aggregation
                aggregation = self.database.aggregations.find_one(
                    {
                        "_id": ObjectId(classid),
                        "courseid": courseid
                    }) if ObjectId.is_valid(classid) else None

                if aggregation is None:
                    msg = "Classroom" if course.use_classrooms(
                    ) else "Team" + " with id " + classid + "not found."
                    error = True
                elif aggregation['default'] and aggregationid:
                    msg = "You can't remove your default classroom."
                    error = True
                else:
                    self.database.aggregations.find_one_and_update(
                        {
                            "courseid": courseid,
                            "default": True
                        }, {
                            "$push": {
                                "students": {
                                    "$each": aggregation["students"]
                                }
                            }
                        })

                    self.database.aggregations.delete_one(
                        {"_id": ObjectId(classid)})
                    msg = "Classroom updated."

            if aggregationid and aggregationid in data["delete"]:
                raise web.seeother(self.app.get_homepath() + "/admin/" +
                                   courseid + "/aggregations")

        try:
            if "upload" in data:
                self.database.aggregations.delete_many(
                    {"courseid": course.get_id()})
                aggregations = custom_yaml.load(data["aggregationfile"].file)
            else:
                aggregations = json.loads(data["aggregations"])

            for index, new_aggregation in enumerate(aggregations):
                # In case of file upload, no id specified
                new_aggregation['_id'] = new_aggregation[
                    '_id'] if '_id' in new_aggregation else 'None'

                # In case of no aggregation usage, set the first entry default
                if not aggregationid and index == 0:
                    new_aggregation["default"] = True

                # If no groups field set, create group from class students if in groups only mode
                if "groups" not in new_aggregation:
                    new_aggregation["groups"] = [] if aggregationid else [{
                        'size':
                        len(new_aggregation['students']),
                        'students':
                        new_aggregation['students']
                    }]

                # Update the aggregation
                aggregation, errors = self.update_aggregation(
                    course, new_aggregation['_id'], new_aggregation)

                # If file upload was done, get the default aggregation id
                if course.use_classrooms() and aggregation['default']:
                    aggregationid = aggregation['_id']
                errored_students += errors

            if len(errored_students) > 0:
                msg = "Changes couldn't be applied for following students : <ul>"
                for student in errored_students:
                    msg += "<li>" + student + "</li>"
                msg += "</ul>"
                error = True
            elif not error:
                msg = "Classroom updated." if course.use_classrooms(
                ) else "Teams updated."
        except:
            msg = 'An error occurred while parsing the data.'
            error = True

        # Display the page
        return self.display_page(course, aggregationid, msg, error)
예제 #11
0
    def post_audiences(self, course, data, active_tab, msg, error):
        try:
            if 'audience' in data:
                if self.user_manager.has_admin_rights_on_course(course):

                    self.database.audiences.insert({
                        "courseid":
                        course.get_id(),
                        "students": [],
                        "tutors": [],
                        "description":
                        data['audience']
                    })
                    msg["audiences"] = _("New audience created.")
                else:
                    msg["audiences"] = _(
                        "You have no rights to add/change audiences")
                    error["audiences"] = True
                active_tab = "tab_audiences"

        except:
            msg["audiences"] = _('User returned an invalid form.')
            error["audiences"] = True
            active_tab = "tab_audiences"

        try:
            if "upload_audiences" in data or "audiences" in data:
                errored_students = []
                if "upload_audiences" in data:
                    self.database.audiences.delete_many(
                        {"courseid": course.get_id()})
                    audiences = custom_yaml.load(data["audiencefile"].file)
                else:
                    audiences = json.loads(data["audiences"])

                for index, new_audience in enumerate(audiences):
                    # In case of file upload, no id specified
                    new_audience['_id'] = new_audience[
                        '_id'] if '_id' in new_audience else 'None'

                    # Update the audience
                    audience, errors = self.update_audience(
                        course, new_audience['_id'], new_audience)

                    # If file upload was done, get the default audience id
                    audienceid = audience['_id']
                    errored_students += errors

                if len(errored_students) > 0:
                    msg["audiences"] = _(
                        "Changes couldn't be applied for following students :"
                    ) + "<ul>"
                    for student in errored_students:
                        msg["audiences"] += "<li>" + student + "</li>"
                    msg["audiences"] += "</ul>"
                    error["audiences"] = True
                elif not error:
                    msg["audiences"] = _("Audience updated.")
                active_tab = "tab_audiences"
        except Exception:
            msg["audiences"] = _('An error occurred while parsing the data.')
            error["audiences"] = True
            active_tab = "tab_audiences"
        return active_tab
예제 #12
0
    def POST_AUTH(self, courseid, classroomid):  # pylint: disable=arguments-differ
        """ Edit a classroom """
        course, _ = self.get_course_and_check_rights(courseid,
                                                     allow_all_staff=True)

        error = False
        data = web.input(tutors=[], groups=[], classroomfile={})
        if "delete" in data:
            # Get the classroom
            classroom = self.database.classrooms.find_one({
                "_id":
                ObjectId(classroomid),
                "courseid":
                courseid
            })

            if classroom is None:
                msg = "Classroom not found."
                error = True
            elif classroom['default']:
                msg = "You can't remove your default classroom."
                error = True
            else:
                self.database.classrooms.find_one_and_update(
                    {
                        "courseid": courseid,
                        "default": True
                    },
                    {"$push": {
                        "students": {
                            "$each": classroom["students"]
                        }
                    }})

                self.database.classrooms.delete_one(
                    {"_id": ObjectId(classroomid)})
                raise web.seeother("/admin/" + courseid + "/classrooms")
        else:
            try:
                if "upload" in data:
                    new_data = custom_yaml.load(data["classroomfile"].file)
                else:
                    # Prepare classroom-like data structure from input
                    new_data = {
                        "description": data["description"],
                        "tutors": data["tutors"],
                        "students": [],
                        "groups": []
                    }
                    for index, groupstr in enumerate(data["groups"]):
                        group = json.loads(groupstr)
                        new_data["students"].extend(group["students"])
                        if index != 0:
                            new_data["groups"].append(group)

                classroom, errored_students = self.update_classroom(
                    course, classroomid, new_data)
                student_list, tutor_list, other_students, users_info = self.get_user_lists(
                    course, classroom["_id"])

                if len(errored_students) > 0:
                    msg = "Changes couldn't be applied for following students : <ul>"
                    for student in errored_students:
                        msg += "<li>" + student + "</li>"
                    msg += "</ul>"
                    error = True
                else:
                    msg = "Classroom updated."
            except:
                classroom = self.database.classrooms.find_one({
                    "_id":
                    ObjectId(classroomid),
                    "courseid":
                    courseid
                })
                student_list, tutor_list, other_students, users_info = self.get_user_lists(
                    course, classroom["_id"])
                msg = 'An error occurred while parsing the data.'
                error = True

        return self.template_helper.get_renderer().course_admin.edit_classroom(
            course, student_list, tutor_list, other_students, users_info,
            classroom, msg, error)
예제 #13
0
    def POST_AUTH(self, courseid, groupid=''):  # pylint: disable=arguments-differ
        """ Edit a group """
        course, __ = self.get_course_and_check_rights(courseid,
                                                      allow_all_staff=True)

        if course.is_lti():
            raise web.notfound()

        audience_list = self.user_manager.get_course_audiences(course)
        audience_students = {}
        for audience in audience_list:
            for stud in audience["students"]:
                audience_students.setdefault(stud, []).append(audience["_id"])

        msg = ''
        error = False
        errored_students = []
        data = web.input(delete=[], groupfile={})
        if len(data["delete"]):

            for classid in data["delete"]:
                # Get the group
                group = self.database.groups.find_one({
                    "_id": ObjectId(classid),
                    "courseid": courseid
                }) if ObjectId.is_valid(classid) else None

                if group is None:
                    msg = ("group with id {} not found.").format(classid)
                    error = True
                else:
                    self.database.groups.find_one_and_update(
                        {"courseid": courseid},
                        {"$push": {
                            "students": {
                                "$each": group["students"]
                            }
                        }})

                    self.database.groups.delete_one({"_id": ObjectId(classid)})
                    msg = _("Audience updated.")

            if groupid and groupid in data["delete"]:
                raise web.seeother(self.app.get_homepath() + "/admin/" +
                                   courseid + "/groups")

        try:
            if "upload" in data:
                self.database.groups.delete_many({"courseid": course.get_id()})
                groups = custom_yaml.load(data["groupfile"].file)
            else:
                groups = json.loads(data["groups"])

            for index, new_group in enumerate(groups):
                # In case of file upload, no id specified
                new_group[
                    '_id'] = new_group['_id'] if '_id' in new_group else 'None'

                # Update the group
                group, errors = self.update_group(course, new_group['_id'],
                                                  new_group, audience_students)
                errored_students += errors

            if len(errored_students) > 0:
                msg = _("Changes couldn't be applied for following students :"
                        ) + "<ul>"
                for student in errored_students:
                    msg += "<li>" + student + "</li>"
                msg += "</ul>"
                error = True
            elif not error:
                msg = _("Groups updated.")
        except:
            raise
            msg = _('An error occurred while parsing the data.')
            error = True

        # Display the page
        return self.display_page(course, msg, error)
예제 #14
0
    def import_elements(self, course, data):
        imported_tasks, renamed_tasks = [], {}
        errors = []

        try:
            with tarfile.open(fileobj=data["archive_file"].file) as tar:
                elements = tar.getnames()

                if "import_inginious_file" in data:
                    # import tasks
                    for taskid in get_tasks_id(elements):
                        try:
                            # get files
                            task_files = get_task_files(taskid, elements)
                            tar_members = [
                                tar.getmember(task_file)
                                for task_file in task_files
                            ]

                            # avoid tasks with same id
                            new_taskid = make_id_unique(
                                taskid, course.get_tasks())
                            if taskid != new_taskid:
                                renamed_tasks[taskid] = new_taskid
                                for tar_member in tar_members:
                                    tar_member.name = new_taskid + tar_member.name.lstrip(
                                        taskid)

                            # extract files
                            tar.extractall(course.get_fs().prefix, tar_members)

                            # check task descriptor is in inginious format and remove tags
                            task_descriptor = self.task_factory.get_task_descriptor_content(
                                course.get_id(), new_taskid)
                            if not check_task_descriptor(task_descriptor):
                                raise TaskUnreadableException(
                                    "Invalid task config format")
                            task_descriptor["categories"] = []
                            self.task_factory.update_task_descriptor_content(
                                course.get_id(), taskid, task_descriptor)

                            imported_tasks.append(new_taskid)
                        except:
                            errors.append(
                                _("Invalid format for task ") + taskid)
                            if new_taskid in course.get_tasks():
                                self.task_factory.delete_task(
                                    course.get_id(), new_taskid)

                    # import sections
                    if "sections.yaml" in elements:
                        try:
                            from inginious.frontend.plugins.course_structure.webapp_course import \
                                get_sections_tasks_ids, replace_sections_tasks_ids, get_toc, update_toc_content

                            sections_content = load(
                                tar.extractfile("sections.yaml").read())
                            replace_sections_tasks_ids(sections_content,
                                                       renamed_tasks)

                            # if all tasks in the sections are imported, add sections to course structure and adapt rank
                            if all(elem in imported_tasks for elem in
                                   get_sections_tasks_ids(sections_content)):
                                toc = get_toc(course)
                                for section in sections_content:
                                    section["rank"] = len(toc)
                                    toc.append(section)

                                update_toc_content(self.course_factory,
                                                   course.get_id(), toc)
                            else:
                                errors.append(
                                    _("Some tasks in the structure are not included in the archive"
                                      ))
                        except:
                            errors.append(
                                _("Sections not supported for this instance"))
        except Exception as e:
            print()
            errors.append(_("Invalid archive format"))

        return self.page(course, errors,
                         None if errors else _("Import successful."))
예제 #15
0
    def POST_AUTH(self, courseid, aggregationid=''):  # pylint: disable=arguments-differ
        """ Edit a aggregation """
        course, __ = self.get_course_and_check_rights(courseid, allow_all_staff=True)

        if course.is_lti():
            raise web.notfound()

        msg=''
        error = False
        errored_students = []
        data = web.input(delete=[], tutors=[], groups=[], aggregationfile={})
        if len(data["delete"]):

            for classid in data["delete"]:
                # Get the aggregation
                aggregation = self.database.aggregations.find_one({"_id": ObjectId(classid), "courseid": courseid}) if ObjectId.is_valid(classid) else None

                if aggregation is None:
                    msg = _("Classroom with id {} not found.").format(classid) if course.use_classrooms() else _("Team with id {} not found.").format(classid)
                    error = True
                elif aggregation['default'] and aggregationid:
                    msg = _("You can't remove your default classroom.")
                    error = True
                else:
                    self.database.aggregations.find_one_and_update({"courseid": courseid, "default": True},
                                                                 {"$push": {
                                                                     "students": {"$each": aggregation["students"]}
                                                                 }})

                    self.database.aggregations.delete_one({"_id": ObjectId(classid)})
                    msg = _("Classroom updated.")

            if aggregationid and aggregationid in data["delete"]:
                raise web.seeother(self.app.get_homepath() + "/admin/" + courseid + "/aggregations")

        try:
            if "upload" in data:
                self.database.aggregations.delete_many({"courseid": course.get_id()})
                aggregations = custom_yaml.load(data["aggregationfile"].file)
            else:
                aggregations = json.loads(data["aggregations"])

            for index, new_aggregation in enumerate(aggregations):
                # In case of file upload, no id specified
                new_aggregation['_id'] = new_aggregation['_id'] if '_id' in new_aggregation else 'None'

                # In case of no aggregation usage, set the first entry default
                new_aggregation["default"] = not aggregationid and index == 0

                # If no groups field set, create group from class students if in groups only mode
                if "groups" not in new_aggregation:
                    new_aggregation["groups"] = [] if aggregationid else [{'size': len(new_aggregation['students']),
                                                                       'students': new_aggregation['students']}]

                # Update the aggregation
                aggregation, errors = self.update_aggregation(course, new_aggregation['_id'], new_aggregation)

                # If file upload was done, get the default aggregation id
                if course.use_classrooms() and aggregation['default']:
                    aggregationid = aggregation['_id']
                errored_students += errors

            if len(errored_students) > 0:
                msg = _("Changes couldn't be applied for following students :") + "<ul>"
                for student in errored_students:
                    msg += "<li>" + student + "</li>"
                msg += "</ul>"
                error = True
            elif not error:
                msg = _("Classroom updated.") if course.use_classrooms() else _("Teams updated.")
        except:
            msg = _('An error occurred while parsing the data.')
            error = True

        # Display the page
        return self.display_page(course, aggregationid, msg, error)