Add Todo¶
We just hooked up a route to view a single todo. It’s natural now to hook up adding todos, meaning an HTML form element that posts to a view then re-displays the list of todos.
In this step we introduce running under the debugger:
- Launch a debug session
- Speedup the debugger if not on Windows
- Set a breakpoint to stop execution, resume, and remove the breakpoint
Steps¶
In
models.py, instead of random ids, let’s increment theidby changing theTodoconstructor (__init__) to:self.id = max([todo.id for todo in todos], default=0) + 1
We’d like the models module to be self-contained. Let the
Todoclass handle adding a todo by adding a static method toTodo:@staticmethod def add(title): todo = Todo(title) todos.append(todo) return todo
Change the
populate_todosfunction to use the class method when adding:def populate_todos(): Todo.add('First') Todo.add('Second') Todo.add('Third')
Confirm this works by re-running the
models.pyrun configuration.We now import randint but don’t use it. Let’s PyCharm fix this by clicking the menu item
Code -> Optimize Imports.Let’s now run our web app under the debugger. Click on
appin the4. Runtool window at the bottom, then click the red square to stop the regular run.In the editor for
app.py, right-click and chooseDebug models.py.Note
If you are on Mac or Linux, the Debugger window’s Console tab will say something like:
warning: Debugger speedups using cython not found. Run '"/Users/pauleveritt/projects/pauleveritt/pauleveritt.github.io/env35/bin/python3.5" "/Applications/PyCharm.app/Contents/helpers/pydev/setup_cython.py" build_ext --inplace' to build.
If so, click the hyperlink after the
Runto install the Cython compiled extensions that speed up the debugger. You only need to do that once per installed Python on your system.Reload in the browser. Everything should work normally.
In
app.py, let’s add a route:@app.route('/todo/add', methods=['POST']) def add_todo(): todo_id = request.form['todo_id'] if todo_id: Todo.add(todo_id) return redirect('/todo/')
requestandredirectneed to be imported. Click on each and doAlt-Enterto let PyCharm help you generate the import.We need an HTML form that collects and submits the todo title. Change
def list_todos()to add this to the HTML:@app.route('/todo/') def list_todos(): todos = Todo.list() div = '<div><a href="/todo/{id}">{title}</a></div>' form = '''<form method="POST" action="add"> <input name="todo_id" placeholder="Add todo..."/> </form> ''' items = [div.format(id=t.id, title=t.title) for t in todos] items.append(form) return '\n'.join(items)
Let’s use the debugger, setting a breakpoint to pause execution. In
add_todo, on the line that performsTodo.add, click in the left margin beside the line to create a big red circle, aka a breakpoint.In your browser, reload the Todo Listing. Type something in the input box and press enter.
PyCharm appears, with the debugger stopped on the line of the breakpoint. Notice also that the browser thinks it is still loading the page, waiting for the server response.
Continue execution by clicking, in the
Debugtool window, the greenResumebutton (it looks like a right arrow.)Let’s break on the next line. Click on the red circle to remove the first breakpoint, then click in the left margin beside the next line (where we return the value) to add a new breakpoint.
In the browser, type a new value into the input and press enter.
PyCharm appears, stopped on the next line.
Note that you did not have to restart PyCharm when changing debugger information such as breakpoints.
Remove this second breakpoint by clicking on the red circle.
Continue execution by clicking the green
Resumebutton
.Your
models.pyshould match the following:models.py in Add Todo¶todos = [] class Todo: def __init__(self, title): self.title = title self.display_fmt = 'Todo {todo_id}' self.id = max([todo.id for todo in todos], default=0) + 1 def __repr__(self): return self.display @property def display(self): return self.display_fmt.format(todo_id=self.id) @staticmethod def list(): return todos @staticmethod def add(title): todo = Todo(title) todos.append(todo) return todo @staticmethod def get_id(todo_id): return [todo for todo in todos if todo.id == todo_id][0] def populate_todos(): Todo.add('First') Todo.add('Second') Todo.add('Third') if __name__ == '__main__': populate_todos() first_todo = Todo.list()[0] first_id = first_todo.id print(Todo.get_id(first_id))
Your
app.pyshould match the following:app.py in Add Todo¶from flask import Flask, request from flask import redirect from models import populate_todos, Todo app = Flask(__name__) @app.route('/') def home_page(): return 'Hello World! <a href="/todo/">Todos</a>' @app.route('/todo/') def list_todos(): todos = Todo.list() div = '<div><a href="/todo/{id}">{title}</a></div>' form = '''<form method="POST" action="add"> <input name="todo_id" placeholder="Add todo..."/> </form> ''' items = [div.format(id=t.id, title=t.title) for t in todos] items.append(form) return '\n'.join(items) @app.route('/todo/<int:todo_id>') def show_todo(todo_id): todo = Todo.get_id(todo_id) fmt = '<h1>Todo {todo_id}</h1><p>{title}</p>' return fmt.format(todo_id=todo.id, title=todo.title) @app.route('/todo/add', methods=['POST']) def add_todo(): todo_id = request.form['todo_id'] if todo_id: Todo.add(todo_id) return redirect('/todo/') if __name__ == '__main__': populate_todos() app.run(debug=True)
Extra Credit¶
- Should we move the
todosarray into theTodoclass as a class attribute?
- Previous topic: Debugging
- Next topic: Sorted Listings