22.11. Project: Improve the User Experience

Recall the web form we created in the last chapter. As designed, if the user enters invalid data, they won’t know about their mistake until they reach the results page. This flaw in the user interface (UI) lowers the quality of the user experience (UX).

One common practice for web forms is to keep a user on the original page when they make a mistake. Also, displaying an error message about what went wrong gives the user a smooth way to make corrections.

For this project, you will update a fake account sign-up page to improve the UI/UX. When a user submits invalid information, the following should happen:

  1. The form page will reload,
  2. All valid entries will remain in their input fields,
  3. One or more messages will appear explaining what went wrong,
  4. The user can re-enter information and submit again.

When the user successfully fills out the form, they will be sent to a different webpage.

22.11.1. Setup

The starter code for this project is saved in the same GitHub repository you used for the exercises. However, the project code is in a separate branch.

  1. Open the LCHS_flask_logic directory in Visual Studio Code. If you haven’t downloaded it from GitHub yet, return to the exercises and follow the instructions for cloning the project to your device.

  2. In the terminal, switch to the project-start branch:

    $ git checkout project-start
    
  3. Just like you did for the chapter exercises, create and activate a new virtual environment.

  4. Install Flask.

  5. Save and commit your work.

22.11.1.1. Run the Application

Launch main.py and open the application in a new tab in your browser.

As designed, the form works. Submitting a valid set of data will lead to a success page. However, the user interface needs some major fixes.

Recall that part of a UI is the code that makes it work. However, the appearance of the interface is just as important.

22.11.2. Part A: Clean Up the View

The layout and colors on the page are a bit distracting. Since this is the sign-up page for your web application, it is the first thing new users see. If it gives them a poor UX, then they might decide NOT to join!

