CS21 Lab 11: Photo Album (Classes & Objects)

Part 1 due Sunday, Apr 27, before midnight

Part 2 due Friday, May 2, before midnight

Overview

In this lab, you’ll write classes to represent Photo and Album objects and then write a program that allows the user to manage and view photos in an album.

Parts of this lab use the graphics library that we previously used in Lab 6, which means that you will need to do your work in the computer labs and cannot do those parts remotely.

Please read through the entire lab description before starting!

Goals

The goals for this lab assignment are:

  • Gain experience writing classes.

  • Gain experience reading and using classes.

  • Gain further experience working with lists of objects.

Programming Tips

As you write programs, use good programming practices:

  • Use a comment at the top of the file to describe the purpose of the program (see example).

  • All programs should have a main() function (see example).

  • Use variable names that describe the contents of the variables.

  • Write your programs incrementally and test them as you go. This is really crucial to success: don’t write lots of code and then test it all at once! Write a little code, make sure it works, then add some more and test it again.

  • Don’t assume that if your program passes the sample tests we provide that it is completely correct. Come up with your own test cases and verify that the program is producing the right output on them.

  • Avoid writing any lines of code that exceed 80 columns.

    • Always work in a terminal window that is 80 characters wide (resize it to be this wide)

    • In vscode, at the bottom right in the window, there is an indication of both the line and the column of the cursor.

Function Comments

All functions should include a comment explaining their purpose, parameters, return value, and describe any side effects. Please see our function example page if you are confused about writing function comments.

1. Part 1. Writing Classes and Creating Objects

This part of the lab is due Sunday, April 27. Be sure you run handin21 to submit the finished code prior to that deadline!

For this lab, you’ll be doing your work in three files:

  1. photo.py: This file will define a Photo class that you’ll use to represent the information about one photo or image (e.g., the creator of the photo, description, path to the corresponding .gif file, tags, etc.)

  2. album.py: This file will define a Album class that you’ll use to represent a collection of Photo objects. It will also provide methods for getting information about the collection as a whole.

  3. manager.py: The main file that will contain your program’s main function and supporting helper functions. The code in this file will create Album and Photo objects to "put everything together" and let the user manage and view the photos in the album according to the specifications below.

1.1. Testing

  • As always, you should test your program as you go.

  • Since you’re working with three files, it’s a good idea to put the tests for each class (i.e., Photo and Album) in the corresponding file.

  • Like we saw in lecture, this can be done by writing a main() method, and then calling it at the bottom using:

    if __name__ == "__main__":
        main()

    This way, your testing code will run when you execute python3 photo.py on the command line, but it won’t run when you import from photo in manager.py

1.2. The Photo Class

The photo.py file should contain a class definition for a class named Photo. The Photo class should have the following methods:

  • Constructor: __init(…​)__

    • Aside from self, the constructor should take four parameters, in this order:

      • the filename of the corresponding .gif file for this photo (a string, for example "bluey.gif").

      • the name of the person who created the photo (a string, for example "Bingo Heeler").

      • a description of the photo (a string, for example "Bluey dancing").

      • the tags describing the photo (a list of strings, for example ["bluey", "sister", "happy"]).

    • The values of the four parameters should be stored as instance variables in the object.

    • All tags in the list provided to the constructor should be converted to lowercase in order to facilitate case-insensitive searching later on.

  • get_filename(…​)

    • This method should take no parameters (other than self), and it should return the name of the .gif file for this photo.

  • get_creator(…​)

    • This method should take no parameters (other than self), and it should return a string containing the name of the person who created the photo.

  • get_description(…​)

    • This method should take no parameters (other than self), and it should return a string containing the description of the photo.

  • get_tags(…​)

    • This method should take no parameters (other than self), and it should return a list of strings that are the tags for this photo.

  • __str__(…​)

    • This method should take no parameters (other than self), and it should return a string containing the description, the name of the creator, and all of the tags, for instance "Bluey dancing, created by Bingo Heeler #bluey #sister #happy".

    • Note that the tags should be listed separately with the "#" character in front of each one.

    • This method should not print anything, just return a string.

Before moving on to the Album class, add test cases to a main function at the bottom of the photo.py file as described in the Testing section above.

Code is provided to create a Photo using the constructor. Uncomment this code and then add more tests; you may also need to create additional Photo objects.

In particular, you are expected to add tests for each of the methods you’ve written in your Photo class, and should make sure you test different cases and different behaviors for these methods. You can test __str__ by printing the object.

1.3. The Album Class

