Delete Todo¶
Deleting a todo is an important part of our application. Let’s add that feature while showing more about debugging:
- Run to Cursor
- Evaluate Expression
- Console
- Moving backwards in frames
Steps¶
This time we’re going to reverse our mode of implementation and start
instead with the UI side in app.py
.
In
app.py
, changelist_todos
to have a delete button:@app.route('/todo/') def list_todos(): todos = Todo.list() div = '''<div><a href="/todo/{id}/delete">{title}</a> <form method="POST" action="/todo/{id}/delete" style="display: inline"> <input type="submit" value="x"/> </form> </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)
This form posts to a
/delete
route on a todo, so let’s add that route:@app.route('/todo/<int:todo_id>/delete', methods=['POST']) def delete_todo(todo_id): todo = Todo.get_id(todo_id) return redirect('/todo/')
Put a breakpoint on the first line inside this
delete_todo
route. Reload your browser (restarting your app in the debugger if necessary) and click on thex
for one of the todos.When execution stops in the route, confirm that
todo_id
is an integer.Click
Resume
to continue execution.
In
models.py
, add a method for deletion:def delete(self): todos.remove(self)
Back in
app.py
, add a line todelete_todo
that calls this new method:@app.route('/todo/<int:todo_id>/delete', methods=['POST']) def delete_todo(todo_id): todo = Todo.get_id(todo_id) todo.delete() return redirect('/todo/')
Reload browser and click X to delete the second todo. Execution stops at your breakpoint.
Put your cursor in the
delete
method inmodels.py
.In the debugger, click
Run to Cursor
.
Let’s make sure this is the correct todo. Click the
Evaluate Expression
button.
In the popup window, enter
self.id == 2
to confirm that this is the correct todo. (Note: it might not be2
based on the sorting and random title generation.)Click the Debug Tool Window’s
Console
tab (beside theDebugger
tab), then click theShow Python Prompt
button.
Let’s see what
todo_int
was defined as in the earlier stack frame by clickingdelete_todo
in theFrames
window.Variables
updates to show us the new scope.Click the
Resume
button.
Your
models.py
should match the following:models.py in Delete Todo¶from random import choice 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 sorted(todos, key=lambda todo: todo.title) @staticmethod def add(title): todo = Todo(title) todos.append(todo) return todo def delete(self): todos.remove(self) @staticmethod def get_id(todo_id): return [todo for todo in todos if todo.id == todo_id][0] def populate_todos(): random_verbs = ['Make', 'Buy', 'Organize', 'Finish'] random_nouns = ['Red', 'Apple', 'Blue', 'Tomato', 'Green', 'Pear', 'Black', 'Bean', 'Yellow', 'Banana', 'Brown', 'Raisin'] for i in range(5): v = choice(random_verbs) w1 = choice(random_nouns) w2 = choice(random_nouns) title = '{v} {w1} {w2}'.format(v=v, w1=w1, w2=w2) Todo.add(title) if __name__ == '__main__': populate_todos() first_todo = Todo.list()[0] first_id = first_todo.id print(Todo.get_id(first_id))
Your
app.py
should match the following:app.py in Delete 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}/delete">{title}</a> <form method="POST" action="/todo/{id}/delete" style="display: inline"> <input type="submit" value="x"/> </form> </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/') @app.route('/todo/<int:todo_id>/delete', methods=['POST']) def delete_todo(todo_id): todo = Todo.get_id(todo_id) todo.delete() return redirect('/todo/') if __name__ == '__main__': populate_todos() app.run(debug=True)
- Previous topic: Sorted Listings
- Next topic: Change Display