From 0b7faf878487f27c9a9441f86a8d4d07e0588914 Mon Sep 17 00:00:00 2001 From: asauer Date: Mon, 15 Apr 2019 00:52:37 +0200 Subject: [PATCH] Finish script cleanup: PEP8, PEP257 --- gruppenbildungsspiel.py | 337 ++++++++++++++++++++++++++++++---------- 1 file changed, 255 insertions(+), 82 deletions(-) diff --git a/gruppenbildungsspiel.py b/gruppenbildungsspiel.py index 75b692a..f5aa02f 100644 --- a/gruppenbildungsspiel.py +++ b/gruppenbildungsspiel.py @@ -1,71 +1,202 @@ -from flask import Flask, render_template, request, redirect, url_for -from wtforms import Form, StringField, SelectField, RadioField, TextAreaField, BooleanField, validators +"""A script for the Sortierhut Game + +Based on the Flask and WTForms web frameworks. +""" + +__version__ = '0.0' +__author__ = 'aamma, asauer' + import uuid import csv import os +from flask import Flask, render_template, request, redirect, url_for +from wtforms import Form, StringField, SelectField, RadioField, TextAreaField, BooleanField, validators + app = Flask(__name__) app.jinja_env.trim_blocks = True app.jinja_env.lstrip_blocks = True -datafile = "data.csv" -solutionfile = "solutions.csv" +datafile = 'data.csv' +solutionfile = 'solutions.csv' + + +def create_used_id_list(): + """Read datafile and create a list of the id in each row. + + Useful to compare if a given id is already used. + """ + data = list() + if not os.path.exists(datafile): + open(datafile, 'a').close() + + with open(datafile, 'r') as f: + reader = csv.reader(f) + + for line in reader: + # line[0] is the row's id + data.append(line[0]) + + return data + + +def convert_csv_to_list_of_lists(csv_file): + """Convert a csv file to a list whose items are lists. + + csv_file -- a valid comma-separated file. + + Useful because a list of lists can be easily iterated. + """ + list_of_lists = list() + + if not os.path.exists(csv_file): + open(csv_file, 'a').close() + + with open(csv_file, 'r', newline='') as f: + reader = csv.reader(f) + + for line in reader: + list_of_lists.append(line) + + return list_of_lists + def check_input(form, field): + """Check if a SelectField from the questions() is still in default mode. + + form -- the form on which this function is called (instance of QuestionForm). + field -- the form field whose input is checked. + """ if field.data == 'default': raise validators.ValidationError('Bitte wähle eine Option aus') -class QuestionForm(Form): - computers = [('default', 'Bitte auswählen'), ('hal', 'HAL 9000 (2001: Odyssee im Weltraum)'), ('deepthought', 'Deep Thought (Hitchhiker\'s Guide)'), ('neuromancer', 'Neuromancer (Neuromancer)'), ('samantha', 'Samatha (Her)'), ('tars', 'TARS und CASE (Interstellar)')] - computer = SelectField(u'Fiktionaler Lieblingscomputer', choices=computers, validators=[validators.InputRequired(), check_input]) - statuses = [('default', 'Bitte auswählen'), ('200', '200 OK'), ('300', '300 Multiple Choices'), ('301', '301 Moved Permanently'), ('402', '402 Payment Required'), ('404', '404 (Sleep) Not Found'), ('408', '408 Request Timeout'), ('418', '418 I am a teapot'), ('450', '450 Blocked by Windows Parental Controls'), ('451', '451 Unavailable For Legal Reasons'), ('502', '502 Bad Gateway (Internetkurort)')] - status = SelectField(u'Welcher HTTP-Statuscode trifft am häufigsten auf dich zu?', choices=statuses, validators=[validators.InputRequired(), check_input]) - vegetables = [('default', 'Bitte auswählen'), ('karotte', '🥕'), ('broccoli','🥦'), ('aubergine', '🍆'), ('kartoffel', '🥔'), ('bretzel', '🥨'), ('tomate', '🍅'), ('chili', '🌶️')] - vegetable = SelectField(u'Lieblingsgemüse', choices=vegetables, validators=[validators.InputRequired(), check_input]) - spirit_animals = [('default', 'Bitte auswählen'), ('feuer', 'Feuerfuchs'), ('wasser', 'Wasserhahn'), ('erde', 'Erdferkel'), ('luft', 'Luftschlange')] - spirit_animal = SelectField(u'Spirit Animal', choices=spirit_animals, validators=[validators.InputRequired(), check_input]) - operating_systems = [('default', 'Bitte auswählen'), ('windows', 'Windows'), ('mac', 'Mac'), ('linux', 'Linux'), ('kaffee', 'Kaffee'), ('glados', 'GLaDOS')] - operating_system = SelectField(u'Bevorzugtes Betriebssystem', choices=operating_systems, validators=[validators.InputRequired(), check_input]) - check = BooleanField('Bitte nicht ankreuzen') - - -class SolutionForm(Form): - user_id = StringField('id', [validators.InputRequired()]) - choices = [('computer', 'Fiktionaler Lieblingscomputer'), ('status', 'HTTP-Statuscode'), ('vegetable', 'Lieblingsgemüse'), ('spirit_animal', 'Spirit Animal'), ('operating system', 'Betriebssystem'), ('check', 'Bitte nicht ankreuzen')] - question = RadioField(u'Welche Frage ist ausschlaggebend für die Hauszuordnung?', choices=choices, validators=[validators.InputRequired(message='Bitte ankreuzen'),]) - solution_text = TextAreaField(u'Textfeld') - -def create_used_id_list(): - data = list() - if not os.path.exists(datafile): - open(datafile, "a").close() - - with open(datafile, "r") as f: - reader = csv.reader(f) - for line in reader: - data.append(line[0]) - return data - def check_id(form, field): + """Check if the datafile contains a given id. + + form -- the form on which this function is called (instance of AnswerForm). + field -- the id input field whose input is checked. + """ used_ids = create_used_id_list() if field.data not in used_ids: raise validators.ValidationError('Leider keine gültige ID') + +class QuestionForm(Form): + """A form of humorous questions.""" + computers = [ + ('default', 'Bitte auswählen'), + ('hal', 'HAL 9000 (2001: Odyssee im Weltraum)'), + ('deepthought', 'Deep Thought (Hitchhiker\'s Guide)'), + ('neuromancer', 'Neuromancer (Neuromancer)'), + ('samantha', 'Samatha (Her)'), + ('tars', 'TARS und CASE (Interstellar)') + ] + statuses = [ + ('default', 'Bitte auswählen'), + ('200', '200 OK'), + ('300', '300 Multiple Choices'), + ('301', '301 Moved Permanently'), + ('402', '402 Payment Required'), + ('404', '404 (Sleep) Not Found'), + ('408', '408 Request Timeout'), + ('418', '418 I am a teapot'), + ('450', '450 Blocked by Windows Parental Controls'), + ('451', '451 Unavailable For Legal Reasons'), + ('502', '502 Bad Gateway (Internetkurort)') + ] + vegetables = [ + ('default', 'Bitte auswählen'), + ('karotte', '🥕 Karotte'), + ('broccoli','🥦 Brokkoli'), + ('aubergine', '🍆 Aubergine'), + ('kartoffel', '🥔 Kartoffel'), + ('bretzel', '🥨 Bretzel'), + ('tomate', '🍅 Tomate'), + ('chili', '🌶️ Chili') + ] + spirit_animals = [ + ('default', 'Bitte auswählen'), + ('feuer', 'Feuerfuchs'), + ('wasser', 'Wasserhahn'), + ('erde', 'Erdferkel'), + ('luft', 'Luftschlange') + ] + operating_systems = [ + ('default', 'Bitte auswählen'), + ('windows', 'Windows'), + ('mac', 'Mac'), + ('linux', 'Linux'), + ('kaffee', 'Kaffee'), + ('glados', 'GLaDOS') + ] + + computer = SelectField(u'Fiktionaler Lieblingscomputer', choices=computers, + validators=[validators.InputRequired(), check_input]) + status = SelectField(u'Welcher HTTP-Statuscode trifft am häufigsten auf dich zu?', + choices=statuses, + validators=[validators.InputRequired(), check_input]) + vegetable = SelectField(u'Lieblingsgemüse', choices=vegetables, + validators=[validators.InputRequired(), check_input]) + spirit_animal = SelectField(u'Spirit Animal', choices=spirit_animals, + validators=[validators.InputRequired(), check_input]) + operating_system = SelectField(u'Bevorzugtes Betriebssystem', choices=operating_systems, + validators=[validators.InputRequired(), check_input]) + check = BooleanField('Bitte nicht ankreuzen') + + class AnswerForm(Form): + """A form for a user_id as a sort of password.""" user_id = StringField('Id', [validators.InputRequired(), check_id]) +class SolutionForm(Form): + """A form for a solution idea for the Sortierhut riddle.""" + choices = [ + ('computer', 'Fiktionaler Lieblingscomputer'), + ('status', 'HTTP-Statuscode'), + ('vegetable', 'Lieblingsgemüse'), + ('spirit_animal', 'Spirit Animal'), + ('operating system', 'Betriebssystem'), + ('check', 'Bitte nicht ankreuzen') + ] + + user_id = StringField('id', [validators.InputRequired(),]) + question = RadioField(choices=choices, validators=[validators.InputRequired(),]) + solution_text = TextAreaField(u'Textfeld') + @app.route('/') def index(): + """Display a home page with an explanation and many navigation links.""" return render_template('index.html', title='Sortierhut') + @app.route('/questions', methods=['GET', 'POST']) def questions(): + """Display a form with some questions. + + Raise ValidationError and stay on questions() page + if any of the dropdown fields are still in the default position on + "Bitte auswählen". + + Else redirect to result(). + """ + def generate_id(): + """Generate a new unique user id.""" + data = create_used_id_list() + # best loop ever + while True: + new_id = str(uuid.uuid1()).split('-')[0] + if new_id in data: + break + else: + return new_id + form = QuestionForm(request.form) + # Assign a number to each house houses = dict() houses[0] = 'knuth' houses[1] = 'lovelace' @@ -74,97 +205,139 @@ def questions(): houses[8] = 'lamarr' if request.method == 'POST' and form.validate(): - with open(datafile, "a", newline='') as f: + with open(datafile, 'a', newline='') as f: new_row = list() new_id = generate_id() new_row.append(new_id) + for item in request.form: new_row.append(request.form[item]) + # If the BooleanField is not checked, + # the request.form dict does not contain an entry for 'check' if len(new_row) == 6: new_row.append('n') - + + # This is the riddle's kern: + # Compute the mod 10 of the selected http status code + # and this then determines the sorting magical_house_number = int(new_row[2]) % 10 new_row.append(houses[magical_house_number]) + writer = csv.writer(f) writer.writerow(new_row) return redirect(url_for('result', user_id=new_id)) + return render_template('questions.html', form=form, title='Sortierhut') -@app.route('/submit_solution', methods=['GET', 'POST']) -def submit_solution(): - form = SolutionForm(request.form) - if request.method == 'POST' and form.validate(): - with open(solutionfile, "a", newline='') as f: - solution_list = list() - for item in request.form: - solution_list.append(request.form[item]) - writer = csv.writer(f) - writer.writerow(solution_list) - return redirect(url_for('thanks')) - return render_template('submit_solution.html', form=form, title='Sortierhut') @app.route('/result') def result(user_id): + """Display the user's id and a comment on what to do next. + + user_id -- argument passed by redirect call in the questions() function. + + Only called in questions(). + """ return render_template('result.html', title='Sortierhut', id=user_id) -def convert_csv_to_list_of_lists(csv_file): - list_of_lists = list() - - if not os.path.exists(csv_file): - open(csv_file, 'a').close() - - with open(csv_file, 'r', newline='') as f: - reader = csv.reader(f) - for line in reader: - list_of_lists.append(line) - return list_of_lists - -@app.route('/admint_form') -def admin_form(): - file_data = convert_csv_to_list_of_lists(datafile) - return render_template('admin_form.html', title='Sortierhut', text='Hallo, Admin Fooboar', file_data=file_data) - -@app.route('/admint_solution') -def admin_solution(): - file_data = convert_csv_to_list_of_lists(solutionfile) - return render_template('admin_solution.html', title='Sortierhut', file_data=file_data) @app.route('/answers', methods=['GET', 'POST']) def access_answers(): + """Display a form for an id that is needed to access the corresponded answers. + + Raise ValidationError and stay on access_answers() page + if no id is submitted or the id is not valid. + + Else redirect to view_answers() page. + """ form = AnswerForm(request.form) + if request.method == 'POST' and form.validate(): ident = request.form['user_id'] - return redirect(url_for('view_access_answers', user_id=ident)) + return redirect(url_for('view_answers', user_id=ident)) + return render_template('access_answers.html', form=form, title='Sortierhut') + @app.route('/view_answers') def view_answers(user_id): + """Display the user's answers. + + user_id -- argument passed by the redirect call in the access_answers() function. + + Only called in access_answers(). + """ + # Create list object to be filled with the datafile row matching the user_id user_row = list() - with open(datafile, "r", newline='') as f: + + with open(datafile, 'r', newline='') as f: reader = csv.reader(f) + for line in reader: + # line[0] is the row's id if line[0] == user_id: user_row = line break - return render_template('view_answers.html', title='Sortierhut', user_id=user_id, user_row=user_row) + + return render_template('view_answers.html', title='Sortierhut', + user_id=user_id, user_row=user_row) + + +@app.route('/submit_solution', methods=['GET', 'POST']) +def submit_solution(): + """Display a form for solution ideas. + + Raise ValidationError and stay on submit_solution() page + if the form input is not valid. + + Else redirect to thanks() page. + """ + form = SolutionForm(request.form) + + if request.method == 'POST' and form.validate(): + with open(solutionfile, 'a', newline='') as f: + solution_list = list() + + # Transfer input data from request.form dict to solution_list list + for item in request.form: + solution_list.append(request.form[item]) + + writer = csv.writer(f) + writer.writerow(solution_list) + + return redirect(url_for('thanks')) + + return render_template('submit_solution.html', form=form, title='Sortierhut') + @app.route('/thanks') def thanks(): + """Display how the right solution will be announced. + + Only called in submit_solution(). + """ return render_template('thanks.html', title='Sortierhut') + @app.route('/houses') def houses(): + """Display a description of the 5 groups (=houses).""" return render_template('houses.html', title='Sortierhut') -def generate_id(): - data = create_used_id_list() - # best loop ever - while True: - new_id = str(uuid.uuid1()).split("-")[0] - if new_id in data: - break - else: - return new_id +@app.route('/admint_form') +def admin_form(): + """Display a table of the data from submitted question form.""" + file_data = convert_csv_to_list_of_lists(datafile) + return render_template('admin_form.html', title='Sortierhut', + text='Hallo, Admin Fooboar', file_data=file_data) + + +@app.route('/admint_solution') +def admin_solution(): + """Display a table of the submitted solutions.""" + file_data = convert_csv_to_list_of_lists(solutionfile) + return render_template('admin_solution.html', title='Sortierhut', file_data=file_data) +