6 Leveling up with advanced SQLAlchemy queries

Okay, these might not be advanced, but there’s going to help us a lot.

6.1 Listing all of our cities

First, it isn’t going to do us much good to say “hey, browse by cities!” if we don’t let everyone know what the options are for cities.

The code below selects all of the distinct city column values from the School model, and then lets us view them. AFter you add the route and cities.html (below), you can see it at work if you visit http://localhost:5000/city.

@app.route('/city')
def city_list():
    # Get the unique city values from the database
    cities = School.query.with_entities(School.city).distinct().all()
    # They're in a weird list of one-element lists, though, like
    # [['Yonkers'],['Brooklyn'],['Manhattan']]
    # so we'll take them out of that
    cities = [city[0] for city in cities]
    return render_template("cities.html", cities=cities)

Documentation for Flask-SQLAlchemy is… not ideal, I found out how to do the distinct-columns thing here

Your cities.html should look like this:

<!doctype html>
<html>
  <head>
    <title>Schools</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" crossorigin="anonymous">
  </head>
  <body>
      <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <a class="navbar-brand" href="#">Schools</a>
      </nav>
      <div class="jumbotron">
        <div class="container">
          <h1 class="display-4">Cities</h1>
        </div>
      </div>
      <div class="container">
        {% for city in cities %}
          <p>{{ city }}</p>
        {% endfor %}
      </div>
    </div>
  </body>
</html>

There we go, all of our cities listed out on a nice page, ready to go.

Of course we’d like to have them printed out nicely (not ALL CAPS LIKE THAT), and each city name link ot the page of schools. To add that, we’ll adjust our {{ city }} disply a little bit.

{% for city in cities %}
  <p><a href="/city/{{ city }}">{{ city.title() }}</a></p>
{% endfor %}

6.1.1 Cleaning text in our Jinja2 templates

If you click a few of the links, you’ll notice that it’s sending everyone to /city/YONKERS and /city/STATEN ISLAND. We didn’t spend so much time letting /city/yonkers and /city/staten-island work to give up and let this happen!

Our fix is ugly, but it works:

<p><a href="/city/{{ city.lower().replace(' ', '-') }}">{{ city.title() }}</a></p>

6.1.2 No wait, maybe not!

You might notice that “New York” appears twice in our list. It’s because sometimes it’s NEW YORK in our database, while sometimes it’s New York. We could edit our database and fix it, but it’s usually easy to just handle bad data elegantly and remove them in our app.

Usually to remove duplicates, you’d use something like list(set(cities)), which is code that everyone blindly uses without understanding it. But hey, it doesn’t matter if you understand it: it removes duplicates!

The problem with this code is it only removes exact matches, and since one city is NEW YORK and one is New York it won’t work just yet. We need to standardize first, then remove duplicates. So our app.py gets edited as such:

@app.route('/city')
def city_list():
    # Get the unique city values from the database
    cities = School.query.with_entities(School.city).distinct().all()
    # ...more notes I'm hiding...
    # Convert to titlecase while we're pulling out of the weird list thing
    cities = [city[0].title() for city in cities]
    # Now that they're both "New York," we can now dedupe and sort
    cities = sorted(list(set(cities)))
    return render_template("cities.html", cities=cities)

And, if you noticed, we sorted them while we were at it! Beautiful work. What a nice page.

6.1.3 Doing the same for ZIP codes

I trust that we can do this without guidance now! You need new route and a new template.