Sorted Listings

Our listings appear in the order they were added, not in alphabetical order. Let’s fix that, while making a bigger database of random todos. We’ll use this to show more debugging features:

  • Break on exceptions
  • Step Over
  • Step Into
  • Step Into My Code

Source for this step

Steps

  1. First, make sure you have no breakpoints open and your application isn’t currently stopped at a breakpoint.

  2. Let’s change populate_todos to dynamically make some synthetic data, at first with a typo that we will later correct:

    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_nounsxxx)
            title = '{v} {w1} {w2}'.format(v=v, w1=w1, w2=w2)
            Todo.add(title)
    
  3. When you save, Flask restarts, runs populate_todos, and has a NameError on startup. You can see the traceback in the Debugger console tab.

  4. Let’s change the debugger to stop on exceptions by clicking Run -> View Breakpoints.

  5. In the popup, select Python Exception Breakpoint -> Any Breakpoint and click the checkbox for On raise.

  6. Re-run debugger by clicking the green Re-run button rerun in the 5. Debug tool window’s toolbar.

  7. This time the debugger stops, but in library code. Let’s fix that.

  8. Go back to Run -> View Breakpoints, then Python Exception Breakpoint -> Any Breakpoint and click the checkbox for Ignore Library Files.

  9. Re-run debugger by clicking rerun and execution stops on the exception but in our code.

  10. Fix the typo by removing xxx and click the resume button resume.

  11. Let’s step through execution. Set a breakpoint on v = choice(random_verbs) by clicking in the left margin beside that line to create a red circle.

  12. Click the Re-run button rerun to restart. Execution will stop on that line, the first time through the for loop.

  13. Click the Step Over button stepover then click it again.

  14. Clear your breakpoint by clicking on the red circle, then click Resume resume.

  15. Let’s use Step Into to go populate_todos. Set a breakpoint on the line for near the end for populate_todos().

  16. Re-run our debug session by clicking Re-run rerun. Execution stops on that line.

  17. Click Step Into stepinto to go into populate_todos. PyCharm moves the highlighted line to the first line inside that function.

  18. Click Step Into stepinto six more times. When you reach v = choice(random_verbs), the debugger “steps into” the choice function of Python’s random modules. Sometimes that sucks, so let’s do something different.

  19. Click Step Out stepout to get out of descending into choice. We’re back to the surface, in v = choice(random_verbs).

  20. This time, click Step Into My Code mycode and click a couple more times. Note that we jump over choice, because it isnt part of this project’s code.

  21. Remove the breakpoint and click Resume resume.

  22. Now that we have a synthetic list, let’s sort it. Change our Todo class’s method def list to return sorted(todos, key=lambda todo: todo.title).

  23. Make sure our debug session is still running, then reload your browser on the todo listing page. Note that the todos are sorted.

  24. Re-run your debug session by clicking rerun and reload your browser. Different todo titles, but sorted.

  25. Your models.py should match the following:

    models.py in Sorted Listings
    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
    
        @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))
    
  26. Your app.py should match the following:

    app.py in Sorted Listings
    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)