def get_class(user, class_id): if not is_teacher(request): return 'Only teachers can retrieve classes', 403 Class = DATABASE.get_class(class_id) if not Class or Class['teacher'] != user['username']: return 'No such class', 404 students = [] for student_username in Class.get('students', []): student = DATABASE.user_by_username(student_username) programs = DATABASE.programs_for_user(student_username) highest_level = max( program['level'] for program in programs) if len(programs) else 0 sorted_public_programs = list( sorted( [program for program in programs if program.get('public')], key=lambda p: p['date'])) if sorted_public_programs: latest_shared = sorted_public_programs[-1] latest_shared['link'] = os.getenv( 'BASE_URL') + f"/hedy/{latest_shared['id']}/view" else: latest_shared = None students.append({ 'username': student_username, 'last_login': utils.mstoisostring(student['last_login']), 'programs': len(programs), 'highest_level': highest_level, 'latest_shared': latest_shared }) if utils.is_testing_request(request): return jsonify({ 'students': students, 'link': Class['link'], 'name': Class['name'], 'id': Class['id'] }) return render_template( 'class-overview.html', lang=requested_lang(), auth=TRANSLATIONS.get_translations(requested_lang(), 'Auth'), menu=render_main_menu('my-profile'), username=current_user(request)['username'], is_teacher=is_teacher(request), current_page='my-profile', class_info={ 'students': students, 'link': os.getenv('BASE_URL') + '/hedy/l/' + Class['link'], 'name': Class['name'], 'id': Class['id'] })
def get_quiz(level_source, question_nr, attempt): if not config.get('quiz-enabled') and g.lang != 'nl': return 'Hedy quiz disabled!', 404 else: # Reading the yaml file quiz_data = quiz_data_file_for(level_source) if not quiz_data.exists(): return 'No quiz yaml file found for this level', 404 # set globals g.lang = lang = requested_lang() g.prefix = '/hedy' # Loop through the questions and check that the loop doesn't reach out of bounds q_nr = int(question_nr) if int(attempt) == 1: questionStatus = 'start' if q_nr <= len(quiz_data['questions']): question = quiz_data['questions'][q_nr - 1].get(q_nr) # Convert the indices to the corresponding characters char_array = [] for i in range(len(question['mp_choice_options'])): char_array.append(chr(ord('@') + (i + 1))) return render_template( 'quiz_question.html', quiz=quiz_data, level_source=level_source, questionStatus=questionStatus, questions=quiz_data['questions'], question=quiz_data['questions'][q_nr - 1].get(q_nr), question_nr=q_nr, correct=session.get('correct_answer'), attempt=attempt, char_array=char_array, menu=render_main_menu('adventures'), lang=lang, username=current_user(request)['username'], is_teacher=is_teacher(request), auth=TRANSLATIONS.get_translations(requested_lang(), 'Auth')) else: return render_template('endquiz.html', correct=session.get('correct_answer'), total_score=session.get('total_score'), menu=render_main_menu('adventures'), lang=lang, quiz=quiz_data, level=int(level_source) + 1, questions=quiz_data['questions'], next_assignment=1, username=current_user(request)['username'], is_teacher=is_teacher(request), auth=TRANSLATIONS.get_translations( requested_lang(), 'Auth'))
def main_page(page): if page == 'favicon.ico': abort(404) lang = requested_lang() effective_lang = lang if page in ['signup', 'login', 'my-profile', 'recover', 'reset', 'admin']: return auth_templates(page, lang, render_main_menu(page), request) if page == 'programs': return programs_page(request) # Default to English if requested language is not available if not path.isfile(f'main/{page}-{effective_lang}.md'): effective_lang = 'en' try: with open(f'main/{page}-{effective_lang}.md', 'r', encoding='utf-8') as f: contents = f.read() except IOError: abort(404) front_matter, markdown = split_markdown_front_matter(contents) menu = render_main_menu(page) if page == 'for-teachers': teacher_classes = [] if not current_user( request)['username'] else DATABASE.get_teacher_classes( current_user(request)['username'], True) return render_template('for-teachers.html', sections=split_teacher_docs(contents), lang=lang, menu=menu, username=current_user(request)['username'], is_teacher=is_teacher(request), auth=TRANSLATIONS.get_translations( lang, 'Auth'), teacher_classes=teacher_classes, **front_matter) return render_template('main-page.html', mkd=markdown, lang=lang, menu=menu, username=current_user(request)['username'], is_teacher=is_teacher(request), auth=TRANSLATIONS.get_translations(lang, 'Auth'), **front_matter)
def render_code_editor_with_tabs(level_defaults, max_level, level_number, version, loaded_program, adventures, restrictions, adventure_name): user = current_user() if not level_defaults: return utils.page_404 (ui_message='no_such_level') arguments_dict = {} # Meta stuff arguments_dict['level_nr'] = str(level_number) arguments_dict['level'] = level_number arguments_dict['prev_level'] = int(level_number) - 1 if int(level_number) > 1 else None arguments_dict['next_level'] = int(level_number) + 1 if int(level_number) < max_level else None arguments_dict['example_programs'] = restrictions['example_programs'] arguments_dict['hide_prev_level'] = restrictions['hide_prev_level'] arguments_dict['hide_next_level'] = restrictions['hide_next_level'] arguments_dict['menu'] = True arguments_dict['latest'] = version arguments_dict['selected_page'] = 'code' arguments_dict['page_title'] = f'Level {level_number} – Hedy' arguments_dict['username'] = user['username'] arguments_dict['is_teacher'] = is_teacher(user) arguments_dict['loaded_program'] = loaded_program arguments_dict['adventures'] = adventures arguments_dict['adventure_name'] = adventure_name # Merge level defaults into adventures so it is rendered as the first tab arguments_dict.update(**attr.asdict(level_defaults)) return render_template("code-page.html", **arguments_dict)
def prejoin_class(class_id, link): Class = DATABASE.get_class(class_id) if not Class or Class['link'] != link: return 'No such class', 404 user = {} if request.cookies.get(cookie_name): token = DATABASE.get_token(request.cookies.get(cookie_name)) if token: if token['username'] in Class.get('students', []): return render_template( 'class-already-joined.html', lang=requested_lang(), auth=TRANSLATIONS.get_translations( requested_lang(), 'Auth'), menu=render_main_menu('my-profile'), username=current_user(request)['username'], current_page='my-profile', class_info={'name': Class['name']}) user = DATABASE.user_by_username(token['username']) return render_template( 'class-prejoin.html', lang=requested_lang(), auth=TRANSLATIONS.get_translations(requested_lang(), 'Auth'), menu=render_main_menu('my-profile'), username=current_user(request)['username'], is_teacher=is_teacher(request), current_page='my-profile', class_info={ 'link': os.getenv('BASE_URL') + '/class/' + Class['id'] + '/join/' + Class['link'] + '?lang=' + requested_lang(), 'name': Class['name'] })
def get_class_info(user, class_id): if not is_teacher(user): return utils.page_403(ui_message='retrieve_class') Class = DATABASE.get_class(class_id) if not Class or Class['teacher'] != user['username']: return utils.page_404(ui_message='no_such_class') if hedy_content.Adventures(g.lang).has_adventures(): adventures = hedy_content.Adventures( g.lang).get_adventure_keyname_name_levels() else: adventures = hedy_content.Adventures( "en").get_adventure_keyname_name_levels() levels = hedy_content.LevelDefaults(g.lang).levels preferences = DATABASE.get_customizations_class(class_id) return render_template( 'customize-class.html', page_title=hedyweb.get_page_title('customize class'), class_info={ 'name': Class['name'], 'id': Class['id'] }, levels=levels, adventures=adventures, preferences=preferences, current_page='for-teachers')
def view_program(id): g.lang = requested_lang() g.prefix = '/hedy' result = DATABASE.program_by_id(id) if not result: return 'No such program', 404 # Default to the language of the program's author (but still respect) # the switch if given. lang = request.args.get("lang") if not lang: lang = result['lang'] arguments_dict = {} arguments_dict['program_id'] = id arguments_dict['page_title'] = f'{result["name"]} – Hedy' arguments_dict['level'] = result['level'] # Necessary for running arguments_dict['loaded_program'] = result arguments_dict['editor_readonly'] = True arguments_dict['show_edit_button'] = True # Everything below this line has nothing to do with this page and it's silly # that every page needs to put in so much effort to re-set it arguments_dict['lang'] = lang arguments_dict['menu'] = render_main_menu('view') arguments_dict['auth'] = TRANSLATIONS.get_translations(lang, 'Auth') arguments_dict['username'] = current_user(request)['username'] or None arguments_dict['is_teacher'] = is_teacher(request) arguments_dict.update(**TRANSLATIONS.get_translations(lang, 'ui')) return render_template("view-program-page.html", **arguments_dict)
def render_code_editor_with_tabs(request, level_defaults, lang, max_level, level_number, menu, translations, version, loaded_program, adventures, adventure_name): if not level_defaults: return utils.page_404 (translations, menu, current_user(request) ['username'], lang, translations.get_translations (lang, 'ui').get ('no_such_level')) arguments_dict = {} # Meta stuff arguments_dict['level_nr'] = str(level_number) arguments_dict['lang'] = lang arguments_dict['level'] = level_number arguments_dict['prev_level'] = int(level_number) - 1 if int(level_number) > 1 else None arguments_dict['next_level'] = int(level_number) + 1 if int(level_number) < max_level else None arguments_dict['menu'] = menu arguments_dict['latest'] = version arguments_dict['selected_page'] = 'code' arguments_dict['page_title'] = f'Level {level_number} – Hedy' arguments_dict['auth'] = translations.get_translations (lang, 'Auth') arguments_dict['username'] = current_user(request) ['username'] arguments_dict['is_teacher'] = is_teacher(request) arguments_dict['loaded_program'] = loaded_program arguments_dict['adventures'] = adventures arguments_dict['adventure_name'] = adventure_name # Translations arguments_dict.update(**translations.get_translations(lang, 'ui')) # Merge level defaults into adventures so it is rendered as the first tab arguments_dict.update(**attr.asdict(level_defaults)) return render_template("code-page.html", **arguments_dict)
def create_class(user): if not is_teacher(user): return 'Only teachers can create classes', 403 body = request.json # Validations if not isinstance(body, dict): return 'body must be an object', 400 if not isinstance(body.get('name'), str): return 'name must be a string', 400 # We use this extra call to verify if the class name doesn't already exist, if so it's a duplicate Classes = DATABASE.get_teacher_classes(user['username'], True) for Class in Classes: if Class['name'] == body['name']: return "duplicate", 200 Class = { 'id': uuid.uuid4().hex, 'date': utils.timems(), 'teacher': user['username'], 'link': utils.random_id_generator(7), 'name': body['name'] } DATABASE.store_class(Class) return {'id': Class['id']}, 200
def adventures_list(): return render_template( 'adventures.html', lang=lang, adventures=load_adventure_for_language(requested_lang()), menu=render_main_menu('adventures'), username=current_user(request)['username'], is_teacher=is_teacher(request), auth=TRANSLATIONS.get_translations(requested_lang(), 'Auth'))
def index(level, step): # Sublevel requested if re.match('\d+-\d+', level): pass # If level has a dash, we keep it as a string # Normal level requested elif re.match('\d', level): try: g.level = level = int(level) except: return 'No such Hedy level!', 404 else: return 'No such Hedy level!', 404 g.lang = requested_lang() g.prefix = '/hedy' loaded_program = '' adventure_name = '' # If step is a string that has more than two characters, it must be an id of a program if step and isinstance(step, str) and len(step) > 2: result = DATABASE.program_by_id(step) if not result: return 'No such program', 404 # If the program is not public, allow only the owner of the program, the admin user and the teacher users to access the program user = current_user(request) public_program = 'public' in result and result['public'] if not public_program and user['username'] != result[ 'username'] and not is_admin(request) and not is_teacher( request): return 'No such program!', 404 loaded_program = { 'code': result['code'], 'name': result['name'], 'adventure_name': result.get('adventure_name') } if 'adventure_name' in result: adventure_name = result['adventure_name'] # We default to step 1 to provide a meaningful default assignment step = 1 adventure_assignments = load_adventure_assignments_per_level(g.lang, level) return hedyweb.render_assignment_editor( request=request, course=HEDY_COURSE[g.lang], level_number=level, assignment_number=step, menu=render_main_menu('hedy'), translations=TRANSLATIONS, version=version(), adventure_assignments=adventure_assignments, loaded_program=loaded_program, adventure_name=adventure_name)
def get_class(user, class_id): app.logger.info('This is info output') if not is_teacher(user): return utils.page_403(ui_message='retrieve_class') Class = DATABASE.get_class(class_id) if not Class or Class['teacher'] != user['username']: return utils.page_404(ui_message='no_such_class') students = [] for student_username in Class.get('students', []): student = DATABASE.user_by_username(student_username) programs = DATABASE.programs_for_user(student_username) highest_level = max( program['level'] for program in programs) if len(programs) else 0 sorted_public_programs = list( sorted( [program for program in programs if program.get('public')], key=lambda p: p['date'])) if sorted_public_programs: latest_shared = sorted_public_programs[-1] latest_shared['link'] = f"/hedy/{latest_shared['id']}/view" else: latest_shared = None students.append({ 'username': student_username, 'last_login': utils.datetotimeordate( utils.mstoisostring(student['last_login'])), 'programs': len(programs), 'highest_level': highest_level, 'latest_shared': latest_shared }) if utils.is_testing_request(request): return jsonify({ 'students': students, 'link': Class['link'], 'name': Class['name'], 'id': Class['id'] }) return render_template( 'class-overview.html', current_page='for-teachers', page_title=hedyweb.get_page_title('class overview'), class_info={ 'students': students, 'link': '/hedy/l/' + Class['link'], 'name': Class['name'], 'id': Class['id'] })
def submit_answer(level_source, question_nr): if not config['quiz-enabled'] and g.lang != 'nl': return 'Hedy quiz disabled!', 404 else: # Get the chosen option from the request form with radio buttons option = request.form["radio_option"] # Reading yaml file if os.path.isfile( f'coursedata/quiz/quiz_questions_lvl{level_source}.yaml'): quiz_data = load_yaml( f'coursedata/quiz/quiz_questions_lvl{level_source}.yaml') else: return 'No quiz yaml file found for this level', 404 # Convert question_nr to an integer q_nr = int(question_nr) # Convert the corresponding chosen option to the index of an option question = quiz_data['questions'][q_nr - 1].get(q_nr) index_option = ord(option.split("-")[1]) - 65 # If the correct answer is chosen, update the total score and the number of correct answered questions if question['correct_answer'] in option: session['total_score'] = session.get( 'total_score') + question['question_score'] session['correct_answer'] = session.get('correct_answer') + 1 # Loop through the questions if q_nr <= len(quiz_data['questions']): return render_template('feedback.html', quiz=quiz_data, question=question, questions=quiz_data['questions'], level_source=level_source, question_nr=q_nr, correct=session.get('correct_answer'), option=option, index_option=index_option, menu=render_main_menu('adventures'), lang=lang, username=current_user(request)['username'], is_teacher=is_teacher(request), auth=TRANSLATIONS.get_translations( requested_lang(), 'Auth')) else: # show a different page for after the last question return 'No end quiz page!', 404
def update_class(user, class_id): if not is_teacher(request): return 'Only teachers can update classes', 403 body = request.json # Validations if not isinstance(body, dict): return 'body must be an object', 400 if not isinstance(body.get('name'), str): return 'name must be a string', 400 Class = DATABASE.get_class(class_id) if not Class or Class['teacher'] != user['username']: return 'No such class', 404 Class = DATABASE.update_class(class_id, body['name']) return {}, 200
def get_quiz_start(level): if not config['quiz-enabled'] and g.lang != 'nl': return 'Hedy quiz disabled!', 404 else: g.lang = lang = requested_lang() g.prefix = '/hedy' #Sets the values of total_score and correct on the beginning of the quiz at 0 session['total_score'] = 0 session['correct_answer'] = 0 return render_template('startquiz.html', level=level, next_assignment=1, menu=render_main_menu('adventures'), lang=lang, username=current_user(request)['username'], is_teacher=is_teacher(request), auth=TRANSLATIONS.get_translations( requested_lang(), 'Auth'))
def create_class(user): if not is_teacher(request): return 'Only teachers can create classes', 403 body = request.json # Validations if not isinstance(body, dict): return 'body must be an object', 400 if not isinstance(body.get('name'), str): return 'name must be a string', 400 Class = { 'id': uuid.uuid4().hex, 'date': utils.timems(), 'teacher': user['username'], 'link': utils.random_id_generator(7), 'name': body['name'] } DATABASE.store_class(Class) return {}, 200
def update_class(user, class_id): if not is_teacher(user): return 'Only teachers can update classes', 403 body = request.json # Validations if not isinstance(body, dict): return 'body must be an object', 400 if not isinstance(body.get('name'), str): return 'name must be a string', 400 Class = DATABASE.get_class(class_id) if not Class or Class['teacher'] != user['username']: return 'No such class', 404 # We use this extra call to verify if the class name doesn't already exist, if so it's a duplicate Classes = DATABASE.get_teacher_classes(user['username'], True) for Class in Classes: if Class['name'] == body['name']: return "duplicate", 200 Class = DATABASE.update_class(class_id, body['name']) return {}, 200
def update_level_preferences(user, class_id): if not is_teacher(user): return 'Only teachers can update class preferences', 403 body = request.json print(body) # Validations if not isinstance(body, dict): return 'body must be an object', 400 if not isinstance(body.get('example_programs'), bool): return 'amount of example programs must be an integer', 400 if not isinstance(body.get('hide_level'), bool): return 'level switch must be a boolean', 400 if not isinstance(body.get('hide_prev_level'), bool): return 'level switch must be a boolean', 400 if not isinstance(body.get('hide_next_level'), bool): return 'level switch must be a boolean', 400 if not isinstance(int(body.get('level')), int): return 'level must ben an integer', 400 Class = DATABASE.get_class(class_id) if not Class or Class['teacher'] != user['username']: return 'No such class', 404 customizations = {} customizations['id'] = class_id customizations['level'] = int(body.get('level')) customizations['adventures'] = body.get('adventures') customizations['example_programs'] = body.get('example_programs') customizations['hide'] = body.get('hide_level') customizations['hide_prev_level'] = body.get('hide_prev_level') customizations['hide_next_level'] = body.get('hide_next_level') Class = DATABASE.update_customizations_class(customizations) return {}, 200
def get_classes(user): if not is_teacher(request): return 'Only teachers can retrieve classes', 403 return jsonify(DATABASE.get_teacher_classes(user['username'], True))
def render_code_editor_with_tabs(request, course, level_number, menu, translations, version, loaded_program, adventures, adventure_name): if os.path.isfile( f'coursedata/quiz/quiz_questions_lvl{level_number}.yaml'): quiz_data = utils.load_yaml( f'coursedata/quiz/quiz_questions_lvl{level_number}.yaml') quiz_data_level = quiz_data['level'] else: quiz_data_level = 0 sublevel = None if isinstance(level_number, str) and re.match('\d+-\d+', level_number): sublevel = int(level_number[level_number.index('-') + 1]) level_number = int(level_number[0:level_number.index('-')]) defaults = course.get_default_text(level_number, sublevel) if not defaults: abort(404) if course.custom: adventures = [ x for x in adventures if x['short_name'] in course.adventures ] arguments_dict = {} # Meta stuff arguments_dict['course'] = course arguments_dict['level_nr'] = str(level_number) arguments_dict['sublevel'] = str(sublevel) if (sublevel) else None arguments_dict['lang'] = course.language arguments_dict['level'] = defaults.level arguments_dict['prev_level'] = int(level_number) - 1 if int( level_number) > 1 else None arguments_dict['next_level'] = int(level_number) + 1 if int( level_number) < course.max_level() else None arguments_dict['menu'] = menu arguments_dict['latest'] = version arguments_dict['selected_page'] = 'code' arguments_dict['page_title'] = f'Level {level_number} – Hedy' arguments_dict['auth'] = translations.get_translations( course.language, 'Auth') arguments_dict['username'] = current_user(request)['username'] arguments_dict['is_teacher'] = is_teacher(request) arguments_dict['loaded_program'] = loaded_program arguments_dict['adventures'] = adventures arguments_dict['adventure_name'] = adventure_name arguments_dict['quiz_data_level'] = quiz_data_level arguments_dict[ 'quiz_enabled'] = config['quiz-enabled'] and course.language == 'nl' # Translations arguments_dict.update( **translations.get_translations(course.language, 'ui')) # Actual assignment arguments_dict.update(**attr.asdict(defaults)) return render_template("code-page.html", **arguments_dict)
def get_classes(user): if not is_teacher(user): return utils.page_403(ui_message='retrieve_class') return jsonify(DATABASE.get_teacher_classes(user['username'], True))
def programs_page(request): username = current_user(request)['username'] if not username: # redirect users to /login if they are not logged in url = request.url.replace('/programs', '/login') return redirect(url, code=302) from_user = request.args.get('user') or None if from_user and not is_admin(request): if not is_teacher(request): return "unauthorized", 403 students = DATABASE.get_teacher_students(username) if from_user not in students: return "unauthorized", 403 texts = TRANSLATIONS.get_translations(requested_lang(), 'Programs') ui = TRANSLATIONS.get_translations(requested_lang(), 'ui') adventures = load_adventure_for_language(requested_lang())['adventures'] result = DATABASE.programs_for_user(from_user or username) programs = [] now = timems() for item in result: program_age = now - item['date'] if program_age < 1000 * 60 * 60: measure = texts['minutes'] date = round(program_age / (1000 * 60)) elif program_age < 1000 * 60 * 60 * 24: measure = texts['hours'] date = round(program_age / (1000 * 60 * 60)) else: measure = texts['days'] date = round(program_age / (1000 * 60 * 60 * 24)) programs.append({ 'id': item['id'], 'code': item['code'], 'date': texts['ago-1'] + ' ' + str(date) + ' ' + measure + ' ' + texts['ago-2'], 'level': item['level'], 'name': item['name'], 'adventure_name': item.get('adventure_name'), 'public': item.get('public') }) return render_template('programs.html', lang=requested_lang(), menu=render_main_menu('programs'), texts=texts, ui=ui, auth=TRANSLATIONS.get_translations( requested_lang(), 'Auth'), programs=programs, username=username, is_teacher=is_teacher(request), current_page='programs', from_user=from_user, adventures=adventures)