The album.py file should contain a class definition for a class named Album. The Album class should have the following methods:

  • Constructor: __init(…​)__

    • In addition to self, the constructor should take a parameter representing the name of the photo album (a string, for example "Heeler family") and also initialize a list instance variable that will later store a list of Photo objects.

  • get_name(…​)

    • This method should take no parameters (other than self) and should return the string that was passed to the constructor as the name of the album.

  • add_photo(…​)

    • This method should take one parameter (in addition to self): the Photo object to add to the album.

    • If there is already a photo in the album with the same description or the same filename as the Photo being added, this method should return False and not change the contents of the list.

    • Otherwise, the Photo should be appended to the list instance variable that was initialized in the constructor, and the method should return True.

  • get_photos(…​)

    • This method should take no parameters (other than self) and return the list of Photo objects in this album.

    • If the album has no photos, this method should return an empty list.

  • get_count(…​)

    • This method should take no parameters (other than self), and it should return the number of photos in the album (an integer).

  • get_tags(…​)

    • This method should take no parameters (other than self), and it should return a list of the distinct tags of all the Photo objects in the album.

    • The tags in the list that is returned should be sorted in alphabetical order. HINT! This is just a list of strings, so you can use the built-in sort capabilities and do not need to implement your own sorting function for this.

    • For instance, if one Photo in the album had the tags ["bluey", "sister", "happy"] and the other had ["happy", "bluey", "bingo"], then this method would return ["bingo", "bluey", "happy", "sister"].

  • __str__()

    • This method should take no parameters (other than self), and it should return a string including the name of the album, the number of photos in the album, and the distinct tags of the photos, for example "Heeler family (2 photos) #bingo #bluey #happy #sister".

    • If there is only one photo in the album, it is okay if that part of the string reads "(1 photos)", i.e. you do not need to correct the grammar.

    • This method should not print anything, just return a string.

Before moving on, add test cases to a main function at the bottom of the album.py file as described in the Testing section above.

Code is provided to create an Album using the constructor and then to add two Photo objects. Uncomment this code and then add more tests; you may also need to create additional Album and Photo objects.

In particular, you are expected to add tests for each of the methods you’ve written in your Album class, and should make sure you test different cases and different behaviors for these methods. You can test __str__ by printing the object.

1.4. Creating Objects from a File

The file manager.py contains an initialize function that is called by main.

Implement the initialize function so that it reads from a file containing information about photos, and then creates and returns an Album object that contains a list of Photo objects.

The first argument is the name of the file that contains information about photos. Each line of the file lists the photo’s filename, creator, description, and tags, all separated by commas, like this:

/usr/local/doc/images/bluey.gif,Bingo Heeler,Bluey dancing,bluey,sister,happy
/usr/local/doc/images/family.gif,Nana,Heeler family,happy,bluey,Bingo
/usr/local/doc/images/hello.gif,Chilli Heeler,Bluey and Bingo,bingo,Bluey,travel
/usr/local/doc/images/parents.gif,Bingo Heeler,Mummy and daddy,happy
/usr/local/doc/images/letsgo.gif,Chilli Heeler,In the car,bingo,bluey,letsgo,travel

The second argument to the initialize function is the name of album, which should be passed to the Album constructor.

Your initialize function should create an Album object; open this file for reading; create a Photo object for each line; and then use the Album object’s add_photo method to add the Photo to the album.

Note that the Photo constructor expects a list as the argument specifying the tags, so you will need to convert what you read from the file into a list, keeping in mind that there may be an arbitrary number of tags per photo, or none at all. You do not need to convert the tags to lowercase since the Photo constructor should do that.

You can assume that the file is well-formatted and that each photo has a distinct filename and description.

Be sure your initialize function works correctly before proceeding. If you use the provided file containing the photo data and print the Album that is returned, you should see "Heeler family (5 photos) #bingo #bluey #happy #letsgo #sister #travel".

1.5. Submitting Your Code for Part 1

When you have completed this part of the assignment, submit your code before the deadline using handin21.

Once you’ve done that, you’re ready to move on to Part 2! You do not need to wait for Instructor feedback or for the Part 1 deadline to pass before completing the rest of the assignment. Let’s do this!

2. Part 2. Managing and Viewing Photos

To complete this assignment, you will modify the manager.py program so that it allows the user to manage and view photos in an album, using the classes you created in Part 1. You should not need to change those classes as you complete Part 2, though you may if you feel that it is necessary.

This part of the lab is due Friday, May 2, which is the last day of classes. Be sure you run handin21 to submit the finished code prior to that deadline!

Your full program will provide the user with five features related to managing an album and viewing the photos. Here is an example of what the final running program will look like.

2.1. Main Menu

After using the initialize function from Part 1 to read in the data files and create an Album object, the program should show a menu of options as follows:

================== Menu ======================
1: list all photos
2: add a photo
3: search photos by tag
4: view a photo
5: slideshow
6: quit

