The goal of the second project is to allow you to practice build web applications and databases in Python. Therefore, this project requires you to use Tornado and SQLite to create a music database.
You are to work in your Raspberry Pi groups and your code should run on the provided machines (although you can do your development anywhere and using any resources you wish).
Once again, to keep track of your work and to facilitate collaboration, your group will use your Projects repository on Bitbucket. The starter code for the project has already been merged into your Projects repository1. You simply need to do a git pull on your local clone to get the latest files.
After you have the files, you can see that the searchit project as the following layout:
searchit
\_ assets
\_ html
\_ album.html SearchIt Album Template
\_ base.html SearchIt Base Template
\_ gallery.html SearchIt Gallery Template
\_ index.html SearchIt Index Template
\_ track.html SearchIt Track Template
\_ yaml
\_ data.yaml Music data YAML
\_ searchit
\_ __init__.py
\_ __main__.py Package Main Execution Module
\_ database.py SearchIt Database Module
\_ web.py SearchIt Web Module
For this project, you need to modify the following files:
data.yaml: This is the source of the data for our database.
album.html: This should display a table consisting of the tracks found in
the album.
gallery.html: This should display a series of thumbnails for the items
found in the group.
index.html: This should display your group name, group members, and group
logo.
track.html: This should display information about the track.
database.py: This implements the SQLite database.
web.py: This implements the Tornado web application.
Each of these files has a TODO marker that identifies what needs to be
implemented. Further details on what needs to be done are provided below.
The order in which you implement the different components is up to you. My recommendation is to get the database created and loaded and then implement and test one selection at a time.
The information for our database is stored in the file
assets/yaml/data.yaml. As you can see, it is in YAML format, which is a
distant cousin to the JSON format we have discussed previously. The data
file looks like this:
- name: Taylor Swift
image: https://upload.wikimedia.org/wikipedia/commons/thumb/b/b2/Taylor_Swift_Red_Tour_2%2C_2013.jpg/800px-Taylor_Swift_Red_Tour_2%2C_2013.jpg
albums:
- name: Red
image: https://upload.wikimedia.org/wikipedia/en/thumb/e/e8/Taylor_Swift_-_Red.png/220px-Taylor_Swift_-_Red.png
tracks:
- State of Grace
- Red
- Treacherous
- I Knew You Were Trouble
- All Too Well
- 22
- I Almost Do
- We Are Never Ever Getting Back Together
- Stay Stay Stay
- The Last Time
- Holy Ground
- Sad Beautiful Tragic
- The Lucky One
- Everything Has Changed
- Starlight
- Begin Again
Each member in the group should modify this file by adding at least one artist and at least one album. To find images, I simply used the Wikipedia entries for the respective artists and albums.
The database code can be found in searchit/database.py. To complete this
module, you will need finish the searchit.database.Database class by
implementing the following methods:
def __init__(self, data=YAML_PATH, path=SQLITE_PATH): # TODO: Set instance variables and call _create_tables and _load_tables self.logger = logging.getLogger() self.data = None self.path = None self.conn = None
The constructor requires you to set the data, path, and conn
instance variables appropriately. Likewise, you need to call the
_create_tables() and _load_tables() methods to create and load the
database tables when the database instance is created.
def _create_tables(self): # TODO: Create Artist, Album, and Track tables pass
The _create_tables method should create the following tables:
Artists:
Columns: ArtistID | Name | Image
Primary Key: ArtistID
Albums:
Columns: ArtistID | AlbumID | Name | Image
Primary Key: AlbumID
Foreign Key: ArtistID
Tracks:
Columns: AlbumID | TrackID | Number | Name
Primary Key: TrackID
Foreign Key: AlbumID
ID and other numerical columns should be INTEGER while everything should
be TEXT. None of the files should be NULL.
def _load_tables(self): # TODO: Insert Artist, Album, and Track tables from YAML pass
The _load_tables method should load the tables above by reading the
assets/yaml/data.yaml file and inserting data into the appropriate
tables. A sketch of the code looks something like this:
for artist in yaml.load(open(self.data)): EXECUTE INSERT ARTIST SQL for album in artist['albums']: EXECUTE INSERT ALBUM SQL for track in album['tracks']: EXECUTE INSERT TRACK SQL
A few notes:
The YAML data structure contains a list of artists, which in turn consists of a dictionary with a list of albums, where each album contains a list of tracks.
The ArtistID, AlbumID, TrackID, and Track Number are stored in the
data file. Instead, you must keep counters for each field and increment or
reset them properly.
To help debug, you should use the logging facility by doing something like:
self.logger.debug('Added Artist: id={}, name={}'.format(artist_id, artist['name']))
def artists(self, artist): # TODO: Select artists matching query pass
The artists method is used to search the Artists table for any Names
that are similar to the artist query. It should return the ArtistID,
Name, and Image.
def artist(self, artist_id=None): # TODO: Select artist albums pass
The artist method is used to retrieve the AlbumID, Name, and Image of
the Albums associated with the artist specified by artist_id.
def albums(self, album): # TODO: Select albums matching query pass
The albums method is used to search the Albums table for any Names that
are similar to the album query. It should return the AlbumID, Name,
and Image.
def album(self, album_id=None): # TODO: Select specific album pass
The album method is used to retrieve the TrackID, Number, and Name of
the Tracks associated with the album specified by album_id.
def tracks(self, track): # TODO: Select tracks matching query pass
The tracks method is used to search the Tracks table for any Names that
are similar to the track query. It should return the TrackID, Name,
and Albums.Image. To retrieve the Image associated with the track you
will need to perform a JOIN on the Albums table.
def track(self, track_id=None): # TODO: Select specific track pass
The track method is used to retrieve the TrackID, Artists.ArtistID,
Artists.Name, Albums.AlbumID, Albums.Name, Number, Tracks.Name the
track specified by track_id. To retrieve all this information, you will
need to perform a JOIN on the Albums table and a JOIN on the Artists
table.
The web application code can be found in searchit/web.py. To complete this
module, you will need finish the searchit.web.Application class and
complete the various Handler classes:
def __init__(self, port=DEFAULT_PORT): tornado.web.Application.__init__(self, debug=True, template_path=Application.TEMPLATE_PATH) # TODO: Initialize database and port self.logger = logging.getLogger() self.ioloop = tornado.ioloop.IOLoop.instance() self.database = None self.port = None # TODO: Add Index, Artist, Album, and Track Handlers
For the Application constructor, you need to set the database and
port instance variables. Additionally, you must add the handlers for
the IndexHandler, ArtistHandler, AlbumHandler, and TrackHandler
classes.
class IndexHandler(tornado.web.RequestHandler): def get(self): # TODO: Implement Index Handler pass
The IndexHandler should check the query and table arguments from the
HTML form.
If the table is Artists then it should call the artists database
method on the query to compute the list of matching artists.
If the table is Albums then it should call the albums database method
on the query to compute the list of matching albums.
If the table is Tracks then it should call the tracks database method
on the query to compute the list of matching tracks.
After this list of matches is computed, it should render the gallery.html
template with the appropriate arguments.
If no table is specified, then it should render the index.html template
with the appropriate arguments.
class ArtistHandler(tornado.web.RequestHandler): def get(self, artist_id=None): # TODO: Implement Artist Handler pass
The ArtistHandler should check if the artist_id is valid. If so, then it
should call the artist database method to retrieve a list of albums and
then render the gallery.html template with appropriate arguments.
If artist_id is not valid, then it should call the artists database
method with an empty string to generate a list of all possible artists and
then render the gallery.html template with the appropriate arguments.
class AlbumHandler(tornado.web.RequestHandler): def get(self, album_id=None): # TODO: Implement Album Handler pass
The AlbumHandler should check if the album_id is valid. If so, then it
should call the album database method to retrieve a list of tracks then
render the album.html template with appropriate arguments.
If album_id is not valid, then it should call the albums database
method with an empty string to generate a list of all possible albums and
then render the gallery.html template with the appropriate arguments.
class TrackHandler(tornado.web.RequestHandler): def get(self, track_id=None): # TODO: Implement Album Handler pass
The TrackHandler should check if the track_id is valid. If so, then it
should call the track database method to retrieve track information then
render the track.html template with appropriate arguments.
If track_id is not valid, then it should call the tracks database
method with an empty string to generate a list of all possible tracks and
then render the gallery.html template with the appropriate arguments.
Although the base.html template is provided to you, you will need to edit
the following templates:
index.htmlThis should be modified to include: a list of the group members and your group logo.
album.htmlThis is used to display all the tracks in an album. This can be done with either a list or a table.
<table class="table table-striped"> <thead> <th>Number</th> <th>Name</th> </thead> <tbody> <tr> <td>$TRACK_NUMBER</td> <td><a href="/track/TRACK_ID">TRACK_NAME</a></td> </tr> ... <tr> <td>$TRACK_NUMBER</td> <td><a href="/track/TRACK_ID">TRACK_NAME</a></td> </tr> </tbody> </table>
gallery.htmlThis is used to display a series of thumbnail images.
<div class="row"> <div class="col-sm-3"> <a href="/PREFIX/ITEM_ID" class="thumbnail text-center"> <img src="ITEM_IMAGE"> <div class="caption"> <h4>ITEM_NAME</h4> </div> </a> </div> ... <div class="col-sm-3"> <a href="/PREFIX/ITEM_ID" class="thumbnail text-center"> <img src="ITEM_IMAGE"> <div class="caption"> <h4>ITEM_NAME</h4> </div> </a> </div> </div>
track.htmlThis is used to display the information amount a track.
<dl> <dt>Track ID</dt> <dd>TRACK_ID</dd> <dt>Artist</dt> <dd><a href="/artist/ARTIST_ID">ARTIST_NAME</a></dd> <dt>Album</dt> <dd><a href="/album/ALBUM_ID">ALBUM_NAME</a></dd> <dt>Track Number</dt> <dd>TRACK_NUMBER</dd> <dt>Track Name</dt> <dd>TRACK_NAME</dd> </dl>
base.htmlThe look of the web application is provided by the Bootstrap framework, which we will be learning after Spring Break. The particular theme is Flatly, which is part of the Bootswatch suite of themes.
Feel free to modify the base.html to use other themes or to apply different
layouts.
To run web application, you can do the following:
$ python -m searchit
You can find an example of the web application at http://xavier.h4x0r.space:9876/.
To submit your project, please commit your work to your Projects repository by the beginning of class, Tuesday, March 15.
Charlie's and Sam's groups need to give Write permissions to repository. ↩