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 in play URL to play_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

  1. Create a new Flask application, using your virtual environment, and set it up (Git repository, .gitignore, manage.py) as you did for Assignment 0.
  2. Download the Shakespeare data files and unpack it into a directory called data.
  3. Commit the Shakespeare files to your Git repository (this is important for submitting them).
  4. 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 be None (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 of import yaml
  • glob.glob('data/*.json') instead of *.yaml
  • use json.load instead of yaml.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.