CS21 Lab 11: Classes and Objects
Part 1 due Saturday, April 27, by 11:59pm
Part 2 due Saturday, May 4, by 11:59pm
Goals
The goals for this lab assignment are:
-
Practice defining your own Python classes
-
More practice working with a list of objects
-
More practice programming with file I/O, and menu-driven program
-
More practice with top down design, functions, and incremental implementation and testing
Getting and Working on Lab11 Code
Make sure you are working in the correct directory when you edit and run lab 11 files.
Run update21
to get the starting point code of the next lab assignment.
This will create a new ~/cs21/labs/11
directory for your lab work.
Edit and run Lab 11 programs from within your cs21/labs/11
directory.
Make sure all programs are saved to your cs21/labs/11
directory! Files outside that directory will not be graded.
$ update21 $ cd ~/cs21/labs/11 $ pwd /home/username/cs21/labs/11 $ ls (should see your program files here)
Then edit and run program files in your ~/cs21/labs/11
directory:
$ code filename.py $ python3 filename.py
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 have a top-level comment! Please see our function example page if you are confused about writing function comments.
Overview
Organizations such as Kickstarter and GoFundMe provide an opportunity for individuals to raise money for a cause, event, charity, etc. by accepting online financial contributions.
They do so by creating a fund, which has a target that is the amount of money they are hoping to raise. Then, individuals can make a donation to the fund, which in some cases may even exceed its target.
In this assignment you will write a program goKickstartMe.py
that reads in
data about funds and donations from a file, and then allows the user to perform
various functions from a menu of options, for instance listing a contributor’s
donations to all funds, or making a donation to a fund.
You will complete this lab in two parts:
-
In Part 1, you will define Python classes to represent a donation and a fund, and write a function to read data from a file. This part of the assignment is due Saturday, April 27.
-
In Part 2, you will write a program that uses these classes to perform various operations. This part of the assignment is due Saturday, May 4.
Be sure you read through the entire assignment description before starting!
1. Part 1. Donation
and Fund
Classes; Reading from a File (due April 27)
Start your program by creating two Python classes as defined below, and then writing code to populate a list. Be sure you test the various functions in these classes before proceeding to Part 2.
1.1. Donation
Class
The Donation
class represents a single donation to one of the funds and is defined in donation.py
.
1.1.1. Instance Variables
Complete the implementation of the Donation
class so that it contains the following data (or "instance variables"):
-
contributor
(string) - the name of the person who made the donation -
date
(string) - the date on which the donation was made -
amount
(float) - the amount of money that was donated
1.1.2. Methods
The Donation
class should define the following methods:
-
__init__
- the constructor that initializes the values forcontributor
,date
, andamount
, in that order -
getContributor
- returns the value of thecontributor
instance variable -
getDate
- returns the value of thedate
instance variable -
getAmount
- returns the value of theamount
instance variable -
__str__
- returns a string that contains thecontributor
,date
, andamount
, e.g. "Bluey donated $25.32 on 2024-04-05".-
Note that the
amount
should be displayed to two digits of precision and should have a dollar sign in front of it.
-
1.1.3. Testing Your Implementation
The code we have provided in donation.py
has a main
function that provides a simple test for your Donation
class implementation:
def main():
donation = Donation("Bluey", "2024-04-05", 25.32)
print(donation.getContributor())
print(donation)
if __name__ == '__main__':
main()
When you run your code, you should see the following:
$ python3 donation.py
Bluey
Bluey donated $25.32 on 2024-04-05
Modify the main
function so that it:
-
prints the result of calling
donation.getDate()
; it should print "2024-04-05" -
prints the result of calling
donation.getAmount()
; it should print 25.32 -
creates a second
Donation
object with different values, then displays the result of callinggetContributor()
,getDate()
, andgetAmount()
on that object; those values should all be correct, based on the values you used to create the object
Be sure your Donation
class is working correctly before proceeding!
1.2. Fund
Class
The Fund
class represents a fund to which contributors can donate and is defined in the file fund.py
.
1.2.1. Instance Variables
Complete the implementation of the Fund
class so that it contains the following instance variables:
-
name
(string) - a short name or title for this fund -
target
(float) - the amount of money that the fund is trying to raise -
total
(float) - the total amount of money that has been donated to the fund so far -
donations
(list ofDonation
objects) - a list containing each of the individual donations
1.2.2. Methods
The Fund
class should define the following methods:
-
__init__
- the constructor that initializes the values forname
andtarget
, in that order. The constructor should initializetotal
to 0 and should setdonations
to an empty list; these should not be arguments to the constructor. -
getName
- returns the value of thename
instance variable -
getTarget
- returns the value of thetarget
instance variable -
getTotal
- returns the value of thetotal
instance variable -
getDonations
- returns the value of thedonations
instance variable, i.e. the entire list ofDonation
objects -
__str__
- returns a string that contains thename
,target
, andtotal
, e.g. "Toys; target: $50.00; current total: $22.50 (45% reached)".-
Note that the
target
andtotal
should be displayed to two digits of precision and should have a dollar sign in front of them -
Also note that the string should contain the percentage of the target that has been reached, i.e.
total
/target
, shown as an int with a percentage sign after it. -
Hint! You can display a percentage sign using
%%
in the template/format string.
-
The Fund
class should also define a donate
method as follows:
-
The method should take a
contributor
parameter as a string, and anamount
parameter as a float. -
If the
contributor
is an empty string, or if theamount
is zero or negative, the method should return False. -
Otherwise, the method should do the following, in this order:
-
Use the
contributor
andamount
parameters to create a newDonation
object. For theDonation
object’sdate
, usestr(date.today())
, which will create a string representing today’s date. -
Add the new
Donation
object to thedonations
instance variable, i.e. thisFund
object’s list ofDonations
-
Use the
amount
to update theFund
object’stotal
-
Return True
-
1.2.3. Testing Your Implementation
The code we have provided in fund.py
has a main
function that provides a simple test for your Fund
class implementation:
def main():
fund = Fund("Toys", 50.0)
print("name: " + fund.getName())
print("target: %f" % (fund.getTarget()))
if __name__ == '__main__':
main()
Then you can run the code and should see the following:
$ python3 fund.py
name: Toys
target: 50.000000
Modify the main
function so that it:
-
Calls the
donate
method on the object with a valid contributor (i.e., a non-empty string) and valid amount (i.e., a positive number). The method should returnTrue
andfund.getTotal()
should show that the total now equals that value. -
Calls the
donate
method on the object with a valid contributor but a negative amount. The method should returnFalse
andfund.getTotal()
should show that the total has not been updated. -
Calls the
donate
method on the object with an empty string for the contributor and a positive number for the amount. The method should returnFalse
andfund.getTotal()
should show that the total has not been updated. -
Calls
print(fund)
to check that the__str__
method is producing the correct string.
Be sure your Fund
class is working correctly before proceeding!
1.3. Reading in Data
The file goKickstartMe.py
contains an initialize
function that is called by main
.
Implement the initialize
function so that it reads from the files containing information
about funds and donations, and then creates and returns a list of Fund
objects that are
populated with their Donations
.
Two small input files have been provided for you; their names are listed in the main
function.
Larger input files are also available and you should test your program with them before submitting Part 2, below.
The first argument to the initialize
function is the name of the file that contains information
about funds. Each line of the file lists the fund’s name (string) and target (float), separated by a comma,
like this:
Toys,50.0
Clothes,250.0
School Supplies,100.0
Your initialize
function should open and read this file, create a Fund
object for each line, and then add
it to the list that this function will return.
You can assume that the file is well-formatted and that each fund has a distinct name.
After reading in the file of funds, your initialize
function should read the file that contains
information about donations. Each line of the file lists the fund’s name (string), the contributor’s
name (string), and the donation amount (float), separated by a comma, like this:
Toys,Bluey,22.50
Clothes,Bingo,20.50
School Supplies,Chilli,15
Toys,Bandit,30.0
Clothes,Bluey,11.0
Clothes,Chilli,25.0
Clothes,Bluey,40.00
Note that the initialize
function should not create Donation
objects for each line, but rather
should use this information to call the donate
method on the appropriate Fund
object in the list
that the function has already created.
Although you can assume that the file is well-formatted, the initialize
function should ignore any lines
that have bad or missing information. For instance, the last three lines of the file we have provided
look like this:
Bad Fund,Bluey,50.0
Toys,,100.0
Toys,Bingo,-40.0
The first of these indicates a fund that does not exist; the next is missing the contributor name; and the last one has a negative donation amount. No Donation
objects should be created for these entries (hint! some of this error handling has already been done in your Fund
class!).
The initialize
function should then return the list of Fund
objects that have been created and updated with their donations. You should print these out to make sure that the list has the correct Fund
objects, and that each Fund
has the correct Donation
objects.
1.4. 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 Funds and Donations (due May 4)
To complete this assignment, you will modify the goKickstartMe
program so that it allows the user to view and manage funds and donations, using the classes you created in Part 1.
2.1. Main Menu
After using the initialize
function from Part 1 to read in the data files and create a list of Fund
objects, the program should show a menu of options as follows:
================== Menu ======================
1: list all funds
2: create a new fund
3: list donations for a fund
4: make a donation
5: list donations for a contributor
6: quit
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 menu again.
2.2. Option 1: List All Funds
If the user selects menu option 1, the program should list all of the funds in the funds
list.
For instance, if the user selects option 1 right after the program has read the two input files (without creating any new funds), the output should be as follows:
1: Clothes; target: $250.00; current total: $96.50 (38% reached)
2: School Supplies; target: $100.00; current total: $15.00 (15% reached)
3: Toys; target: $50.00; current total: $52.50 (105% reached)
The funds should be enumerated starting with 1 and should be listed in alphabetical order, which means that you will have to sort them.
To help you with this, the goKickstartMe.py
code has a sort
function that implements selection sort; you can use this as a foundation for your solution.
Keep in mind that you are sorting the list of Funds
using the value returned by each object’s getName()
method, so you will need to modify this code to get it to sort the list correctly.
If there are no Fund
objects in the list, the program should print "There are no funds".
After listing the funds, the program should then return to the Main Menu.
2.3. Option 2: Create a New Fund
If the user selects menu option 2, the program should ask the user to provide information to create a new fund and then add it to the funds
list.
The program must prompt the user to enter the new fund’s name and target value. It should handle illegal values as follows:
-
If the name is an empty string, the program should display "Name cannot be empty" and then continue to prompt the user to enter the name again until they enter a non-empty string.
-
If there is already a fund with the same name, the program should display "A fund with that name already exists" and then continue to prompt the user to enter the name again until they enter name of a fund that doesn’t already exist. Names can be considered case-sensitive, so "Toys" and "TOYS" would be considered different names.
-
If the target value is zero or negative, the program should display "Target value must be positive" and then continue to prompt the user to enter the target again until they enter a positive number.
Once valid name and target values have been read from the user, the program should create a new Fund
object and add it to the funds
list. It should then return to the Main Menu.
2.4. Option 3: List Donations for a Fund
If the user selects menu option 3, the program should ask the user to select one of the funds, and then should list all of the donations for that fund.
The program should first list the names of the funds in alphabetical order and enumerate them starting with 1 (Hint! reuse parts of your solution to Option 1 for this!).
It should then prompt the user to select one of the funds by its number, i.e. the number on the left of the listing. If the user enters a number less than 1 or larger than the number of funds, the program should display "That is an invalid choice" and then continue to prompt the user to enter the value again until they enter a valid number.
Once a valid value has been read from the user, the program should list the donations for the corresponding Fund
object in the funds
list.
For instance, if the user selects option 3 right after the program has read the two input files (without creating any new funds or making any donations), the output should be as follows (user input in bold; the donation dates should reflect today’s date):
Here are the funds:
1: Clothes
2: School Supplies
3: Toys
Please select a fund: 4
That is an invalid choice
Please select a fund: 0
That is an invalid choice
Please select a fund: 1
Here are the donations:
Bingo donated $20.50 on 2024-04-05
Bluey donated $11.00 on 2024-04-05
Chilli donated $25.00 on 2024-04-05
Bluey donated $40.00 on 2024-04-05
Note that:
-
The donations can be listed in whatever order they are stored in the
Fund
object’sdonations
list: they do not need to be sorted. -
You should invoke the
Donation
object’s__str__
function by passing theDonation
to theprint
function, i.e.print(donation)
is the same asprint(donation.__str__())
). -
If there are no donations in this fund, the program should print "There are no donations for this fund".
After listing the donations, the program should then return to the Main Menu.
2.5. Option 4: Make a Donation
If the user selects menu option 4, the program should ask the user to select one of the funds, and then should allow the user to enter information about a new donation to that fund.
The program should first list the names of the funds in alphabetical order and enumerate them starting with 1, as in Option 3.
It should then prompt the user to select one of the funds by its number, i.e. the number on the left of the listing. If the user enters a number less than 1 or larger than the number of funds, the program should display "That is an invalid choice" and then continue to prompt the user to enter the value again until they enter a valid number.
The program must then prompt the user to enter the contributor’s name and donation amount. It should handle illegal values as follows:
-
If the name is an empty string, the program should display "Name cannot be empty" and then continue to prompt the user to enter the name again until they enter a non-empty string.
-
If the donation amount is zero or negative, the program should display "The donation amount must be positive" and then continue to prompt the user to enter the amount again until they enter a positive number.
Once valid name and amount values have been read from the user, the program should invoke the donate
method on the appropriate Fund
object from the funds
list. If the donate
method returns True, it should print "Thank you for your donation!". It should then return to the Main Menu.
2.6. Option 5: List Donations for a Contributor
If the user selects menu option 5, the program should ask the user to enter the name of a contributor, and then list all of that contributor’s donations.
The program should first prompt the user to enter a contributor’s name. If the name is an empty string, the program should display "Name cannot be empty" and then continue to prompt the user to enter the name again until they enter a non-empty string.
The program should then list all of that contributor’s donations for all funds.
For instance, if the user selects option 5 right after the program has read the two input files (without creating any new funds or making any donations), the output should be as follows (user input in bold; the donation dates should reflect today’s date):
Enter the contributor's name: Bluey
Here are the donations:
$22.50 to Toys on 2024-04-05
$11.00 to Clothes on 2024-04-05
$40.00 to Clothes on 2024-04-05
Bluey has made 3 donations for a total of $73.50
Note that:
-
You do not need to display the donations in any particular order.
-
For each donation, you should indicate the donation amount, the name of the fund, and the donation date.
After displaying the individual donations, the program should display the number of donations and the total amount for that contributor. If the contributor has not made any donations, the program should display "The contributor has not made any donations". It should then return to the Main Menu.
2.7. Quitting the Program
If the user enters 6 at the Main Menu prompt, the program should print "Good bye!" and end.
2.8. Testing with Large Input Files
Before you submit your code, be sure that you are able to run the entire program and use all the features with the two large input files that we have provided.
Modify your main()
function so that you set the names of the funds_file
and donations_file
accordingly:
funds_file = "/usr/local/doc/funds.txt"
donations_file = "/usr/local/doc/donations.txt"
Then run the program and try out all of the menu options.
The link below is output from an example run of a working program that chooses each menu option, some more than one time. Be sure your code works similarly:
Once you’ve checked your work, you’re ready to submit your code! Congratulations on finishing the final lab of the course!
3. Optional Challenge Question
As an optional, no-credit, just-for-fun (and more practice) challenge, implement the following feature:
If the user selects menu option 7, the program should ask the user to select one of the funds, and then show the aggregate donations by contributor, i.e. the donations to that fund grouped by the contributor.
The program should first list the funds in alphabetical order and enumerate them starting with 1, as in Options 3 and 4.
It should then prompt the user to select one of the funds by its number, i.e. the number on the left of the listing. If the user enters a number less than 1 or larger than the number of funds, the program should display "That is an invalid choice" and then continue to prompt the user to enter the value again until they enter a valid number.
Then, for each contributor who has a made a donation to that fund, the program should list the contributor’s name, the number of donations, and the total amount of all their donations. The output should be organized alphabetically by the contributors' names.
For instance, assume a fund has had the following donations:
-
Tia donated $40
-
Andy donated $25
-
Chris donated $10
-
Andy donated $30
-
Tia donated $50
-
Andy donated $10
The output should then indicate (in this order):
-
Andy has made 3 donations for a total of $65
-
Chris has made 1 donation for a total of $10
-
Tia has made 2 donations for a total of $90
If the fund has not had any donations, the program should display "There are no donations to this fund".
After displaying the output, the program should then return to the Main Menu.
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.
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 ( or ) 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 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!