Change Display

We currently let the route decide what to display in the listing of todos. We should let the todo be in charge of its formatting, for consistency across our app.

We will use this change to illustrate the following debugger concepts:

  • Watches
  • Conditional breakpoints

Source for this step

Steps

  1. In models.py, let’s change the assigned display format to also show the id:

    self.display_fmt = '{title} (ID: {todo_id})'
    
  2. Let’s change the display property we use to calculate and return the display value:

    @property
    def display(self):
        return self.display_fmt.format(todo_id=self.id, title=self.title)
    
  3. Just to be clean, let’s change our __repr__ to use this:

    def __repr__(self):
        return self.display
    
  4. In app.py, change list_todos to use t.display instead of t.title:

    items = [div.format(id=t.id, title=t.display) for t in todos]
    
  5. Using the debugger, we’d like to see the calculated value for the last todo. Put the breakpoint in models.py on this line:

    return self.display_fmt.format(todo_id=self.id, title=self.title)
    
  6. Click the Debugger’s Rerun button rerun then reload the todo listing in in the browser.

  7. Click Resume resume several times until you get to the fifth todo, then use Evaluate Expression evaluate to see the value of self.display.

  8. Let’s make that more efficient using a watch, which is a way to calculate an expression and show in the debugger’s Variables as you move through code.

  9. In Variables panel, click the + button for New Watch.

  10. In the input prompt, type this expression and hit enter:

    ``self.display_fmt.format(todo_id=self.id, title=self.title)``
    
  11. Click the Rerun button rerun then reload the todo listing in in the browser.

  12. This time, as you step through to the last todo, the watch shows you the calculated value that will be returned.

  13. It is still inefficient to step until you reach the todo. Let’s make a conditional breakpoint, which only stops when an expression is true.

  14. Right-click on your breakpoint inside def display. In the Condition box, enter self.id == 4.

  15. Click the Rerun button rerun then reload the todo listing in in the browser.

  16. Note that the debugger this time stops only at the 4th todo.

  17. Your models.py should match the following:

    models.py in Change Display
    from random import choice
    
    todos = []
    
    
    class Todo:
        def __init__(self, title):
            self.title = title
            self.display_fmt = '{title} (ID: {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, title=self.title)
    
        @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))
    
  18. Your app.py should match the following:

    app.py in Change Display
    from flask import Flask, request
    from flask import redirect
    
    from models import populate_todos, Todo
    
    app = Flask(__name__)
    
    
    @app.route('/', methods=['POST', 'GET'])
    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.display) 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', 'GET'])
    def add_todo():
        todo_id = request.form['todo_id']
    
        if request.method == 'POST':
            print('POSTed')
        else:
            print('GETed')
    
        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)