Assignment 1
This assignment is to write a Flask application that enables the user to browse the plays of William Shakespeare. It is due on February 2, 2016.
Attention
This is a solo assignment, so you must complete and submit it on your own. You may talk about general concepts with your classmates, but all code you submit must be your own or adapted from the class examples.
Hint
In Lecture 2.1, we discussed the data structures you need. The notebook contains the examples we worked.
Before completing this assignment, make sure that you can set up and run an assignment as described in Assignment 0.
This assignment will exercise the following skills:
- Flask programming
- Designing and responding to URLs
- Working with basic Python data structures (lists and dictionaries) in both Python and Jinja
- Generating HTML from Python data structures
Change Log
- February 8
- Change grade value through Part 2.
- February 2
- Change
play
parameter inplay
URL toplay_id
to reduce confusion. - Better document sorting plays.
- Add extended hint for implementing the
scene
function. - Uploaded new-and-improved Shakespeare data, and documented the keys in act objects.
- Added notes on using JSON to load data.
- Added example code for building the global play dictionary.
Helpful Resources
The following resources will likely be helpful in completing this assignment:
Getting Started
- Create a new Flask application, using your virtual environment, and set it up (Git repository,
.gitignore
,manage.py
) as you did for Assignment 0. - Download the Shakespeare data files and unpack it into a directory called
data
. - Commit the Shakespeare files to your Git repository (this is important for submitting them).
-
Install the
PyYAML
package in your virtual environment:pip install PyYAML
You can also install packages from within PyCharm; if you browse to the Project Interpreter settings, there is a ‘+’ button at the bottom. Click that and enter the package name.
Overview of Data
The data files you downloaded contain the complete plays of Shakespeare, obtained from Open Source Shakespeare.
Each play is as a separate YAML file, e.g. hamlet.yaml
. YAML is a format for describing lists, dictionaries, strings, numbers, etc.; the basic data structures that you can use in both Python and JavaScript. It's much like JSON, if you're familiar with that (if not, you will be later this semester!), but it's easier for humans to read.
Each file describes one play. It is a dictionary with the following keys:
id
- The play's ID, suitable for use in a URL.
title
- The play's title.
full_title
- The play's full title, if
title
is abbreviated. date
- The year in which the play was written.
characters
- A mapping of character IDs to character names. Characters are identified by their ID, and character IDs should generally be useful in URLs; use this mapping to find the display name for a character ID.
acts
- A list of acts in the play.
Each act is a dictionary with the following keys:
act
- The act number (if your copy of the data files does not have this, redownload them).
scenes
- A list of scenes.
Each scene is a dictionary with the following keys:
act
- The act number
scene
- The scene number
title
- The scene title, description, or setting.
blocks
- The blocks (speaches) that make up the scene.
Each block has the following properties:
speaker
- The ID of the character who speaks these lines. Look up the character's name in the
characters
key up above. It may beNone
(YAML:null
), in which case no one is speaking (e.g. for stage directions). lines
- A list of lines that are spoken. If a line is enclosed in
[…]
characters, then the line is a stage direction.
For example, A Midsummer Night's Dream opens like this:
date: '1595' title: Midsummer Night's Dream full_title: A Midsummer Night's Dream characters: All-mnd: All Bottom: Bottom Cobweb: Cobweb Demetrius-mnd: Demetrius # more characters elided acts: - act: 1 scenes: - act: 1 scene: 1 title: Athens. The palace of THESEUS. blocks: - speaker: null lines: - '[Enter THESEUS, HIPPOLYTA, PHILOSTRATE, and Attendants]' - speaker: Theseus lines: - Now, fair Hippolyta, our nuptial hour - Draws on apace; four happy days bring in - 'Another moon: but, O, methinks, how slow' - This old moon wanes! she lingers my desires, - Like to a step-dame or a dowager - Long withering out a young man revenue. - speaker: Hippolyta lines: - Four days will quickly steep themselves in night; - Four nights will quickly dream away the time; - And then the moon, like to a silver bow - New-bent in heaven, shall behold the night - Of our solemnities.
Loading the Data
Your program should first load the data into memory (it can be a good idea to not have the whole data set in memory, but for our purposes it's easier to load it all).
To do this, first list all the files. If you called your data directory data
, you can do this to find all the files:
import glob for fn in glob.glob('data/*.yaml'): # load data from file fn
To load a play from its file, import the yaml
module (up at the top of your program) and use code like the following:
with open(fn, 'r') as yf: play = yaml.load(yf)
This will load the play into a dictionary and store that dictionary in the variable play
.
You should load all of your plays into a data structure; I would recommend creating a global variable plays
that contains a dictionary mapping play IDs (the part of the filename before .yaml
) to play objects.
Hint
You can do all of this with:
# create global plays dictionary plays = {} # loop over the files for fn in glob.glob('data/*.yaml'): # load data from fn and put in dictionary with open(fn, 'r') as yf: play = yaml.load(yf) plays[play['id']] = play
Finally, it is useful to be able to sort the plays; you can get a list of plays from your dictionary by calling plays.values()
.
Hint
Parsing YAML can be slow. If loading the data is too slow on your computer, use the JSON version instead, by making the following changes:
import json
instead ofimport yaml
glob.glob('data/*.json')
instead of*.yaml
- use
json.load
instead ofyaml.load
Assignment Requirements
Now that we have the preliminaries out of the way, this section describes the assignment requirements — the pages you need to create.
Listing Plays
The root page (/
) should have a heading that says something useful (e.g. ‘Plays of Shakespeare’), followed by a list of plays.
- The list should be a
ul
(bulleted list) - List the plays in order of year published (earliest first)
- The play should be listed by its
title
- The title should be a hyperlink to the play,
/play/<id>/
where<id>
is the play's ID - After the title, list the play's date in parentheses
For example:
<ul> <li><a href="/plays/12night/">Twelfth Night</a> (1599)</li> <!-- other plays --> </ul>
If you pass the plays to your template as a list (e.g. by using the values()
method on your plays
dictionary), then you can sort it in Jinja:
{% for play in plays|sort(attribute='date') %} {% endfor %}
Part 1: Displaying Play Information
When the user browses to /plays/12night/
, or the corresponding URL for any other play, they should see a page that has the following:
- The play's full title as a level-1 heading
- The play's date
- A list of characters
- A list of acts, each of which has a sub-list of scenes. Each scene should be a link to a page for that scene, of the form:
/play/<id>/acts/<act>/scenes/<scene>
, so a link to Act 2 Scene 3 of Twelfth Night will be/play/12night/acts/2/scenes/3
.
Use Level 2 headings to introduce the list of characters and the list of acts (table of contents).
You can implement this by creating a URL handler as follows:
@app.route('/plays/<play_id>/') def show_play(play_id): # show the play!
Part 2: Displaying Scenes
Create a URL handler for a scene:
@app.route('/plays/<play_id>/acts/<int:act_no>/scenes/<int:scene_no>') def show_scene(play_id, act_no, scene_no): # show the scene!
The scene page should have the following:
- A level-1 heading of the play's title
- A level-2 heading with the act and scene number
- The scene's setting (right after the L2 act & scene number)
- The blocks/speeches in the play
- Hyperlinks to the previous and next scenes, if relevant (the first scene should not have a previous link, and the last scene should not have a next link). These links should have the act and scene number as the text (e.g. ‘Act 2 Scene 3’).
Hint
The show_scene
function will need to locate the specific scene to display. Also, you need to be able to check if there are more scenes. This will be easier if you convert the act/scene structure into a single list of scenes. If you have already located the play and stored it in a variable play
, you can do this as follows:
scenes = [] # loop over the acts for act in play['acts']: # loop over the scenes for scene in act['scenes']: # add the scene to the master scenes list scenes.append(scene)
You can then search this scenes
list for the scene you seek:
scene = None scene_pos = None for i, s in enumerate(scenes): # check the act & scene number if s['act'] == act_no and s['scene'] == scene_no: scene = s scene_pos = i break # At this point, `scene` is the scene, and `scene_pos` the index # Use `scene_pos` to check if it is the first/last scene
Each block/speech should have the speaker, if there is one, followed by the lines. Enclose each speech in a div
element. Enclose each line in a div
as well, so they'll be laid out as blocks and have separate lines.
Hint
You could also use the <br>
tag to separate lines. However, the div
element helps us do nice styling things later.
Lines that are enclosed in square brackets (‘[…]
’) should be displayed in italics (the em
tag) without the square brackets. The Jinja2 string manipulation filters will be helpful for doing this.
Note
If you correctly complete everything up to this point, you will receive 95/100 points on the assignment.
Part 3: Cross-Referencing Characters
For this part, we will implement a cross-referencing system for characters.
Create a new URL handler for URLs of the form /characters/<character>
that take a character ID.
This page should display the character's name as a level 1 heading, and a list of all plays in which that character appears (looked up by ID — character IDs are globally unique across all plays). The plays should be listed by date, exactly as they appear in the main plays list (part 1).
Modify the play info page (part 2) and the scene displays (part 3) so that the character names are hyperlinks to the character pages.
Extra Credit: Characters in an Act
- Add an additional page,
/plays/<play>/acts/<act>/
, for the act itself. - Make the act entries in the play info page link to act pages.
- In the act page, list the scenes as hyperlinks to the scenes.
- In a separate list, list all characters who appear in that act.
Submitting
Create a text file, README.txt
, with your name and any notes about external resources that you used. Commit it to your Git repository.
Submit your assignment using manage.py submit
to create a zip file from your Git repository, and submit the zip file on TRACS.