Skip to content
This repository was archived by the owner on Jun 9, 2021. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions students/mattmaeda/mailroom_app/LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
License
-------

Licensed under the Creative Commons Attribution-ShareAlike 4.0 International Public License.

https://creativecommons.org/licenses/by-sa/4.0/legalcode
1 change: 1 addition & 0 deletions students/mattmaeda/mailroom_app/README.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The mailroom app for python class
10 changes: 10 additions & 0 deletions students/mattmaeda/mailroom_app/bin/mailroom
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env python

"""
Entry point for the mailroom app
"""

import mailroom.cli

if __name__ == "__main__":
mailroom.cli.main()
11 changes: 11 additions & 0 deletions students/mattmaeda/mailroom_app/mailroom/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env python

"""
mailroom package
"""

from pathlib import Path

__version__ = "0.1.1"

DATA_DIR = Path(__file__).parent / "data"
91 changes: 91 additions & 0 deletions students/mattmaeda/mailroom_app/mailroom/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#!/usr/bin/env python
"""
Interface to mailroom package
"""

from __future__ import print_function

import sys
import math
import logging
from mailroom import model, DATA_DIR

logging.getLogger(__name__)

MENU = """
Choose an option:

1. Send Thank You
2. Create Report
3. Quit

> """

DB = model.DonorDB.load_from_file(DATA_DIR / "sample_data.json")

def get_selection():
""" Display menu option and get user input """
logging.info("Get menu selections")
selection = input(MENU)
logging.info("User selected option %s", selection)
return selection.strip()


def send_thank_you():
""" Record a donation and send thank you message """
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would consider breaking this into two functions, get_donor_name and get_donation_amount, or something like that. Your function would look something like this then:

def send_thank_you():
    donor = get_donor_name() # this function would run the while loop, add the donor to the database if it isn't there and return the name
    amount = get_donation_amount() # this function would run the second while loop, get the donation amount
    donor.add_donation(amount) 
    print(DB.send_letter(donor))

while True:
logging.debug("Getting user input for name")
name = input("Enter donor's name > ").strip()
break

while True:
amount = input("Enter a donation amount > ").strip()
try:
logging.debug("Getting user input for donation amount")
amount = float(amount)
if math.isnan(amount) or math.isinf(amount) or round(amount, 2) == 0.00:
logging.warn("User did not enter a valid amount")
raise ValueError
break
except ValueError:
print("Invalid amount '{}' entered.".format(amount))

donor = DB.get_donor(name)
if donor is None:
logging.info("Donor info collected. Adding donor")
donor = DB.add_donor(name)

donor.add_donation(amount)
print(DB.send_letter(donor))


def print_report():
""" Print out donor report """
logging.info("Print out the user report")
print(DB.get_donor_report())


def quit_program():
""" Exits program """
logging.info("Exiting program")
sys.exit(0)


def main():
""" Entry point for the entire application """
menu_dict = {"1": send_thank_you,
"2": print_report,
"3": quit_program}

while True:
selection = get_selection()
print(selection)

try:
menu_dict[selection]()
except KeyError:
print("ERROR: Selection '{}' is invalid!".format(selection))


if __name__ == "__main__":
main()
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"George Washington":[1],"John Adams":[3],"Thomas Jefferson":[3],"John Quincy Adams":[2],"James Madison":[2]}
200 changes: 200 additions & 0 deletions students/mattmaeda/mailroom_app/mailroom/model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
#!/usr/bin/env python
"""
Data model for mailroom package
"""
import json
import logging

logging.getLogger(__name__)

THANK_YOU_LETTER = """
Dear {0:s},

Thank you for your donation of ${1:.2f}.
"""

REPORT_HEADER = "{donor: <50}| {total: <12}| {num: <10}| {avg: <12}"
REPORT_LINE = "{0: <50} ${1: 12.2f}{2: 12d}{3: 14.2f}"

class Donor(object):
"""
Donor class responsible for handling donor information
"""
_donorname = None

def __init__(self, donor_name, initial_donations=None):
logging.info("Set up donor '%s'", donor_name)
self.name = donor_name
if initial_donations is None:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Preferred way of saying this is if not initial_donations:

logging.info("Donor %s has not initial donations", donor_name)
initial_donations = []
self.donations = list(initial_donations)


@property
def name(self):
""" donor name getter """
return self._donorname


@name.setter
def name(self, donor_name):
""" donor name setter
:param donor_name: donor name
:type: str
"""
self._donorname = donor_name


@property
def total_donations(self):
""" donation getter """
logging.info("Get the total donations for %s", self.name)
return sum(self.donations)


@property
def avg_donations(self):
""" get average donations """
logging.info("Get the average donations for %s", self.name)
return self.total_donations / len(self.donations)


@property
def num_donations(self):
""" get number of donations """
logging.info("Getting the number of donations for %s", self.name)
return len(self.donations)


@property
def last_donation(self):
""" get the last donation made by donor """
logging.info("Get the last donation for %s", self.name)
try:
return self.donations[-1]
except IndexError:
logging.warn("Donor %s has not made any donations", self.name)
return None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Python returns None by default.



def add_donation(self, amount):
""" add donation to the list of donations """
logging.info("Donor %s made a donation of %d", self.name, amount)
self.donations.append(amount)


class DonorDB(object):
"""
Database that holds all donor information
"""

def __init__(self, donors=None):
if donors is None:
donors = {}
self.donor_data = {d.name: d for d in donors}


def save_to_file(self, filename):
"""
output donor information to file
"""
output = {k: v.donations for k, v in self.donor_data.items()}
with open(filename, "w") as outfile:
json.dump(output, outfile)


@classmethod
def load_from_file(cls, filename):
"""
Load DB from JSON file
"""
with open(filename) as infile:
donors = json.load(infile)

return cls([Donor(*d) for d in donors.items()])


@property
def donors(self):
"""
get donation values
"""
return self.donor_data.values()


def get_donor(self, name):
"""
get donor by name

:param name: name

:return: donor object; None if not found
:rtype: Donor
"""
logging.info("Getting donor object for donor %s", name)
return self.donor_data.get(name)


def add_donor(self, name):
"""
Add new donor to DB

:param name: donor name

:return: donor object
:rtype: Donor
"""
logging.info("Adding donor %s", name)
donor = Donor(name)
self.donor_data[name] = donor
return donor


@staticmethod
def send_letter(donor):
"""
Generate thank you letter

:param donor: donor object

:return: formatted thank you letter
:rtype: String
"""
logging.info("Generating thank you letter for %s", donor.name)
return THANK_YOU_LETTER.format(donor.name, donor.last_donation)


def get_donor_report(self):
"""
Generate sorted list of donors from largest donation total to the least

:return: formatted donation report
:rtype: String
"""
logging.info("Generating the donor report")
donor_dict = {}

for donor in self.donor_data.values():
donor_dict.setdefault(donor.total_donations, []).append(donor)

report = "\n"
report += (REPORT_HEADER.format(donor="Donor Name",
total="Total Given",
num="Num Gifts",
avg="Average Gift"))
report += "\n"
report += "-" * 90
report += "\n"


for amount in reversed(sorted(donor_dict)):
for donor in donor_dict[amount]:
report += (REPORT_LINE.format(donor.name,
donor.total_donations,
donor.num_donations,
donor.avg_donations))
report += "\n"

report += "\n\n"
return report
3 changes: 3 additions & 0 deletions students/mattmaeda/mailroom_app/mailroom/test/.coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[report]
exclude_lines =
if __name__ == .__main__.:
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"George Washington":[1]}
Loading