It should then prompt the user to choose one of the six options.

If the user enters a value between 1 and 5, the program should invoke a function that performs the corresponding feature as described below. If they enter 6, the program should end.

You can assume that the user enters an int, but need to check that it is between 1 and 6. For any other numerical input, the program should display "Invalid selection" and show the prompt again.

2.2. Menu Option 1: List All Photos

If the user selects menu option 1, the program should show the description, creator, and tags for all the photos in the album.

For instance, if the user selects option 1 right after the program has read the provided input file (without adding any new photos), the output should be as follows:

Here are the photos in this album:
Bluey and Bingo, created by Chilli Heeler #bingo #bluey #travel
Bluey dancing, created by Bingo Heeler #bluey #sister #happy
Heeler family, created by Nana #happy #bluey #bingo
In the car, created by Chilli Heeler #bingo #bluey #letsgo #travel
Mummy and daddy, created by Bingo Heeler #happy

The photos must be listed in alphabetical order by their description. You may use any sorting algorithm you like, but in this case you cannot simply call sort() on the list of Photo objects in the album, since it will not know how to sort them by their descriptions.

If there are no photos in the album, the program should display "No photos in the album".

After listing the photos, the program should then return to the Main Menu.

2.3. Menu Option 2: Add a Photo

If the user selects menu option 2, the program should ask the user to provide information to create a new photo and then add it to the album.

The program must prompt the user to enter the name of the file, the name of the person who created the photo, the description of the photo, and the tags for the photo, in that order.

The tags should be read all at once in a whitespace separated string. Keep in mind, though, that the Photo constructor expects the tags to be in a list. So if the user enters "muffin cousin puppy", this needs to be converted to the list ["muffin", "cousin", "puppy"] before being passed to the constructor. You do not need to convert the tags to lowercase, since the Photo constructor is expected to do that.

Once the values have been read from the user, the program should create a new Photo object and use the Album object’s add_photo() method to attempt to add it to the album. If add_photo() returns True, the program should display "Successfully added photo."; otherwise, it should display "Could not add photo to album." It should then return to the Main Menu.

To test this, select this option and then enter:

  • "muffin.gif" as the filename

  • "Bluey" as the name of the creator

  • "Muffin losing it" as the description

  • "muffin cousin puppy" as the tags

If everything is successful, you should see the following when you test menu option 1, assuming you did not restart the program (new values highlighted in bold):

Here are the photos in this album:
Bluey and Bingo, created by Chilli Heeler #bingo #bluey #travel
Bluey dancing, created by Bingo Heeler #bluey #sister #happy
Heeler family, created by Nana #happy #bluey #bingo
In the car, created by Chilli Heeler #bingo #bluey #letsgo #travel
Muffin losing it, created by Bluey #muffin #cousin #puppy
Mummy and daddy, created by Bingo Heeler #happy

2.4. Menu Option 3: Search Photos by Tag

If the user selects menu option 3, the program should display the tags of all photos in the album and then ask the user to enter a tag.

It should then list the descriptions of all of the photos in the album that have that tag.

For instance, if the user selects option 3 right after the program has read the provided input file (without adding any new photos), it should show Here are the tags: bingo bluey happy letsgo sister travel and then ask the user to enter a tag.

If the user then specifies "bluey" as the tag to search for, the output should be as follows:

Here are the photos with that tag:
Bluey and Bingo
Bluey dancing
Heeler family
In the car

The photos should be listed in alphabetical order by their description.

Searching for tags should be case-insensitive, meaning that the above results should be shown if the user enters "bluey" or "Bluey" or "BLUEY".

If there are no photos in the album with the specified tag, the program should print "No photos in the album with that tag".

After listing the photos, the program should then return to the Main Menu.

2.5. Menu Option 4: View a Photo

If the user selects menu option 4, the program should show an enumerated list (starting at 1) of the descriptions of the photos in the album and then prompt the user to select one of the photos by using the corresponding number.

For instance, if the user selects option 4 right after the program has read the provided input file (without adding any new photos), the prompt should be as follows:

1: Bluey and Bingo
2: Bluey dancing
3: Heeler family
4: In the car
5: Mummy and daddy
Choose a photo:

Assuming the user enters a value between 1 and the number of photos, the program should then display that photo using the display_image function that is provided in manager.py.

Note that the argument to the display_image should be the name of the file containing the image.

If you have correctly implemented the Photo class and pass the filename of the selected photo to display_image, you should see that image appear in a popup window. This should work for the five photos that are listed in the "photos.txt" file that is read by your initialize function, and should also work if you add a new photo (menu option 2) with "muffin.gif" as the filename, since that file is included in the directory with your code.

