├── .gitignore ├── LICENSE ├── README.md ├── app.py ├── guess.py ├── requirements.txt └── templates ├── error.html ├── guess.html ├── index.html ├── learn.html └── question.html /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # PyInstaller 26 | # Usually these files are written by a python script from a template 27 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 28 | *.manifest 29 | *.spec 30 | 31 | # Installer logs 32 | pip-log.txt 33 | pip-delete-this-directory.txt 34 | 35 | # Unit test / coverage reports 36 | htmlcov/ 37 | .tox/ 38 | .coverage 39 | .cache 40 | nosetests.xml 41 | coverage.xml 42 | 43 | # Translations 44 | *.mo 45 | *.pot 46 | 47 | # Django stuff: 48 | *.log 49 | 50 | # Sphinx documentation 51 | docs/_build/ 52 | 53 | # PyBuilder 54 | target/ 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Miguel Grinberg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flask-pycon2015 2 | 3 | This repository contains the code for my Python 2015 tutorial "Flask Workshop". 4 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from flask import Flask, render_template, redirect, url_for, session 4 | from flask_wtf import Form 5 | from wtforms.fields import RadioField, StringField, SubmitField 6 | from wtforms.validators import Required 7 | from guess import Guess, GuessError 8 | 9 | app = Flask(__name__) 10 | app.config['SECRET_KEY'] = 'secret!' 11 | game = Guess('Python') 12 | game.expand('Python', 'C++', 'Is it interpreted?', False) 13 | game.expand('C++', 'Java', 'Does it run on a VM?', True) 14 | 15 | 16 | class YesNoQuestionForm(Form): 17 | answer = RadioField('Your answer', choices=[('yes', 'Yes'), ('no', 'No')]) 18 | submit = SubmitField('Submit') 19 | 20 | 21 | class LearnForm(Form): 22 | language = StringField('What language did you pick?', 23 | validators=[Required()]) 24 | question = StringField('What is a question that differentiates your ' 25 | 'language from mine?', validators=[Required()]) 26 | answer = RadioField('What is the answer for your language?', 27 | choices=[('yes', 'Yes'), ('no', 'No')]) 28 | submit = SubmitField('Submit') 29 | 30 | 31 | @app.route('/') 32 | def index(): 33 | session['question'] = 0 34 | return render_template('index.html') 35 | 36 | 37 | @app.route('/question', methods=['GET', 'POST']) 38 | def question(): 39 | if 'question' not in session: 40 | # if we have no question in the session we go to the start page 41 | return redirect(url_for('index')) 42 | 43 | id = session['question'] 44 | question = game.get_question(id) 45 | if question is None: 46 | # if there is no question, then we have reached the end of the game, 47 | # so we redirect to the guess page 48 | return redirect(url_for('guess')) 49 | 50 | form = YesNoQuestionForm() 51 | if form.validate_on_submit(): 52 | # the user answered the question, advance to the next question 53 | session['question'] = game.answer_question(form.answer.data == 'yes', 54 | id) 55 | return redirect(url_for('question')) 56 | 57 | # present the question to the user 58 | return render_template('question.html', question=question, form=form) 59 | 60 | 61 | @app.route('/guess', methods=['GET', 'POST']) 62 | def guess(): 63 | if 'question' not in session: 64 | # if we have no question in the session we go to the start page 65 | return redirect(url_for('index')) 66 | 67 | id = session['question'] 68 | guess = game.get_guess(id) 69 | if guess is None: 70 | # we don't have a guess, we shouldn't be here 71 | return redirect(url_for('index')) 72 | 73 | form = YesNoQuestionForm() 74 | if form.validate_on_submit(): 75 | if form.answer.data == 'yes': 76 | # the language was guessed correctly, game over 77 | return redirect(url_for('index')) 78 | 79 | # ask the user to expand the game with a new question 80 | return redirect(url_for('learn')) 81 | 82 | # present the guess to the user 83 | return render_template('guess.html', guess=guess, form=form) 84 | 85 | 86 | @app.route('/learn', methods=['GET', 'POST']) 87 | def learn(): 88 | if 'question' not in session: 89 | # if we have no question in the session we go to the start page 90 | return redirect(url_for('index')) 91 | 92 | id = session['question'] 93 | guess = game.get_guess(id) 94 | if guess is None: 95 | # we don't have a guess, we shouldn't be here 96 | return redirect(url_for('index')) 97 | 98 | form = LearnForm() 99 | if form.validate_on_submit(): 100 | game.expand(guess, form.language.data, form.question.data, 101 | form.answer.data == 'yes') 102 | return redirect(url_for('index')) 103 | return render_template('learn.html', guess=guess, form=form) 104 | 105 | 106 | @app.errorhandler(GuessError) 107 | @app.errorhandler(404) 108 | def runtime_error(e): 109 | return render_template('error.html', error=str(e)) 110 | 111 | 112 | if __name__ == '__main__': 113 | app.run(host='0.0.0.0', port=5000, debug=False) 114 | -------------------------------------------------------------------------------- /guess.py: -------------------------------------------------------------------------------- 1 | class GuessError(RuntimeError): 2 | """Custom exception for game errors.""" 3 | pass 4 | 5 | 6 | class Guess: 7 | def __init__(self, initial_guess): 8 | self.data = [{'guess': initial_guess}] 9 | 10 | def expand(self, old_guess, new_guess, question, answer_for_new): 11 | """Add a new guess to the game. 12 | 13 | Parameters: 14 | old_guess (str): The existing guess that is being expanded. 15 | new_guess (str): The new guess added to the game. 16 | question (str): A question to separate old_guess from new_guess. 17 | answer_for_new (bool): The answer to the question for new_guess. 18 | 19 | Raises: 20 | GuessError: Input is invalid. 21 | 22 | Returns: 23 | None 24 | 25 | Example: 26 | The example below sets up a "Guess the Programming Language" game 27 | with Python, C++, Java and Ruby: 28 | 29 | game = Guess('Python') 30 | game.expand('Python', 'C++', 'Is it interpreted?', True) 31 | game.expand('C++', 'Java', 'Does it run on a VM?', True) 32 | game.expand('Python', 'Ruby', 'Does it enforce indentation?', 33 | False) 34 | """ 35 | old_guess_id = self._get_guess_id(old_guess) 36 | if old_guess_id is None: 37 | raise GuessError(old_guess + ' is unknown.') 38 | if self._get_guess_id(new_guess) is not None: 39 | raise GuessError(new_guess + ' is known already.') 40 | last_index = len(self.data) 41 | if answer_for_new: 42 | self.data.append({'guess': new_guess}) 43 | self.data.append({'guess': old_guess}) 44 | else: 45 | self.data.append({'guess': old_guess}) 46 | self.data.append({'guess': new_guess}) 47 | self.data[old_guess_id] = {'question': question, 'yes': last_index, 48 | 'no': last_index + 1} 49 | 50 | def get_question(self, id=0): 51 | """Get the current question for this game. 52 | 53 | Parameters: 54 | id (int): The identifier of the current position in the game. 55 | 56 | Returns: 57 | The question for the given position in the game if available, or 58 | None otherwise. If None is returned, that means that the game has 59 | reached a point where a guess is available. 60 | 61 | Raises: 62 | IndexError: Unknown id. 63 | """ 64 | return self.data[id].get('question') 65 | 66 | def get_guess(self, id=0): 67 | """Get the guess for this game. 68 | 69 | Parameters: 70 | id (int): The identifier of the current position in the game. 71 | 72 | Returns: 73 | The guess for the given position in the game if available, or None 74 | otherwise. If None is returned, that means there are still 75 | questions that need to be answered. 76 | 77 | Raises: 78 | IndexError: Unknown id. 79 | """ 80 | return self.data[id].get('guess') 81 | 82 | def answer_question(self, answer, id=0): 83 | """Provide an answer for the current question. 84 | 85 | Parameters: 86 | id (int): The identifier of the current position in the game. 87 | 88 | Returns: 89 | The id for the next position in the game. 90 | 91 | Raises: 92 | GuessError: The id given does not point to a question. 93 | IndexError: Unknown id. 94 | """ 95 | if answer: 96 | new_id = self.data[id].get('yes') 97 | else: 98 | new_id = self.data[id].get('no') 99 | if new_id is None: 100 | raise GuessError('Not a question') 101 | return new_id 102 | 103 | def _get_guess_id(self, guess): 104 | for i in range(len(self.data)): 105 | if self.data[i].get('guess') == guess: 106 | return i 107 | return None 108 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==0.10.1 2 | Flask-WTF==0.11 3 | Jinja2==2.7.3 4 | MarkupSafe==0.23 5 | WTForms==2.0.2 6 | Werkzeug==0.10.1 7 | itsdangerous==0.24 8 | -------------------------------------------------------------------------------- /templates/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Guess the Language! 4 | 5 | 6 |

