Flask 101: Filtering Searches and Deleting Data

Last time we got our Flask based music database application partially functional. It could now add data to the database, edit said data and also display everything in the database. But we didn’t cover how to filter the data by using the user’s filter choice (Artist, Album name or publisher name) and search string. We also didn’t cover how to delete items from the database. That is the two-fold goal of this article.


Filtering Search Results

Filtering search results using SQLAlchemy (via Flask-SQLAlchemy) is actually quite easy. All you need to do is create some very simple query objects. Open up the main.py file that we were editing last time and replace the search_results() function with the following version of the code:

@app.route('/results')
def search_results(search):
    results = []
    search_string = search.data['search']

    if search_string:
        if search.data['select'] == 'Artist':
            qry = db_session.query(Album, Artist).filter(
                Artist.id==Album.artist_id).filter(
                    Artist.name.contains(search_string))
            results = [item[0] for item in qry.all()]
        elif search.data['select'] == 'Album':
            qry = db_session.query(Album).filter(
                Album.title.contains(search_string))
            results = qry.all()
        elif search.data['select'] == 'Publisher':
            qry = db_session.query(Album).filter(
                Album.publisher.contains(search_string))
            results = qry.all()
        else:
            qry = db_session.query(Album)
            results = qry.all()
    else:
        qry = db_session.query(Album)
        results = qry.all()

    if not results:
        flash('No results found!')
        return redirect('/')
    else:
        # display results
        table = Results(results)
        table.border = True
        return render_template('results.html', table=table)

Here we added a somewhat lengthy conditional if statement. We first check to see if the user has entered a search string in the search text box. If so, then we check to see which filter the user has chosen from the combobox: Artist, Album or Publisher. Depending on the user’s choice, we create a custom SQLAlchemy query. If the user doesn’t enter a search term or if our web application gets confused and doesn’t recognize the user’s filter choice, then we do a query against the full database. This is something that probably shouldn’t be done in production as if the database gets really large, then doing a query against your database will end up making your web application unresponsive. You can simply add some validation to your form’s input to prevent this from happening (i.e. don’t query the database with an empty search string). However we won’t be covering that here.

Anyway, go ahead and try this code out and see how it works. I tried several different search terms and it seemed to work fine for my use cases. You will note that I simply used the contains method which is great for looking up a string in a table’s column. You can always index your database and do other various optimizations to it including making these queries a lot more focused if you want to. Feel free to play around with this code and see how you can improve it.

Now we will move on and learn how to delete items from the database!


Deleting Data

There are times when you enter something into the database that you just want to delete. Technically you could use our editing functionality to just edit the entry to whatever you want, but sometimes you just need to purge data permanently. So the first thing we need to do is add a Delete column to our results table. You will want to open up tables.py and add a new LinkCol instance to the Results class:

from flask_table import Table, Col, LinkCol

class Results(Table):
    id = Col('Id', show=False)
    artist = Col('Artist')
    title = Col('Title')
    release_date = Col('Release Date')
    publisher = Col('Publisher')
    media_type = Col('Media')
    edit = LinkCol('Edit', 'edit', url_kwargs=dict(id='id'))
    delete = LinkCol('Delete', 'delete', url_kwargs=dict(id='id'))

Just as we did when we created the link for editing our data, we add a new link for deleting the data. You will note that the second argument, which is the endpoint, points to a delete function. So the next step is to open up our main.py file and add said delete() function:

@app.route('/delete/', methods=['GET', 'POST'])
def delete(id):
    """
    Delete the item in the database that matches the specified
    id in the URL
    """
    qry = db_session.query(Album).filter(
        Album.id==id)
    album = qry.first()

    if album:
        form = AlbumForm(formdata=request.form, obj=album)
        if request.method == 'POST' and form.validate():
            # delete the item from the database
            db_session.delete(album)
            db_session.commit()

            flash('Album deleted successfully!')
            return redirect('/')
        return render_template('delete_album.html', form=form)
    else:
        return 'Error deleting #{id}'.format(id=id)

This code is actually pretty similar to our edit() function from the last article. You will note that we updated the route though. So instead of specifying ‘/item/, we made it ‘/delete/. This makes the URLS between the two functions different so they actually execute the correct function when the link is clicked on. The other difference is that we don’t need to create a special saving function here. We just reference the db_session object directly and tell it to remove the album if it’s found in the database and then commit our changes.

If you run the code, you should see something like the following when doing an empty string search:

The last thing we need to do is create the delete_album.html that we referenced above. Let’s create that file and save it to our templates folder. Once that file is created, just add the following:


Delete Album - Flask Music Database

Delete Album

{% from "_formhelpers.html" import render_field %}
{{ render_field(form.artist) }} {{ render_field(form.title) }} {{ render_field(form.release_date) }} {{ render_field(form.publisher) }} {{ render_field(form.media_type) }}

This code will render our form to show the user what they are deleting. Let’s try clicking on the delete link for one of the duplicates in our table. You should see a screen like this appear:

When you press the Delete button, it will redirect you to the home page where you will see a message that the item was deleted successfully:

To verify that the deletion worked, just do another empty string search. Your results should show one less item in the table:


Wrapping Up

Now you should know how to do some basic filtering of search results from the database. You also have learned how to successfully delete items from the database in your Flask application. There are several places in the code that could use a refactoring and general cleanup. You could also add some CSS styling to your application to make it look prettier. Those are exercises that I will leave for the reader. Have fun playing around with the code and giving Flask a try. It’s a neat little web framework and well worth a look!


Download Code

Download a tarball of the code from this article: flask_musicdb_v.tar


Other Articles in the Series

5 thoughts on “Flask 101: Filtering Searches and Deleting Data”

  1. Thanks for this series. I have a question on the sqlalchemy filtering with the related tables- when I try this for a Artist filter I either get all albums (if there is any match) or none. The other two filters work fine- so I suspect something is off in the relational connections. I am using Python 3.6 on Ubuntu 16.04 (in a dedicated virtual environment) with current pip installs of flask and sqlalchemy.

    Thanks again

  2. I’m not sure I understand the question. If you choose the Artist filter from the combobox, it should return all albums by that artist when there is a match and nothing is there are no matches. What were you expecting it to do?

  3. From a new database (created with db_creator.py) I added two entries:
    Artist/Title/Date/Pub/Type:
    Artist 1/Album 1/1992/Noone/Digital
    Unknown/Second Record/2013/Somebody/Digital

    If I search for “Unknown” Artist, I get both entries returned.
    If I search for “xyz”, No entries are returned (correct)

    When I try looking at the query for Artist in main.py independently of the flask site- the query always returns all the entries if any match occurs, or returns nothing if no match. I wondered if the query through the relationship/backdef had some magic to it?

    Thanks

  4. Alright, I figured it out. I didn’t have the SQLAlchemy query right. It ended up needing to be db_session.query(Album, Artist).filter( Artist.id==Album.artist_id).filter(Artist.name.contains(search_string))

    I updated the post to reflect that. Thanks!

Comments are closed.