Studio: FlickList 6
In this studio we will build persistence into our app. Finally, our hypothetical user will be able to add a new movie to the list, close the window and go eat a sandwich, forget what movie she wanted to watch, and then come running back to our site, and the same movie will still be faithfully sitting in her list so she can go stream it on our other awesome website, FlicksNet.
We'll start using a term and associated acronym that is common when connecting applications to databases: object-relational mapping (ORM). Applications store information in the form of objects and databases store data in the form of relational data (tables). An ORM library or module will often do much of the heavy lifting for us, when it comes to translating back and forth between these differing contexts.
Walkthrough
For today's walkthrough, we'll be looking at three different branches. On the walkthrough6
branch you'll see the solution to last week's studio. But our work today will begin on the walkthrough6a
branch, where you can see we've made some significant changes to our code. We've refactored our code in a way that is very similar to how you refactored your code in Class 9's prep work as you coded along with the video lessons. Specifically, we've added code to support our chosen ORM implementation, SQLAlchemy, and we've made a new persistent class called Movie
. To see the differences between the the solution to last week's studio and our code now, type this command in your terminal: git diff walkthrough6 walkthrough6a
.
The most powerful line in this whole batch of changes is this:
def get_current_watchlist():
return [movie.name for movie in Movie.query.all()]
Your instructor has already created a user and database for flicklist
in their phpMyAdmin (you will do the same in the Studio portion below), so they can run the code below in a Python shell to create the movie
table. Now that we've added if __name__ == "__main__":
to prevent app.run()
from running when we load main.py
as a module, we can now safely import db
and Movie
from main.py
:
(flask-env) $ python
>>> from main import db, Movie
/home/dm/miniconda3/envs/flicklist/lib/python3.6/site-packages/flask_sqlalchemy/__init__.py:839: FSADeprecationWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future. Set it to True or False to suppress this warning.
>>> db.create_all()
>>> db.session.add(Movie('Mulan'))
>>> db.session.add(Movie('Rushmore'))
>>> db.session.add(Movie('Damsels in Distress'))
>>> db.session.commit()
>>> Movie.query.all()
[<Movie 'Mulan'>, <Movie 'Rushmore'>, <Movie 'Damsels in Distress'>]
Now when we fire up the app and view it, we see those movies in the watchlist!
But we've still got some work to do to fully utilize our new database capabilities. In particular:
- Adding movies to the watchlist isn't persistent - if we type in a name and click Add it, then go back and reload, nothing has changed.
- Clicking on I Watched It! makes no persistent changes, either.
- Rating movies isn't persistent.
Today we will change all these functionalities to be persistent. We'll walk through the first two together, then you'll handle the last one on your own in the Studio.
A List of Movie Instances
Our next change is going to be a massive breaking change to our application. Up until now, we've been representing movies as strings of their names. The time has come to represent movies as instances of the Movie
class. This will rip through our application, breaking lots of stuff. We'll rename lots of variables so it's clear when we're talking about a Movie
object, and when we're talking about a movie name (a property of Movie
objects). You can explicitly see our changes by running git diff walkthrough6a walkthrough6b
.
For example, notice how we update our get_current_watchlist
function to get a list of Movie
objects from the database rather than return a list of strings, as we did previously.
Our code previously returned a list of strings:
def get_current_watchlist():
# old: list of strings
return [movie.name for movie in Movie.query.all()]
And we refactored it to return a list of Movie
objects:
def get_current_watchlist():
# new: list of Movie instances
return Movie.query.all()
And notice how we have to rework the templates to call .name
on the model object when we want a human-readable version of the movie name, and .id
when we want to reference a specific row in our movie table.
In this batch of changes, we've changed our most important data structure and we've made it possible to add movies to the watch list from the web browser. Hooray! Cool stuff! The world is our oyster.
Watching Movies
In the next batch of changes, we'll modify the I Watched It! button to change the database, and truly cross off the movie in the database, persistently. You can view these changes by running the command git diff walkthrough6b studio6
. We also finish our transition from strings to Movie
objects by modifying the ratings templates.
Studio
Now that we have some database functionality wired into our application, let's continue to convert our app to use the database. Our next task is to implement persistent ratings, so a user can rate a movie and the information is stored in the database for later.
Checking out the Studio code
Follow the instructions for getting the code in order to get the starter code for studio6
.
You should have already installed MAMP locally but if you need a refresher it is in Class 8 Prep Work.
Your Tasks
Here's an outline of the steps we'll take. We provide additional details below.
- Create MySQL user and database
- Initialize your database
- Run the flicklist application
- Modify flicklist to store movie ratings
Create MySQL User and Database
Now we will need to create a database locally (on your computer). We will use the same names as we did in the walkthrough.
Make sure your servers are running with MAMP. Go to phpMyAdmin in your browser and create a user named flicklist with the password MyNewPass. Check the box that says Create database with same name and grant all privileges then press the Go button in the bottom right-hand corner.
Initialize Your Database
Make sure your flask environment is activated. For this and future Studios we'll use the flask-env
virtual environment that was created while you coded along with the video lessons. Since you already installed the necessary conda packages in the Database Configuration lesson, your flask-env
is ready to use SQLAlchemy.
Reenact the python shell session from the walkthrough. Here it is again:
(flask-env) $ python
>>> from main import db, Movie
/home/dm/miniconda3/envs/flicklist/lib/python3.6/site-packages/flask_sqlalchemy/__init__.py:839: FSADeprecationWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future. Set it to True or False to suppress this warning.
>>> db.create_all()
>>> db.session.add(Movie('Mulan'))
>>> db.session.add(Movie('Rushmore'))
>>> db.session.add(Movie('Damsels in Distress'))
>>> db.session.commit()
>>> Movie.query.all()
[<Movie 'Mulan'>, <Movie 'Rushmore'>, <Movie 'Damsels in Distress'>]
Run the Existing FlickList
Exit the Python shell and start the FlickList program by running this command in the terminal:
python main.py
.
In your browser, add one movie then cross it off.
Modify FlickList to Store Movie Ratings
- Change
main.py
'sMovie
class to have a ratings property. Make this a string property, since it will hold values'*'
,'**'
,'***'
, and so on. - Drop the
movie
table using the Python shell. After starting up the shell, importdb
andMovie
as we did above, then use the commanddb.drop_all()
. Since you have just changed the model, we'll need to recreate the table from scratch. - Reinitialize your database by running
db.create_all()
, as we did above. - Update
get_watched_movies
to return all unwatched movies in the database, as a list ofMovie
objects. It currently returns a static list of movies. - Refactor the
ratings.html
template so that it works whenmovies
is a list ofMovie
objects, rather than a list of strings. - Change the behavior of the
rate_movie
function inmain.py
so that when a user rates a movie it actually stores the rating using the column/property you just added. - Display the stored ratings on the ratings page by pre-populating the select box with each movie's old rating, if there is a rating for the movie (see the
TODO
inratings.html
). Theselected
attribute of theoption
tag will come in handy. Read more about it at w3schools.
Give Yourself a Hand
If you made it this far, you've completed a challenging studio. Congratulations! And if you don't get it right away, don't give up. Keep on trying: be persistent!