Open the style.css and register.html files in Visual Studio Code. Make the following changes by adjusting the CSS style rules, adding class attributes inside the HTML tags, or adding new HTML code.

  1. Remove the yellow background color from the page.

  2. Fix the rainbow background in the header. Just because you can apply lots of color, doesn’t mean you should.

    Tip

    Note how the black letters in the heading stand out against the lighter colors, but get hard to read on top of the darker colors. The opposite would be true for a light text color.

    For best results, try to keep a consistent, sharp contrast between the text color and the background.

  3. Center the h2 and form elements on the page.

  4. Inside the form, left-align the input labels and center the Submit button.

  5. Make the font size for the labels and input boxes large enough to easily read.

  6. Style the Submit button to make it stand out more.

  7. Below the form, display some rules for filling out the input fields:

    1. The username should be 3-8 characters long. It cannot contain spaces.
    2. The password needs to be 8 or more characters long, with no spaces. Also, it should contain at least one letter, one number, and one special symbol (%, #, &, or *).
  8. Once you finish updating the appearance of the page, save and commit your work.

The form looks better now, and it does work. However, try entering some invalid information and click Submit.

Notice that the form gets erased, and this is a problem. The code behaves correctly by rejecting invalid entries. However, the user has no way of knowing this! To them, the form simply didn’t work.

To improve their experience, users need to receive some type of feedback whenever they submit a form. This will be your focus in the remaining sections.

22.11.3. Part B: Keep Valid Entries

One good way to improve the UX is to keep any correct entries in place and remove the incorrect ones. This becomes more and more important as the number of input fields increases.

Open main.py and take a look inside the sign_up() function. The inputs dictionary organizes data for the form. Each key is the label for one of the input fields. Each value is a list with strings to assign to the type, name, and placeholder attributes.

Inside register.html, note how the for loop builds the labels and input fields for the form.

Example

7
8
9
{% for (label, values) in inputs.items() %}
   <label>{{label}}: <input type="{{values[0]}}" name="{{values[1]}}" placeholder="{{values[2]}}" required /></label>
{% endfor %}
  1. Line 7: The label variable is assigned a key from the inputs dictionary. The values variable is assigned the list for that key.
  2. Line 8: Each time the loop repeats, the {{label}} placeholder is filled in by a key from the dictionary. The type, name, and placeholder strings are assigned from the values list.

In order to save valid entries after the user submits the form, you need to update both the HTML and the Python code.

22.11.3.1. Update register.html

The template only needs one modification for this part. Inside the input tag, add the value="{{values[3]}}" attribute. If the user submits a valid entry, it will be saved in the values list. {{values[3]}} will place that value into the input field when the page reloads.

If the user submits an invalid entry, values[3] will be assigned the empty string. This clears the input field when the page reloads.

22.11.3.2. Update main.py

  1. Return to main.py. For each list in the inputs dictionary, add the empty string as the last element.

    42
    43
    44
    45
    46
    47
    inputs = {
       # Label: [type, name, placeholder, value]
       'Username': ['text', 'username', '3-8 characters, no spaces', ''],
       'Password': ['password', 'password', '8 or more characters, no spaces', ''],
       'Confirm Password': ['password', 'confirm', 'Retype the password', '']
    }
    

    The first time the page loads, all of the input fields will be empty, and the placeholder text will appear.

  2. Examine the check_username() function. It defines two parameters, name and form_info. name is the string the user submitted in the Username field. form_info refers to the inputs dictionary. The function returns True or False depending on whether or not name is valid (3-8 characters long, with no spaces).

  3. Add a conditional to the function. If True, assign name to the Username list in the dictionary.

    16
    17
    18
    19
    20
    def check_username(name, form_info):
       if 3 <= len(name) <= 8 and ' ' not in name:
          form_info['Username'][3] = name
    
       return 3 <= len(name) <= 8 and ' ' not in name
    

    In line 18, form_info['Username'][3] refers to index 3 of the Username list. When the webpage loads, this entry will be assigned to the value attribute inside the <input> tag.

  4. Save your work, then reload the webpage. Test the code by entering a valid username and invalid password. Properly done, your correct entry should remain in the input field after the page reloads. Test the code again by entering an invalid username. This time, the name field should clear when the page reloads.

  5. Follow a similar process for the check_password() and check_confirm() functions.

  6. Check your work! There are six possible valid/invalid combinations to test with the form. Note that an invalid password should clear the bottom two input fields.

Once your application passes all of the tests, save and commit your code.

22.11.4. Part C: Display Error Messages

Your next step is to display error messages on the form page. Each message will appear below its matching input box. These alerts provide details for fixing any mistakes.

Once again, you will need to work with the code in both the template and main.py.

  1. In register.html, add a paragraph element below the input.

     7
     8
     9
    10
    {% for (label, values) in inputs.items() %}
       <label>{{label}}: <input type="{{values[0]}}" name="{{values[1]}}" placeholder="{{values[2]}}" value="{{values[3]}}" required /></label>
       <p class="error">{{values[4]}}</p>
    {% endfor %}
    

    {{values[4]}} is a placeholder for the error message. If the entry is valid, this space will remain empty. If the entry is invalid, text will be inserted.

    Note that the class attribute applies some styling to the error text.

  2. In main.py, add another empty string to the end of each list in the inputs dictionary.

    42
    43
    44
    45
    46
    47
    inputs = {
       # Label: [type, name, placeholder, value, error_msg]
       'Username': ['text', 'username', '3-8 characters, no spaces', '', ''],
       'Password': ['password', 'password', '8 or more characters, no spaces', '', ''],
       'Confirm Password': ['password', 'confirm', 'Retype the password', '', '']
    }
    

    The first time the page loads, no error messages appear.

  3. Return to the check_username() function. An invalid username is either too long, too short, or contains spaces. Modify the conditional to check for each of these errors:

    16
    17
    18
    19
    20
    21
    22
    23
    def check_username(name, form_info):
       if ' ' in name:
          form_info['Username'][4] = 'Username cannot contain spaces.'
       elif len(name) < 3 or len(name) > 8:
          form_info['Username'][4] = 'Username must be 3-8 characters long.'
       else:
          form_info['Username'][3] = name
       return 3 <= len(name) <= 8 and ' ' not in name
    
    1. Lines 17 & 18: Check for spaces in name. If True, replace the last entry in the Username list with an error message.
    2. Lines 19 & 20: Check if name is too short or too long. If True, replace the last entry in the Username list with a different error message.
    3. Lines 21 & 22: If both conditions are False, then name is valid. Store its value in the Username list, just like in part B.
  4. Save your work, then reload the webpage. Test by entering usernames that are too long, too short, or contain spaces. Make sure you see the proper error message each time. Also, be sure to enter a valid username (no error message should appear).

  5. Follow a similar process for the check_password() and check_confirm() functions. Be sure to test your application!

Save and commit your code before moving to Part D.

22.11.5. Part D: Redirect on Success

OK, you’ve got the appearance, validation, and error messages in place. The final part of this project deals with what happens after a successful form submission.

Note that the sign_up() function redirects the user to a success page if all of their entries are valid.

# If all of the input fields contain valid data, send the user to the success page.
if check_inputs(username, password, confirm, inputs):
   return redirect('/success')

As mentioned earlier in this chapter, redirect sends the program flow to a different path and function. In this case, the user sees a cheerful success message! However, what happens if a user guesses the path for the success page?

Try It!

Reload the form page. Instead of filling in the input fields, enter http://127.0.0.1:5000/success in the address bar.

Whoa! Success without ANY valid data!

Your application lets users access any webpage on your site if they know its URL. However, they should only be able to reach the success page if they submit valid data from the form. You need to fix this!

  1. In the return redirect() statement, add code = 307 after the template name.
  2. In the success() function, add a conditional to check for a GET/POST request.
    1. If a GET request was made, redirect back to the form page.
    2. For a POST request, render the success.html template.
  3. Save, then reload the form page. Test your code by entering the URL for the success page in the address bar. You should be redirected back to the form. Also, make sure you wind up at the success page when you submit valid entries in the form!
  4. Demonstrate your finished application to your teacher. Once it checks out, save and commit your code.

Note

code = 307 is a crude way of restricting access to the success page, but it gets you thinking in the right direction.

Unfortunately, exploring better ways to restrict access is beyond the scope of this text.

22.11.6. Bonus Mission

In this project, you built code to display error messages inside a form. The goal was to provide feedback to the user so they could correct their mistakes.

The Flask framework contains tools to handle user feedback. The process is called message flashing, and it gives developers a way to streamline their code.

In main.py, you kept track of messages as part of the inputs dictionary. With message flashing, Flask does this work automatically. The Flask website provides a short tutorial on how to set up and display flash messages. Take a look at the examples, and then refactor your application to use the flash tools.

Good applications and user interfaces are all about feedback. If the user does not get enough feedback they will probably end up hating the application. Flask provides a really simple way to give feedback to a user with the flashing system.

—Flask documentation