Single Todo

Our show_todo is still using dummy data. Let’s have it use actual data, with a lookup static method from models.Todo similar to how we get the list of todos. As we do this, we’ll see how to efficiently re-arrange code.

Source for this step

Steps

  1. In models.py, let’s start by extending populate_todos to add a total of 3 sample todos. Put your cursor on todos.append(Todo('First')) and press Ctrl-D twice to duplicate the line. Changed the next two lines to have an argument of 'Second' and 'Third'. (macOS: Cmd-D)

  2. The Todo class constructor looks weird. self.title should be assigned on the first line. Click anywhere in that line and press Shift-Alt-Up to move the line up.

  3. After the __init__ constructor, add a new method:

    def get_id(self, todo_id):
        return [todo for todo in todos if todo.id == todo_id][0]
    
  4. Hmm, PyCharm thinks something’s going on with get_id. Mouse-over it and see that it thinks this method can be static. Click on def get_id, press Alt-Enter, and change it to a static method.

  5. Let’s take this for a test drive. Change model.py‘s main block:

    if __name__ == '__main__':
        populate_todos()
        first_todo = Todo.list()[0]
        first_id = first_todo.id
        print(Todo.get_id(first_id))
    
  6. Re-run models.py (make sure the correct run configuration is selected.)

  7. We realize get_id is probably better under list. With your cursor anywhere in get_id, press Cmd-W until PyCharm’s selection extends to including the method and the @staticmethod decorator. (macOS: Alt-Up)

  8. Press Shift-Alt-Down until the method moves after list.

  9. As usual, press Ctrl-Alt-L to clean up any line formatting. (macOS: Cmd-Alt-L)

  10. We need our web app to use this method for show_todo. Let’s add a first line in show_todo:

    todo = Todo.get_id(todo_id)
    

    using autocomplete. Type todo = T then tab, . and tab to accept get_id, then t and tab to complete todo_id. Use Shift-Enter to make a second line.

  11. Let’s have a bit richer HTML. On the second line in show_todo, define a format string fmt = ''.

  12. With your cursor inside that string, press Alt-Enter and choose Inject language reference -> HTML. Now provide this for the value:

    fmt = '<h1>Todo {todo_id}</h1><p>{title}</p>'
    

    As you type, you’ll get the full power of WebStorm HTML editing, right in your Python string.

  13. Change the third line to use this format string:

    return fmt.format(todo_id=todo.id, title=todo.title)
    
  14. Reload your browser and go to a todo. You’ll get an error in get_id. Later we’ll use the debugger to hunt down the problem. The reason is, the route gives us a string and our id’s are integers. Change the route definition to @app.route('/todo/<int:todo_id>').

  15. Reload your browser.

  16. Your app.py should match the following:

    app.py in Single Todo
    from flask import Flask
    
    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>'
        items = [div.format(id=t.id, title=t.title) for t in todos]
        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)
    
    
    if __name__ == '__main__':
        populate_todos()
        app.run(debug=True)
    
  17. Your models.py should match the following:

    models.py in Single Todo
    from random import randint
    
    todos = []
    
    
    class Todo:
        def __init__(self, title):
            self.title = title
            self.display_fmt = 'Todo {todo_id}'
            self.id = randint(1000, 9999)
    
        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 get_id(todo_id):
            return [todo for todo in todos if todo.id == todo_id][0]
    
    
    def populate_todos():
        todos.append(Todo('First'))
        todos.append(Todo('Second'))
        todos.append(Todo('Third'))
    
    
    if __name__ == '__main__':
        populate_todos()
        first_todo = Todo.list()[0]
        first_id = first_todo.id
        print(Todo.get_id(first_id))
    

Analysis

PyCharm’s productivity features are starting to show:

  1. Moving lines. Shift-Alt-Up and Shift-Alt-Down are so much faster then cutting and pasting the line. You don’t even have to select anything, just click in the line.
  2. Smart autocomplete. In many places, we get accurate and fast completion.
  3. Language injection. Having HTML-aware editing, in the middle of Python, is quite useful. Same is true for CSS, SQL, JS, etc.

Extra Credit

  1. We also use Shift-Alt-Up to move a line up. Can we select an entire method and move it, using Shift-Alt-Up?
  2. If we extend our selection too far with Ctrl-W (macOS: Alt-Up), will Alt-Down gradually de-select?