The display_image function will wait for the user to click the photo or close the window before continuing; you do not have to handle this part. After it returns, the program should return to the Main Menu.

You may assume that the user will enter an int when prompted for the photo number, but if they enter a number less than 1 or greater than the number of photos, the program should show "Please enter a valid number." and prompt again until they enter a valid number.

2.6. Menu Option 5: Slideshow

If the user selects menu option 5, the program should iterate over all photos in the album and use the display_image function to show each photo one at a time.

As mentioned above, the display_image function will wait for the user to click the photo or close the window before returning.

After the last photo is shown, the program should return to the Main Menu.

NOTE! You will notice some flickering when proceeding through the album since the window showing the photo will close and then reopen each time the user clicks it. This behavior is okay! You may attempt to fix this but are not required to do so.

3. Requirements

The code you submit for labs is expected to follow good style practices, and to meet one of the course standards, you’ll need to demonstrate good style on six or more of the lab assignments across the semester. To meet the good style expectations, you should:

  • Write a comment that describes the program as a whole using a comment at the top of the file.

  • All programs should have a main() function.

  • Use descriptive variable names.

  • Write code with readable line length, avoiding writing any lines of code that exceed 80 columns.

  • Add comments to explain complex code segments (as applicable).

  • Write a comment for each function (except main) that describes its purpose, parameters and return value. This includes methods in classes as well as other functions.

In addition, you’ll need to demonstrate good top-down design practices on two or more lab assignments across the semester. To meet the top-down design expectations, you should:

  • Have a main() function that serves as an outline for your program.

  • Your main() function should call other functions that implement subtasks.

  • The functions that are called by main() should be well-named and have good comments explaining their purpose, parameters (including their types), and return value (including its type).

Your program should meet the following requirements:

  1. Your implementation of the Photo class should be in the file photo.py and its methods should behave as described in section 1.2.

  2. Your photo.py file should contain tests of all the methods you’ve written in that class.

  3. Your implementation of the Album class should be in the file album.py and its methods should behave as described in section 1.3.

  4. Your album.py file should contain tests of all the methods you’ve written in that class.

  5. Your manager.py file should contain an initialize function that correctly creates Photo objects and adds them to an Album as described in section 1.4.

  6. Your manager.py program should contain functionality for the Main Menu as described in section 2.1, including prompting for a valid selection from the menu.

  7. Your manager.py program should contain functionality for listing all photos in the album as described in section 2.2, including the correct output (sorted by description) and displaying a message if the album contains no photos.

  8. Your manager.py program should contain functionality for adding a photo to the album as described in section 2.3, including displaying an appropriate message depending on whether the photo could be added or not.

  9. Your manager.py program should contain functionality for searching for photos with a specified tag as described in section 2.4, including case-insensitive search.

  10. Your manager.py program should contain functionality for viewing a single photo as described in section 2.5, including prompting for a valid photo number.

  11. Your manager.py program should contain functionality for viewing all photos in a slideshow as described in section 2.6.

4. General Hints & Tips

  • Use what you know about good Top-Down Design and incremental implementation and testing to implement the program for Part 2. Do not try to complete it all in one sitting! Rather, implement and test each piece a little bit at a time.

  • Refer to in-class code for examples. You likely need to refer to code from many different weeks depending on the example you are looking for, as we’ve covered functions, while loops, formatted output, file I/0, searching, sorting, strings, lists, etc. over many weeks.

  • Use the print() function to add debug statements to help you see what your program is doing as you try to find and fix bugs (and be sure to remove these after you fix the bugs, though!).

  • You can reuse a lot of functions that you wrote in previous labs, e.g. for getting inputs in a certain range, and use other functions as starting points for implementing similar functionality in this assignment.

  • Ask for help if you need it! Go to office hours and Ninja sessions, and post questions on Ed if you get stuck.

Answer the Questionnaire

After each lab, please complete the short Google Forms questionnaire. Please select the right lab number (Lab 11) from the dropdown menu on the first question.

Once you’re done with that, you should run handin21 again.

Submitting lab assignments

Remember to run handin21 to turn in your lab files! You may run handin21 as many times as you want. Each time it will turn in any new work. We recommend running handin21 after you complete each program or after you complete significant work on any one program.

Logging out

When you’re done working in the lab, you should log out of the computer you’re using.

First quit any applications you are running, including your vscode editor, the browser and the terminal. Then click on the logout icon (logout icon or other logout icon) and choose "log out".

If you plan to leave the lab for just a few minutes, you do not need to log out. It is, however, a good idea to lock your machine while you are gone. You can lock your screen by clicking on the lock xlock icon. PLEASE do not leave a session locked for a long period of time. Power may go out, someone might reboot the machine, etc. You don’t want to lose any work!