Guess the Language!

7 |

Error: {{ error }}

8 |

Click here to begin a new game.

9 | 10 | -------------------------------------------------------------------------------- /templates/guess.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Guess the Language! 4 | 5 | 6 |

Guess the Language!

7 |

My guess: {{ guess }}

8 |

Was I right?

9 |
10 | {{ form.hidden_tag() }} 11 |

12 | {% for option in form.answer %} 13 | {{ option }} {{ option.label }}
14 | {% endfor %} 15 | {% for error in form.answer.errors %}[{{ error }}]{% endfor %} 16 |

17 | {{ form.submit }} 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Guess the Language! 4 | 5 | 6 |

Guess the Language

7 |

Think of your favorite programming language. I'm going to try to guess it!

8 |

Ready? Click here to begin!

9 | 10 | 11 | -------------------------------------------------------------------------------- /templates/learn.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Guess the Language! 4 | 5 | 6 |

Guess the Language!

7 |

Sorry I did not know your language. Can you help me learn it?

8 |
9 | {{ form.hidden_tag() }} 10 |

{{ form.language.label }} {{ form.language() }} {% for error in form.language.errors %}[{{ error }}]{% endfor %}

11 |

{{ form.question.label }} {{ form.question() }} {% for error in form.question.errors %}[{{ error }}]{% endfor %}

12 |

What is the answer to this question for your language?

13 |

14 | {% for option in form.answer %} 15 | {{ option }} {{ option.label }}
16 | {% endfor %} 17 | {% for error in form.answer.errors %}[{{ error }}]{% endfor %} 18 |

19 | {{ form.submit }} 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /templates/question.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Guess the Language! 4 | 5 | 6 |

Guess the Language!

7 |

{{ question }}

8 |
9 | {{ form.hidden_tag() }} 10 |

11 | {% for option in form.answer %} 12 | {{ option }} {{ option.label }}
13 | {% endfor %} 14 | {% for error in form.answer.errors %}[{{ error }}]{% endfor %} 15 |

16 | {{ form.submit }} 17 |
18 |

Click here to end this game.

19 | 20 | 21 | --------------------------------------------------------------------------------