def create_question(): toid = request.args.get("to", "") if not toid == "": question = Question(submitter_id=current_user().id, to_id=int(toid), description=request.form["description"]) db.session.add(question) db.session.commit() socketio.emit("question_added", "question created", room="consultants", namespace="/consultants") return redirect("/questions/by/" + str(current_user().id))
def load_adventure_assignments_per_level(lang, level): loaded_programs = {} # If user is logged in, we iterate their programs that belong to the current level. Out of these, we keep the latest created program for both the level mode (no adventure) and for each of the adventures. if current_user(request)['username']: user_programs = db_get_many( 'programs', {'username': current_user(request)['username']}, True) for program in user_programs: if program['level'] != level: continue program_key = 'level' if not program.get( 'adventure_name') else program['adventure_name'] if not program_key in loaded_programs: loaded_programs[program_key] = program elif loaded_programs[program_key]['date'] < program['date']: loaded_programs[program_key] = program assignments = [] adventures = load_adventure_for_language(lang)['adventures'] for short_name, adventure in adventures.items(): if not level in adventure['levels']: continue assignments.append({ 'short_name': short_name, 'name': adventure['name'], 'image': adventure.get('image', None), 'default_save_name': adventure['default_save_name'], 'text': adventure['levels'][level].get('story_text', 'No Story Text'), 'start_code': adventure['levels'][level].get('start_code', ''), 'loaded_program': '' if not loaded_programs.get(short_name) else loaded_programs.get(short_name)['code'], 'loaded_program_name': '' if not loaded_programs.get(short_name) else loaded_programs.get(short_name)['name'] }) # We create a 'level' pseudo assignment to store the loaded program for level mode, if any. assignments.append({ 'short_name': 'level', 'loaded_program': '' if not loaded_programs.get('level') else loaded_programs.get('level')['code'], 'loaded_program_name': '' if not loaded_programs.get('level') else loaded_programs.get('level')['name'] }) return assignments
def vote(self, key): if not auth.logged_in(): return self.redirect('/projects') project = Project.get(key) if project.has_voted(auth.current_user()): project.remove_vote(auth.current_user()) project.put() else: project.vote(auth.current_user()) project.put() return self.redirect('/projects')
def claim(self, key): user = auth.User(auth.current_user()) group = user.group if group.owner.user_id() == auth.current_user().user_id(): project = Project.get(key) group.project = project group.put() return self.redirect('/groups/%s' % group.key()) else: Messages.add('You are not the owner of your group. Only the ' + 'owner of the group may select a project.') return self.redirect('/projects')
def add_torrent(): tracker_url = "%s/announce" % app.config.get('BASE_URL') f = request.files.get('torrent', None) if f is not None and f: torrent_enc = f.read() torrent_data = bdecode(torrent_enc) # print torrent_data if not 'info' in torrent_data: raise ValueError("No info in hash") torrent_data['announce'] = tracker_url if "announce-list" in torrent_data: del torrent_data['announce-list'] info_hash = hashlib.sha1(bencode(torrent_data['info'])).hexdigest() user = auth.current_user() with rc.pipeline() as p: p.set(info_hash, bencode(torrent_data)) p.hmset("info|%s" % info_hash, {'user': user, 'info_hash': info_hash}) p.sadd("perm|%s" % info_hash, 'user:%s:w' % user, 'user:%s:d' % user, 'user:%s:a' % user) p.zadd("torrents", info_hash, 1) p.zadd("torrents|seen", info_hash, time.time()) p.lpush("torrents|last", info_hash) p.ltrim("torrents|last", 0, NLAST) p.execute() return info_hash else: raise APIException("torrent file is required")
def vote(self, key): """Votes for a project. This action removes the user's vote from the project if the user had already voted for it. """ if not auth.logged_in(): return self.redirect('/projects') project = Project.get(key) if project.has_voted(auth.current_user()): project.remove_vote(auth.current_user()) project.put() else: project.vote(auth.current_user()) project.put() return self.redirect('/projects')
def signup(self): user = auth.User(auth.current_user()) if user.in_group(): Messages.add('You are already in a group') return self.redirect('/groups') else: return self.render('groups_signup')
def post(self): name = self.request.get('name') description = self.request.get('description') Idea(name=name, description=description, author=auth.current_user()).put() return self.redirect('/ideas')
def consultant_logout(): user = current_user() user.status = "offline" db.session.commit() session.pop("user", None) session.pop("last_url", None) return redirect("/consultant/login")
def render(self, template_name, data={}): """Renders the template in the site wide manner. Retrieves the template data needed for the base template (login URL and text, user information, etc.) and merges it with the data passed to the method. Templates are retrieved from the template directory specified in the settings and appended with the suffix ".html" Arguments: template_name: the name of the template. this is the file name of the template without the .html extension. data: a dictionary containing data to be passed to the template. """ (login_text, login_url) = auth.login_logout(self.request) data['uri_for'] = webapp2.uri_for data['user'] = auth.current_user() data['admin'] = auth.user_is_admin() data['login_url'] = login_url data['login_text'] = login_text template = jinja.get_template(template_name + '.html') return self.response.out.write(template.render(data))
def create(self): """Handles the POST data for creating a group. Form Variables: name: the name of the new group public: true if the group should be joinable by the public """ if auth.logged_in(): name = self.request.get('name') public = self.request.get('public') == 'public' owner = auth.current_user() if Group.exists(name): Messages.add('A group with that name already exists') return self.redirect('/groups/signup') Group(name=name, public=public, owner=owner, members=[owner]).put() return self.redirect('/groups') else: Messages.add('You must be logged in to create a group') return self.redirect('/groups/signup')
def render(self, template_name, data={}): """Renders the template in the site wide manner. Retrieves the template data needed for the base template (login URL and text, user information, etc.) and merges it with the data passed to the method. Templates are retrieved from the template directory specified in the settings and appended with the suffix ".html" Arguments: template_name: the name of the template. this is the file name of the template without the .html extension. data: a dictionary containing data to be passed to the template. """ (login_text, login_url) = auth.login_logout(self.request) if auth.logged_in(): data['user'] = auth.User(auth.current_user()) data['admin'] = auth.user_is_admin() data['login_url'] = login_url data['login_text'] = login_text data['messages'] = Messages.get() path = os.path.join(settings.BASE_DIR, settings.TEMPLATE_DIR, "%s.html" % template_name) return self.response.out.write(template.render(path, data))
def has_perm(info_hash, perm): if not auth.is_authenticated(session): return False if not rc.exists("perm|%s" % info_hash): return False return rc.sismember("perm|%s" % info_hash, "user:%s:%s" % (auth.current_user(), perm))
def create_token(*args, **kwargs): if request.method == 'GET': return render_template("new_token.html") errors = dict() data = dict(client_type='public') if not 'name' in request.form: errors['name_error'] = "Name is required" if not 'scopes' in request.form: errors['scopes_error'] = 'Select at least one scope' if errors: return render_template("new_token.html", errors=errors) user = auth.current_user() client = auth.Client.from_dict(dict(name=request.form.get('name'), user_id=user, client_type='public', redirect_uris='http://localhost', client_id="*****@*****.**" % random_string(32), client_secret=random_string(32))).save() rc.sadd("user|%s|clients" % user, client.client_id) token = auth.Token.from_dict(dict(client_id=client.client_id, user_id=client.user_id, token_type='bearer', personal=True, access_token=random_string(64), scopes=" ".join( [x.encode('ascii') for x in request.form.getlist('scopes')]))).save() print "saved token %s" % repr(token) rc.sadd("user|%s|tokens" % user, token.token_id) return redirect("/oauth/tokens")
def parse(): body = request.json if not body: return "body must be an object", 400 if 'code' not in body: return "body.code must be a string", 400 if 'level' not in body: return "body.level must be a string", 400 code = body['code'] level = int(body['level']) # Language should come principally from the request body, # but we'll fall back to browser default if it's missing for whatever # reason. lang = body.get('lang', requested_lang()) # For debugging print(f"got code {code}") response = {} username = current_user(request)['username'] or None # Check if user sent code if not code: response["Error"] = "no code found, please send code." # is so, parse else: try: hedy_errors = TRANSLATIONS.get_translations( lang, 'HedyErrorMessages') result = hedy.transpile(code, level) response["Code"] = "# coding=utf8\n" + result except hedy.HedyException as E: # some 'errors' can be fixed, for these we throw an exception, but also # return fixed code, so it can be ran if E.args[0] == "Invalid Space": error_template = hedy_errors[E.error_code] response[ "Code"] = "# coding=utf8\n" + E.arguments['fixed_code'] response["Warning"] = error_template.format(**E.arguments) else: error_template = hedy_errors[E.error_code] response["Error"] = error_template.format(**E.arguments) except Exception as E: print(f"error transpiling {code}") response["Error"] = str(E) logger.log({ 'session': session_id(), 'date': str(datetime.datetime.now()), 'level': level, 'lang': lang, 'code': code, 'server_error': response.get('Error'), 'version': version(), 'username': username }) return jsonify(response)
def redirect_ab (request, session): # If the user is logged in, we use their username as identifier, otherwise we use the session id user_identifier = current_user(request) ['username'] or str (session_id ()) # This will send 50% of the requests into redirect. redirect_proportion = 50 redirect_flag = (hash_user_or_session (user_identifier) % 100) < redirect_proportion return redirect_flag
def join(self, key): group = Group.get(key) user = auth.User(auth.current_user()) if user.in_group(): Messages.add('You are already in a group') return self.redirect('/groups/%s' % key) if user.pending_join(): Messages.add('You have already applied to join a group') return self.redirect('/groups/%s' % key) if group.public: group.members.append(auth.current_user()) Messages.add('You have joined the group') else: group.pending_users.append(auth.current_user()) Messages.add('You have requested to join the group') group.put() return self.redirect('/groups/%s' % key)
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'], auth=TRANSLATIONS.data[lang]['Auth'])
def remove_token(token_id): if token_id is None: abort(400) rc.srem("user|%s|tokens" % auth.current_user(), token_id) token = auth.get_token(token_id) if token is not None: token.delete() return redirect("/oauth/tokens")
def remove_client(client_id): if client_id is None: abort(400) rc.srem("user|%s|clients" % auth.current_user(), client_id) client = auth.get_client(client_id) if client is not None: client.delete() return redirect("/oauth/clients")
def edit_question(qid): question = Question.query.get(qid) if request.method == "GET": return render_template("user/questioning.html", question=question, to=question.to) else: question.description = request.form["description"] db.session.commit() return redirect("/questions/by/" + str(current_user().id))
def update_group(self, key): if not auth.logged_in(): return self.redirect('/groups') user = auth.current_user() group = Group.get(key) if group.owner.user_id() != user.user_id() and not auth.user_is_admin(): Messages.add('Only the owner of the group owner may modify it') return self.redirect('/groups') name = self.request.get('name') public = self.request.get('public') == 'public' abandon = self.request.get('abandon-project') sub_text = self.request.get('submission-text') sub_url = self.request.get('submission-url') remove_submission = self.request.get_all('remove-submission') remove = self.request.get_all('remove') owner = self.request.get('owner') delete = self.request.get('delete') if delete: group.delete() return self.redirect('/groups') group.name = name group.public = public if abandon: group.project = None if sub_text and sub_url: Submission(text=sub_text, url=sub_url, group=group).put() for sub in Submission.get(remove_submission): sub.delete() pending = list(group.pending_users) for user in pending: approve = self.request.get("approve-%s" % user) if approve == "approve": group.members.append(user) group.pending_users.remove(user) elif approve == "refuse": group.pending_users.remove(user) group.owner = auth.user_from_email(owner) for user in remove: if auth.user_from_email(user) == group.owner: Messages.add('Cannot remove the group owner') return self.redirect('/groups/%s/edit' % key) else: group.members.remove(auth.user_from_email(user)) group.put() return self.redirect('/groups/%s' % key)
def leave(self, key): group = Group.get(key) user = auth.User(auth.current_user()) if user.group != group: Messages.add('You cannot leave a group you are not in') return self.redirect('/groups/%s' % key) group.members.remove(user.gae_user) group.put() return self.redirect('/groups')
def edit(self, key): if not auth.logged_in(): return self.redirect('/groups') user = auth.current_user() group = Group.get(key) if group.owner.user_id() == user.user_id() or auth.user_is_admin(): return self.render('groups_edit', { 'group': group }) else: Messages.add('Only the owner of this group may edit it') return self.redirect('/groups/%s' % key)
def answer_for(qid): question = Question.query.get(qid) if request.method == "GET": return render_template("consultant/answering.html", question=question) else: if question.answer == None: answer = Answer(question_id=qid, by_id=current_user().id, description=request.form["answer"]) db.session.add(answer) db.session.commit() else: question.answer.description = request.form["answer"] db.session.commit() return redirect("/questions?status=no")
def adventure_page(adventure_name, level): user = current_user (request) level = int (level) adventures = load_adventure_for_language (requested_lang ()) # If requested adventure does not exist, return 404 if not adventure_name in adventures ['adventures']: return 'No such Hedy adventure!', 404 adventure = adventures ['adventures'] [adventure_name] # If no level is specified (this will happen if the last element of the path (minus the query parameter) is the same as the adventure_name) if re.sub (r'\?.+', '', request.url.split ('/') [len (request.url.split ('/')) - 1]) == adventure_name: # If user is logged in, check if they have a program for this adventure # If there are many, note the highest level for which there is a saved program desired_level = 0 if user ['username']: existing_programs = db_get_many ('programs', {'username': user ['username']}, True) for program in existing_programs: if 'adventure_name' in program and program ['adventure_name'] == adventure_name and program ['level'] > desired_level: desired_level = program ['level'] # If the user has a saved program for this adventure, redirect them to the level with the highest adventure if desired_level != 0: return redirect(request.url.replace ('/' + adventure_name, '/' + adventure_name + '/' + str (desired_level)), code=302) # If user is not logged in, or has no saved programs for this adventure, default to the lowest level available for the adventure if desired_level == 0: for key in adventure ['levels'].keys (): if type_check (key, 'int') and (desired_level == 0 or desired_level > key): desired_level = key level = desired_level # If requested level is not in adventure, return 404 if not level in adventure ['levels']: abort(404) adventure_assignments = load_adventure_assignments_per_level(requested_lang(), level) g.prefix = '/hedy' return hedyweb.render_assignment_editor( request=request, course=HEDY_COURSE[requested_lang()], level_number=level, assignment_number=1, menu=render_main_menu('hedy'), translations=TRANSLATIONS, version=version(), adventure_assignments=adventure_assignments, # The relevant loaded program will be available to client-side js and it will be loaded by js. loaded_program='', loaded_program_name='', adventure_name=adventure_name)
def report_error(): post_body = request.json logger.log({ 'session': session_id(), 'date': str(datetime.datetime.now()), 'level': post_body.get('level'), 'code': post_body.get('code'), 'client_error': post_body.get('client_error'), 'version': version(), 'username': current_user(request) ['username'] or None }) return 'logged'
def render_adventure(adventure_name, adventure, course, request, lang, level_number, menu, translations, version, loaded_program): arguments_dict = {} arguments_dict['lang'] = lang arguments_dict['level_nr'] = str(level_number) arguments_dict['level'] = level_number arguments_dict[ 'prev_level'] = level_number - 1 if level_number - 1 in adventure[ 'levels'] else None arguments_dict[ 'next_level'] = level_number + 1 if level_number + 1 in adventure[ 'levels'] else None arguments_dict['menu'] = menu arguments_dict['latest'] = version arguments_dict['selected_page'] = 'code' arguments_dict[ 'page_title'] = f'Adventure mode: {adventure ["name"]} {level_number} – Hedy' arguments_dict['auth'] = (translations.data.get(lang) or translations.data['en'])['Auth'] arguments_dict['username'] = current_user(request)['username'] arguments_dict['loaded_program'] = loaded_program arguments_dict['adventure_name'] = adventure_name arguments_dict['full_adventure_name'] = adventure['name'] # Translations arguments_dict.update(**translations.get_translations(lang, 'ui')) # Actual assignment for key, value in adventure['levels'][level_number].items(): arguments_dict[key] = value if not 'story_text' in arguments_dict: arguments_dict['story_text'] = '' if not 'story_commands' in arguments_dict: arguments_dict['story_commands'] = [] # We use the intro_text and commands from the corresponding Hedy level, if they are not present in the adventure hedy_course = course.get_assignment(level_number, None) if not 'intro_text' in arguments_dict: arguments_dict['intro_text'] = hedy_course.intro_text if not 'commands' in arguments_dict: arguments_dict['commands'] = hedy_course.commands return render_template("code-page.html", **arguments_dict)
def ideas(request): if request.method == "GET": (login_text, login_url) = auth.login_logout(request) idea_list = Idea.objects.all().order_by("-post_time") return basic_response( "apps/ideas.html", request, {"admin": auth.user_is_admin(), "ideas": idea_list, "login_url": login_url, "login_text": login_text}, ) elif request.method == "POST": idea = Idea() idea.name = request.POST["name"] idea.description = request.POST["description"] idea.author = auth.current_user() idea.save() return HttpResponseRedirect(reverse("apps.views.ideas")) else: raise Http404
def home(): user = auth.current_user() return flask.render_template('index.html', username=user.username, #password=user.password, )
def adventure_page(adventure_name, level): user = current_user(request) level = int(level) adventures = load_adventure_for_language(requested_lang()) # If requested adventure does not exist, return 404 if not adventure_name in adventures['adventures']: return 'No such Hedy adventure!', 404 adventure = adventures['adventures'][adventure_name] loaded_program = '' # If no level is specified (take last item of path and remove query parameter, if any, then compare to adventure_name) if re.sub(r'\?.+', '', request.url.split('/')[len(request.url.split('/')) - 1]) == adventure_name: # If user is logged in, check if they have a program for this adventure # If there are many, note the highest level for which there is a saved program desired_level = 0 if user['username']: existing_programs = db_get_many('programs', {'username': user['username']}, True) for program in existing_programs: if 'adventure_name' in program and program[ 'adventure_name'] == adventure_name and program[ 'level'] > desired_level: desired_level = program['level'] # If the user has a saved program for this adventure, redirect them to the level with the highest adventure if desired_level != 0: return redirect(request.url.replace( '/' + adventure_name, '/' + adventure_name + '/' + str(desired_level)), code=302) # If user is not logged in, or has no saved programs for this adventure, default to the lowest level available for the adventure if desired_level == 0: for key in adventure['levels'].keys(): if type_check(key, 'int') and (desired_level == 0 or desired_level > key): desired_level = key level = desired_level # If a level is specified and user is logged in, check if there's a stored program available for this level elif user['username']: existing_programs = db_get_many('programs', {'username': user['username']}, True) for program in existing_programs: if 'adventure_name' in program and program[ 'adventure_name'] == adventure_name and program[ 'level'] == level: loaded_program = program['code'] # If requested level is not in adventure, return 404 if not level in adventure['levels']: abort(404) return hedyweb.render_adventure(adventure_name=adventure_name, adventure=adventure, course=HEDY_COURSE[requested_lang()], request=request, lang=requested_lang(), level_number=level, menu=render_main_menu('hedy'), translations=TRANSLATIONS, version=version(), loaded_program=loaded_program)
def update_group(self, key): """Updates a group with information from the moderation form. Form Variables: name: the name of the group public: true if the group should be joinable by the public abandon-project: true if the group moderator wants to abandon the current project submission-text: the text to be displayed for the new submission submission-url: the URL of the new submission remove-submission: a list of submissions to be removed remove: a list of users to be removed from the group owner: the owner of the group delete: true if the group moderator wants to disband the group """ if not auth.logged_in(): return self.redirect('/groups') user = auth.current_user() group = Group.get(key) if (group.owner.user_id() != user.user_id() and not auth.user_is_admin()): Messages.add('Only the owner of the group owner may modify it') return self.redirect('/groups') name = self.request.get('name') public = self.request.get('public') == 'public' abandon = self.request.get('abandon-project') sub_text = self.request.get('submission-text') sub_url = self.request.get('submission-url') remove_submission = self.request.get_all('remove-submission') remove = self.request.get_all('remove') owner = self.request.get('owner') delete = self.request.get('delete') if delete: group.delete() return self.redirect('/groups') group.name = name group.public = public if abandon: group.project = None if sub_text and sub_url: Submission(text=sub_text, url=sub_url, group=group).put() for sub in Submission.get(remove_submission): sub.delete() pending = list(group.pending_users) for user in pending: approve = self.request.get("approve-%s" % user) if approve == "approve": group.members.append(user) group.pending_users.remove(user) elif approve == "refuse": group.pending_users.remove(user) group.owner = auth.user_from_email(owner) for user in remove: if auth.user_from_email(user) == group.owner: Messages.add('Cannot remove the group owner') return self.redirect('/groups/%s/edit' % key) else: group.members.remove(auth.user_from_email(user)) group.put() return self.redirect('/groups/%s' % key)
def parse(): body = request.json if not body: return "body must be an object", 400 if 'code' not in body: return "body.code must be a string", 400 if 'level' not in body: return "body.level must be a string", 400 if 'adventure_name' in body and not type_check(body['adventure_name'], 'str'): return "if present, body.adventure_name must be a string", 400 code = body['code'] level = int(body['level']) # Language should come principally from the request body, # but we'll fall back to browser default if it's missing for whatever # reason. lang = body.get('lang', requested_lang()) querylog.log_value(level=level, lang=lang) response = {} username = current_user(request)['username'] or None # Check if user sent code if not code: response["Error"] = "no code found, please send code." # is so, parse else: try: hedy_errors = TRANSLATIONS.get_translations( lang, 'HedyErrorMessages') with querylog.log_time('transpile'): result = hedy.transpile(code, level) response["Code"] = "# coding=utf8\nimport random\n" + result except hedy.HedyException as E: traceback.print_exc() # some 'errors' can be fixed, for these we throw an exception, but also # return fixed code, so it can be ran if E.args[0] == "Invalid Space": error_template = hedy_errors[E.error_code] response[ "Code"] = "# coding=utf8\n" + E.arguments['fixed_code'] response["Warning"] = error_template.format(**E.arguments) elif E.args[0] == "Parse": error_template = hedy_errors[E.error_code] # Localize the names of characters if 'character_found' in E.arguments: E.arguments['character_found'] = hedy_errors[ E.arguments['character_found']] response["Error"] = error_template.format(**E.arguments) elif E.args[0] == "Unquoted Text": error_template = hedy_errors[E.error_code] response["Error"] = error_template.format(**E.arguments) else: error_template = hedy_errors[E.error_code] response["Error"] = error_template.format(**E.arguments) except Exception as E: traceback.print_exc() print(f"error transpiling {code}") response["Error"] = str(E) logger.log({ 'session': session_id(), 'date': str(datetime.datetime.now()), 'level': level, 'lang': lang, 'code': code, 'server_error': response.get('Error'), 'version': version(), 'username': username, 'is_test': 1 if os.getenv('IS_TEST_ENV') else None, 'adventure_name': body.get('adventure_name', None) }) return jsonify(response)
def user_offline(): #user = User.query.get(session['user']['uid']) user = current_user(); user.status = 'offline' db.session.commit() return 'offline'
def user(): print(str(auth.current_user())) return str(auth.current_user())
f = request.files.get('file', None) if f is not None and f: try: img = Image.open(f, f.content_type) buf = StringIO.StringIO() img.save(buf, "PNG") image_b64 = buf.read().encode('base64') data['icon'] = image_b64 except Exception, ex: traceback.print_exc() errors['file_error'] = "Unknown file format: %s" % ex if errors: return render_template('new_client.html', **errors) user = auth.current_user() data.update(dict(name=request.form.get('name'), user_id=user, redirect_uris=" ".join([x.encode('ascii') for x in request.form.getlist('request_uris')]), client_id="*****@*****.**" % random_string(32), client_secret=random_string(32))) client = auth.Client.from_dict(data).save() rc.sadd("user|%s|clients" % user, client.client_id) return redirect("/oauth/clients") @app.route('/oauth/clients', methods=['GET']) @auth.requires_auth def list_client():
def list_tokens(): tokens = [auth.get_token(token_id) for token_id in rc.smembers("user|%s|tokens" % auth.current_user()) if token_id is not None] return render_template("tokens.html", tokens=tokens)
def save_grant(client_id, code, r, *args, **kwargs): if not auth.is_authenticated(session): abort(400) return auth.save_grant(auth.current_user(), client_id, code, r, args, kwargs)
def before_request(): """ 每次请求之前把g.user设为当前用户 """ g.user = current_user()
def user_logout(): current_user().status = 'offline' db.session.commit() session.pop('user', None) session.pop('last_url', None) return redirect('/user/login')
def answer_by(): uid = current_user().id questions = Question.query.join(Answer).filter(Answer.by_id == uid).all() return render_template("consultant/consultant_questionlist.html", questions=questions)