class Individual(js.JsonSaveable): name = js.String() donations = js.List() def __init__(self, name, donations): self.name = name self.donations = donations def add_donation(self, donation): self.donations.append(donation) def number_donations(self): return int(len(self.donations)) def sum_donations(self): return sum(self.donations) def avg_donations(self): return self.sum_donations() / \ self.number_donations() def last_donation(self): return self.donations[-1] def json_format(self): setattr(self, 'donor_name', self.name) setattr(self, 'donor_donations', self.donations) return self.to_json_compat() @property def thank_you(self): """Add a donation to a donors records and print a report.""" return ( 'Thank you so much for the generous gift of ${0:.2f}, {1}!'.format( self.donations[-1], self.name))
class ClassWithList(js.JsonSaveable): x = js.Int() lst = js.List() def __init__(self, x, lst): self.x = x self.lst = lst
class Donor(js.JsonSaveable): _name = js.String() _donations = js.List() def __init__(self, name, donations=None): self._name = name if donations is None: self._donations = [] else: self._donations = donations def __str__(self): return f"{self._name} donated ${sum(self._donations):,.2f}" def __repr__(self): return f"{self._name} donated ${sum(self._donations):,.2f}" @property def name(self): return self._name @property def donations(self): return self._donations @property def total(self): return sum(self._donations) @property def num_donation(self): return len(self._donations) @property def avg_donation(self): return sum(self._donations) / len(self._donations) def new_donation(self, donation): self._donations.append(donation) def __eq__(self, other): return sum(self._donations) == sum(other._donations) def __ne__(self, other): return not sum(self._donations) == sum(other._donations) def __lt__(self, other): return sum(self._donations) < sum(other._donations) def __gt__(self, other): return not sum(self._donations) < sum(other._donations) def __le__(self, other): return sum(self._donations) >= sum(other._donations) def __ge__(self, other): return not sum(self._donations) >= sum(other._donations)
class MyClass(js.JsonSaveable): x = js.Int() y = js.Float() lst = js.List() def __init__(self, x, lst): self.x = x self.lst = lst
class DonorDB(js.JsonSaveable): _donors = js.List() def __init__(self, donors=None): if donors is None: self._donors = [] else: self._donors = donors @property def donors(self): return self._donors def add_donor(self, donor): self._donors.append(donor) def load(self): with open("Donor_DB.json", "r") as db_file: self._donors = js.from_json(db_file)._donors def save(self): with open("Donor_DB.json", "w") as db_file: db_file.write(self.to_json()) def generate_donor_list(self): output = "-" * 20 + "\nList of Donors:\n" + "-" * 20 for donor in sorted(self._donors, key=lambda donor: donor.name): output += "\n" + donor.name output += "\n" + "-" * 20 return output def build_report(self): donors = self._donors header = ["Donor Name", "Total Given", "Num Gifts", "Average Gifts"] report = "" report += "-" * 75 report += f"\n{header[0]:30}| {header[1]:15}| {header[2]:10}| {header[3]:20}\n" report += "-" * 75 for donor in sorted(self._donors, key=lambda donor: donor.total, reverse=True): report += f"\n{donor.name:30}${donor.total:>15,.2f}{donor.num_donation:>13}{donor.avg_donation:>15,.2f}" report += "\n" + "-" * 75 return report
class Donor(js.JsonSaveable): name = js.String() donations = js.List() def __init__(self, name, donations=None): if not name: raise ValueError("Donor name can not be empty") self.name = name if donations: self.donations = donations else: self.donations = [] @property def first_name(self): name_split = self.name.split() if len(name_split) >= 1: return name_split[0] @property def last_name(self): name_split = self.name.split() if len(name_split) == 1: return '' else: return ''.join(name_split[1:]) @property def donor_donations(self): """ Returns list of donor donations :return: list of donor donations """ return self.donations @property def donor_donations_sum(self): """ Returns sum of all donor donations :return: donor latest donation """ return sum(self.donations) @property def latest_donation(self): """ Returns donor latest donation :return: donor latest donation """ if self.donations: return self.donations[-1] def add_donation(self, amount): """ Adds donation to donor donations :return: """ if float(amount) <= 0: raise ValueError("donation amount can not be negative") self.donations.append(float(amount)) def generate_letter(self): """ Generate letter for donor """ return "Dear {},\n \nThank you for your generous donation {}.\n \n\n\t\tSincerely, \n\t\tLocal Charity". \ format(self.name, self.latest_donation)
class Donors(js.JsonSaveable): data_file = 'donors' donors_list = js.List() def __init__(self, donors_list=None): # list of donors objects if donors_list: self.donors_list = donors_list else: self.donors_list = [] @property def list_of_donors(self): return [donor.name for donor in self.donors_list] @property def count(self): return len(self.donors_list) def add_donor(self, donor): self.donors_list.append(donor) def get_donor(self, name): if name == "": return None for donor in self.donors_list: #print("donor is {}..".format(donor)) if donor.name == name: return donor new_donor = Donor(name, []) self.add_donor(new_donor) return new_donor def send_letters(self): """ Send letters to every one, the letters will be stored as text files on disk """ for donor in self.donors_list: file_name = donor.name + ".txt" letter = donor.generate_letter() with open(file_name, "w") as f: f.write(letter) def create_a_report(self): """ Prints donor information for all donors """ print("Donor Name | Total Given | Num Gifts | Average Gift") for donor in self.donors_list: if donor.donations: print(f"{donor.name:26} $ {sum(donor.donations):>10.2f} {len(donor.donations):9} ${sum(donor.donations)/len(donor.donations):>12.2f}") else: print("coming to else") def load_donors_list(self): temp_list = [] donations = [] for donor in donor_data: donor_obj = Donor(donor, donations) donor_obj.donations = donor_data[donor] temp_list.append(donor_obj) self.donors_list = temp_list return self.count def load_json_from_file(self): print("Loading from json") with open(self.data_file + ".json", 'r') as file_in: temp = js.from_json(file_in) self.donors_list = temp.donors_list return self.count def save_donors_list(self): donor_data = {} for donor in self.donors_list: name = donor.name donations = donor.donations donor_data[name] = donations return self.count def save_json_to_file(self): print("Saving to json file") #json_dlist = self.to_json() with open(self.data_file + ".json", 'w') as file_out: self.to_json(file_out) # file_out.write(json_dlist) return self.count def print_json(self): print(self.to_json()) def total_contribution(self): return sum([sum(d.donations) for d in self.donors_list]) def challenge(self, mul, min_donation=None, max_donation=None): if min_donation and max_donation: def func(x): return max_donation > x > min_donation elif min_donation: def func(x): return x > min_donation elif max_donation: def func(x): return x < max_donation else: def func(x): return True return Donors([Donor(donor.name, list(map(lambda x: x ** mul, filter(func, donor.donations)))) for donor in self.donors_list])
class Donors(js.JsonSaveable): """Provide a class to handle a collection of donors.""" _donors = js.List() def __init__(self, _donors): """Instantiate a Donors class object with a list of SingleDonors.""" self._donors = _donors def __iter__(self): """Make the Donors class object iterable.""" return iter(self._donors) # def __str__(self): # # Used only for debugging -- to be removed or commented out # return str([donor.__repr__() for donor in self._donors]) def __contains__(self, donor_str): """Provide a method to check if donor (expects a str) is in donors.""" return donor_str in [donor.name for donor in self._donors] def get_donor(self, name): """Given a name (str), return the donor object, or raise ValueError.""" for donor in self._donors: if donor.name == name: return donor else: raise ValueError("No such donor exists") def append(self, donor): """Append a donor object to the list of donors.""" self._donors.append(donor) def print_donor_names(self): """Print existing donor names on screen in alphabetical order.""" donors_L = [ donor.name for donor in sorted(self._donors, key=SingleDonor.sort_by_name) ] num = len(donors_L) donors_S = (("\n" + ", ".join(["{}"] * num)).format(*donors_L)) print(donors_S) def create_report(self): """Create and print a report.""" report = "" title_line_form = "\n{:<26}{:^3}{:>13}{:^3}{:>13}{:^3}{:>13}\n" title_line_text = ('Donor Name', '|', 'Total Given', '|', 'Num Gifts', '|', 'Average Gift') report += title_line_form.format(*title_line_text) report += str('- ' * 38) form_line = "\n{:<26}{:>3}{:>13}{:>3}{:>13}{:>3}{:>13}" donors_list = sorted(self._donors, key=SingleDonor.sort_by_total, reverse=True) for donor in donors_list: report += (form_line.format( donor.name, '$', sum(donor.donations), ' ', len(donor.donations), '$', round((sum(donor.donations) / len(donor.donations)), 2))) report += "\n" print(report) def challenge(self, factor, min_donation, max_donation, projection): """Return a new Donors class object or a projected sum.""" result = [ donor.challenge(factor, min_donation, max_donation, projection) for donor in self._donors ] if projection: return sum(result) else: return Donors(result) def get_total(self): """Return the aggregate amount of donations for all donors.""" return sum([sum(donor.donations) for donor in self._donors])
class SingleDonor(js.JsonSaveable): """Provide a class for a single donor.""" _donations = js.List() _name = js.String() def __init__(self, _name, _donations): """Instantiate a SingleDonor class object.""" self._name = _name if isinstance(_donations, list): self._donations = _donations elif isinstance(_donations, tuple): self._donations = list(_donations) else: self._donations = [_donations] @property def name(self): """Provide a getter method for the name property.""" return self._name @property def donations(self): """Provide a getter method for the donations property.""" return self._donations def sort_by_total(self): """Provide a sort_key for sorting by total donations.""" return sum(self._donations) def sort_by_name(self): """Provide a sort_key for sorting by name.""" return self._name def __str__(self): """Return self._name.""" return self._name def __repr__(self): """Return SingleDonor("Name", [donations]).""" if len(self._donations) == 1: return 'SingleDonor("{}", {})'.format(self._name, self._donations[0]) else: return 'SingleDonor("{}", {})'.format(self._name, self._donations) def __eq__(self, other): """Return True if names and donations are the same.""" return (self._name, self._donations) == (other.name, other.donations) def __lt__(self, other): """Provide __lt__ method used in sorting somehow, I guess.""" return ((self._name, self._donations) < (other.name, other.donations)) def add_donation(self, amount): """Add a donation.""" self._donations.append(amount) def get_last_donation(self): """Return the last donation.""" return self._donations[-1] # def challenge(self, # factor, # min_donation, # max_donation, # projection): # """Return a SingleDonor class object or the projected contribution. # # Either multiply all donations by the factor, or # multiply only those donations which are above min_donation or # below max_donation, if any of these parameters is provided, # while the remaining donations remain unchanged. # If the projection is True, return the projected contribution. # """ # # Several safeguards # if type(factor) is str or factor <= 1: # raise ValueError("Factor must be a number > 1") # elif type(min_donation) is str or type(max_donation) is str: # raise ValueError("Input must be a number") # elif min_donation is not None and max_donation is not None: # raise ValueError("Min and max must not be both defined") # # # Helper function. # def subject_to_increase(x): # """Return True if x above min /below max or if min/max undefined.""" # if min_donation is not None: # return x > min_donation # elif max_donation is not None: # return x < max_donation # else: # return True # # # The only reason for the following ugly construct is because # # I couldn't imagine how to structure my solution to use map/filter # some_donations = list(filter(subject_to_increase, self.donations)) # updated_donations = list(map(lambda x: x * factor # if x in some_donations # else x, # self.donations # ) # ) # # # The following does the same as above but looks much clearer # # updated_donations = [x * factor # # if subject_to_increase(x) # # else x # # for x in self.donations # # ] # # # projected contribution = increased donationed minus old donations # if projection: # return sum(updated_donations) - sum(self.donations) # else: # return SingleDonor(self.name, updated_donations) def multiplier_factory(self, factor, min_donation, max_donation): """Create the multiplier function for use in challenge method. Args: factor (float): the multiplier to be locked in the return function min_donation (float or None): a condition to be locked in max_donation (None or float): a condition to be locked in Returns: a function which will multiply its argument by factor subject to conditions. """ # Several safeguards if type(factor) is str or factor <= 1: raise ValueError("Factor must be a number > 1") elif type(min_donation) is str or type(max_donation) is str: raise ValueError("Input must be a number") elif min_donation is not None and max_donation is not None: raise ValueError("Min and max must not be both defined") def func(x): def subject_to_increase(x): """Decide if the donation (i.e. x) must be increased.""" if min_donation is not None: return x > min_donation elif max_donation is not None: return x < max_donation else: return True if subject_to_increase(x): return factor * x else: return x return func def challenge(self, factor, min_donation, max_donation, projection): """Return an updated SingleDonor object or a projected contribution.""" multiplier = self.multiplier_factory(factor, min_donation, max_donation) updated_donations = list(map(multiplier, self.donations)) # projected contribution = increased donations minus old donations if projection: return sum(updated_donations) - sum(self.donations) else: return SingleDonor(self.name, updated_donations)
class Donor(js.JsonSaveable): """donor giving to organization""" id = js.Int() firstname = js.String() lastname = js.String() email = js.String() _donations = js.List() _donation_id = js.Int() def __init__(self, id, firstname=None, lastname=None, email=None): """args: id (int): identification for donor. Will try to force to int when initiated or raise error. firstname (str, optional): string representing given name lastnamt (str, optional): string representing surname _donations (list): contains Donation objects from donor _donation_id (int): tracks indentification for donations""" try: self.id = int(id) except ValueError: raise ValueError('id input should be interpreted as integer') self.firstname = firstname self.lastname = lastname self.email = email self._donations = [] self._donation_id = 1 def donation_total(self): """returns the total amount the donor has donated""" if self.donations: print(self.donations) return sum([i.amount for i in self.donations]) else: return 0 def add_donation(self, amount, date=datetime.datetime.utcnow()): """adds donation for user""" self._donations.append(Donation(amount=amount, date=date, id=self._donation_id)) self._donation_id += 1 def donation_count(self): """returns count of donations""" return len(self._donations) @property def donations(self): """returns donations""" return self._donations @property def fullname(self): """returns combine first and last name""" return " ".join([self.firstname, self.lastname]) def summarize_donor(self): """provides summary tuple of donor""" return (self.id, self.fullname, self.donation_total(), self.donation_count(), self.donation_total()/self.donation_count()) def __repr__(self): return str(self.to_json_compat())
class DonorData(js.JsonSaveable): data = js.List() def __init__(self, data): self.data = data
class Donor(js.JsonSaveable): _donation = js.List() _name = js.String() def __init__(self, name, donation=None): if name == '': self._name = 'Anonymous' else: self._name = name if donation is None: self._donation = [] elif isinstance(donation, (int, float)): self._donation = [donation] else: self._donation = donation @property def name(self): return self._name @name.setter def name(self, name): self._name = str(name) @property def donation(self): return self._donation @donation.setter def donation(self, donation): if type(donation) == list: self._donation = donation else: raise TypeError("Donation has to be a list of int") def add_donation(self, donation_amount): if donation_amount >= 0: self._donation.append(donation_amount) else: raise ValueError("Donation amount must be greater than 0.") def __repr__(self): return "Donor({} : {})".format(self._name, self._donation) def __str__(self): return "Donor: {} donated {}\n".format(self._name, self._donation) def donation_occurrences(self): return len(self._donation) def total_donation_amount(self): return sum(self._donation) def average_total_donor_amount(self): try: return self.total_donation_amount() / self.donation_occurrences() except ValueError: return self._donation def stats(self): return [ self.total_donation_amount(), self.donation_occurrences(), self.average_total_donor_amount() ]
class DonorList(js.JsonSaveable): _donors = js.List() file_name = "donor_list_json_file.json" new_donors_list = js.List() def __init__(self, donors=[]): if donors is None: self._donors = [] elif isinstance(donors, Donor): self._donors = [donors] else: self._donors = donors """def save_to_json(self): target_directory = os.getcwd() target_file_path = os.path.join(target_directory, self.file_name) new_donors_list = table_dictionary.to_json() with open(target_file_path, 'w') as fp: js.to_json(new_donors_list, fp, indent=4)""" def save_to_json(self): target_directory = os.getcwd() target_file_path = os.path.join(target_directory, self.file_name) donor_to_json = table_dictionary.to_json() with open(target_file_path, "w") as fp: fp.write(donor_to_json) def load_from_json(self): target_directory = os.getcwd() target_file_path = os.path.join(target_directory, self.file_name) with open(target_file_path, 'r') as fp: donor_load = fp.read() return js.from_json(donor_load) def populate_dict(self): target_directory = os.getcwd() target_file_path = os.path.join(target_directory, self.file_name) if os.path.exists(target_file_path): abd = self.load_from_json() self._donors = abd._donors else: self.init_dict() def init_dict(self): self._donors = [ Donor('Toni Orlando', [150.00, 200.00, 100.00]), Donor('Amanda Clark', [1800.00]), Donor('Robin Hood', [1234.56, 4500.34, 765.28]), Donor('Gina Travis', [523.10, 75.00]), Donor('Mark Johnson', [850.00, 20.14]) ] @property def donors(self): return self._donors def __str__(self): d_list = '' for d in self._donors: d_list += d + '\n' return d_list def __repr__(self): d_list = 'repr' for d in self._donors: d_list += str(d) return d_list def add_donor_list(self, donor): return self._donors.append(donor) def names_only_list(self): names_list = [] for d in self._donors: names_list.append(d.name) return names_list def find_donor_history(self, name): for d in self._donors: if d.name == name: return d def display_donor_list(self): """Prints the full list of donors and corresponding donation history.""" for d in self._donors: print("{:<20}: {}".format(d.name, d.donation)) pass """def __iter__(self): return iter(self._donors)""" def stat_donor_list(self): new_list = [] for k in self._donors: sum_donations = k.total_donation_amount() total_gifts = k.donation_occurrences() average_gift = k.average_total_donor_amount() new_list.append([k.name, sum_donations, total_gifts, average_gift]) sorted_new_list = sorted(new_list, key=itemgetter(1), reverse=True) return sorted_new_list
class Donor(js.JsonSaveable): _name = js.String() _list_donations = js.List() _donation_count = js.Int() _amount = js.Float() def __init__(self, name, list_donations): self._name = name self._list_donations = list_donations self._donation_count = len(list_donations) self._amount = sum(list_donations) @property def name(self): return self._name @property def amount(self): return self._amount @amount.setter def amount(self, amount): self._amount = amount def add(self, donation_amount): self._amount += donation_amount self._donation_count += 1 self._list_donations.append(donation_amount) @property def donation_count(self): return self._donation_count @property def average(self): return self._amount / self._donation_count def get_letter_text(self, name, amount): msg = [] msg.append('Dear {},'.format(name)) msg.append( '\n\n\tThank you for your very kind donation of ${:.2f}.'.format( amount)) msg.append('\n\n\tIt will be put to very good use.') msg.append('\n\n\t\t\t\tSincerely,') msg.append('\n\t\t\t\t-The Team\n') return "".join(msg) def challenge(self, factor, min_donation=None, max_donation=None): """Get the sum of projection donation from a donor""" new_list = [] if min_donation is not None and max_donation is not None: #filter minimum & maximum new_list = list( filter(lambda donation: donation >= min_donation, self._list_donations)) new_list = list( filter(lambda donation_amount: donation_amount <= max_donation, new_list)) new_list = list(map(lambda x: x * factor, new_list)) elif min_donation is None and max_donation is not None: #filter maximum new_list = list( filter(lambda donation: donation <= max_donation, self._list_donations)) new_list = list(map(lambda x: x * factor, new_list)) elif min_donation is not None and max_donation is None: # filter minimum new_list = list( filter(lambda donation: donation >= min_donation, self._list_donations)) new_list = list(map(lambda x: x * factor, new_list)) else: # no minimum and maximum new_list = list(map(lambda x: x * factor, self._list_donations)) return sum(new_list) def __lt__(self, other): return self._amount < other._amount def __gt__(self, other): return self._amount > other._amount def __eq__(self, other): return self._amount == other._amount
class Donor(js.JsonSaveable): """ Class to store a donor record Args: full_name(str) format: <first name> [<middle_name>] <last_name> [,<suffix>] or first_name(str) middle_name(str) last_name(str) suffix(str) donation(float) amount of today's donation or donations(list) list of dicts containing donations Not typically specified ----------------------- did(str) string representation of a record UUID created(str) string representation of an utcnow isoformat timestamp with "Z" appended Usage: The class allows flexibility in how a record is set. It is allowable to create an empty instance of the class and update it piecemeal. It is also allowable to pass in all information needed to create a record using different strategies such as a full_name vs the constituent parts. In general, record manipulation is simply the name and donation. However, it is also allowable to set information that is normally auto-created such as the did, time created and entire donation list. This allows a record to be created from its repr which may have practical uses in record backup/recreation as needed. Example Use Cases: # empty donor record In [599]: d=Donor() In [600]: repr(d) Out[600]: "Donor(did='97d744de-de23-11e7-bee5-0800274b0a84', created='2017-12-11T03:30:11.077937Z' )" In [601]: # automatic parsing of full_name In [601]: d=Donor(full_name="sally q smith, iv") In [602]: repr(d) Out[602]: "Donor(did='067f51c4-de24-11e7-bee5-0800274b0a84', first_name='Sally', middle_name='Q', last_name='Smith', suffix='IV', created='2017-12-11T03:33:16.728654Z' )" In [603]: # name creation based name sub-components In [594]: d=Donor(first_name="joe", last_name="smith", donation=100) In [595]: repr(d) Out[595]: "Donor(did='7724e750-de23-11e7-bee5-0800274b0a84', first_name='Joe',last_name='Smith', donations=[{'amount': 100.0, 'date': '2017-12-11Z'}], created='2017-12-11T03:29:16.221954Z' )" In [596]: In [636]: d=Donor(full_name="john adams") In [637]: d.add_donation=1000 In [638]: repr(d) Out[638]: "Donor(did='7e93d724-de25-11e7-bee5-0800274b0a84', first_name='John',last_name='Adams', donations=[{'amount': 1000.0, 'date': '2017-12-11Z'}, {'amount': 1000.0, 'date': '2017-12-11Z'}], created='2017-12-11T03:43:47.686457Z' )" In [639]: """ created = js.String() audit = js.List() _did = js.String() _first_name = js.String() _last_name = js.String() _middle_name = js.String() _suffix = js.String() _donations = js.List() def __init__(self, did="", created="", full_name="", first_name="", last_name="", middle_name="", suffix="", donations=None, donation=None): self.__name__ = "Donor" # normally no did is passed in, so we create one if did == "": did = str(uuid.uuid1()) # create a uuid for each record self.did = did # normally, no timestamp is passed in, so we create one if created == "": created = datetime.datetime.utcnow().isoformat() + "Z" # keep track of record creation self.created = created # test to see if any of the name components are set sub_name_set = any(sub_name != "" for sub_name in (first_name, middle_name, last_name, suffix)) # we don't expect full_name and a subset to be set at the same time if full_name != "" and sub_name_set: raise ValueError("You cannot set 'full_name' and a subset of the " "name at the same time.") # set the name via full_name or via the constituent parts if full_name != "": self.full_name = full_name else: self.first_name = first_name.title() self.last_name = last_name.title() self.middle_name = middle_name.title() self.suffix = suffix.upper() if (donations is not None) and (donation is not None): raise ValueError("You cannot set 'donations' and 'donation' at " "the same time.") if donation is not None: # if a donation is passed in, initialize the donations, # then add the donation self._donations = list() self.add_donation(donation) else: if donations is not None: # TODO donations need more validation self._donations = donations else: self._donations = list() @property def did(self): """ return the donor did """ return self._did @did.setter @audit_log def did(self, value): """ set the did """ self._did = value @property def donations(self): """ print all donations """ return self._donations @donations.setter @audit_log def donations(self, value): """ allow bulk setting of donation entries """ self._donations = value @audit_log def add_donation(self, value): """ add a single donation """ today = datetime.datetime.utcnow().date().isoformat() + "Z" try: value = float(value) except ValueError: raise ValueError("Donations must be values.") self._donations.append({"amount": value, "date": today}) @property def number_donations(self): """ return the number of donations """ return len(self._donations) @property def total_donations(self): """ return a sum of the donations """ total_donations = 0 for donation in self.donations: total_donations += donation["amount"] return round(total_donations, 2) @property def average_donations(self): """ return the average donation amount """ try: return round(self.total_donations / self.number_donations, 2) except ZeroDivisionError: return 0 @property def first_name(self): """ return the first name """ return self._first_name @first_name.setter @audit_log def first_name(self, value): """ set the first name """ self._first_name = value.title() @property def middle_name(self): """ return the middle name """ return self._middle_name @middle_name.setter @audit_log def middle_name(self, value): """ set the middle name """ self._middle_name = value.title() @property def last_name(self): """ return the last name """ return self._last_name @last_name.setter @audit_log def last_name(self, value): """ set the last name """ self._last_name = value.title() @property def suffix(self): """ return the suffix """ return self._suffix @suffix.setter @audit_log def suffix(self, value): """ set the suffix """ # special case roman numbers to all caps if value.lower() in ["i", "ii", "iii", "iv", "v"]: self._suffix = value.upper() else: self._suffix = value.title() @property def informal_name(self): """ return full name minus the suffix """ return " ".join( filter(None, [self.first_name, self.middle_name, self.last_name])) @property def full_name(self): """ return the formal, full name """ # if suffix, add with a comma if self.suffix != "": suffix = ", " + self.suffix else: suffix = "" return " ".join( filter(None, [self.first_name, self.middle_name, self.last_name ])) + suffix @full_name.setter def full_name(self, full_name): """ allow the donor information to be updated via full_name as well """ # for simplicity's sake, we make the assumption a suffix will # follow a comma # capture suffix, if present suffix = "" if full_name.find(","): try: suffix = full_name.split(",")[1].strip() except Exception: pass # name with suffix removed informal_name = full_name.split(",")[0].strip() # we assume the last name is one word minus the suffix last_name = informal_name.split()[-1] # build a list with everything to the left of the last name everything_but_last = informal_name.split()[0:-1] # pull the first name off try: first_name = everything_but_last.pop(0) except IndexError: # if we get an error, they probably gave us a malformed name first_name = "" try: # pull the middle, if exists middle_name = everything_but_last.pop(0) except IndexError: middle_name = "" self.first_name = first_name self.middle_name = middle_name self.last_name = last_name self.suffix = suffix def _query_attributes(self, which_attributes=(), return_empty=True): """ return list of formatted attribute/value entries """ attributes = [] for attr in which_attributes: # for our purposes, never print internal attributes if not attr.startswith("_"): try: str_val = repr(getattr(self, attr)) # don't return empty values if they don't want them if eval(str_val) or return_empty: attributes.append(attr + "=" + str_val) except (AttributeError, SyntaxError): # we know certain attributes are not readable, so skip them pass return attributes def __repr__(self): """ Return only the (settable) attributes Return only settable attributes so eval(repr(Donor(<foo>))) == Donor(<foo>) """ which_attributes = [ "did", "first_name", "middle_name", "last_name", "suffix", "donations", "created" ] attributes = self._query_attributes(which_attributes, return_empty=False) return "Donor( " + ", ".join(attributes) + " )" def __str__(self): """ return all the non-internal attributes """ which_attributes = [] for attr in dir(self): if not attr.startswith("_"): which_attributes.append(attr) return "str(Donor(\n " + ",\n ".join( self._query_attributes(which_attributes)) + " ))"
class donor_database(js.JsonSaveable): dl = js.List() def __init__(self, donors=None): self.donor_data = {d: d for d in donors} def save_to_file(self, filename): for donor in self.donor_data.values(): name = donor.name gifts = donor.donations temp = [name, gifts] self.dl.append(temp) print(self.dl) ''' with open(filename, 'w') as outfile: json.dump(self.dl, outfile) print("\njson dump complete!") ''' with open(filename, 'w') as outfile: self.to_json(outfile) print("\nto_json complete!") @classmethod def load_from_file(cls, filename): with open(filename, 'r') as infile: obj = js.from_json(infile) print("\nfrom_json complete!") return obj @property def donors(self): return self.donor_data.values() def donor_list(self): listing = [] for donor in self.donors: listing.append(donor.name) return "\n".join(listing) def find_donor(self, name): return self.donor_data.get(name) def add_donor(self, name): print('Donor added: ' + name) donor = donor_record(name) self.donor_data[donor] = donor return donor def print_screen_report(self): print('') print('{:20}{:>15}{:>10}{:>10}'.format('Donor Name', '| Total Gifts', '| Num Gifts', '| Ave Gift')) print('-' * 55) for donor in self.donor_data.values(): name = donor.name gifts = donor.donations num_gifts = len(gifts) total_gifts = "{:.2f}".format(donor.total_donations) avg_gift = "{:.2f}".format(donor.average_donation) print('{:20}{:>15}{:>10}{:>10}'.format(name, total_gifts, num_gifts, avg_gift)) def create_individual_letters(self): try: for donor in self.donor_data.values(): name = donor.name total_gifts = donor.total_donations file_name = name.replace(" ", "_") + ".txt" my_file = open(file_name, "w") my_file.write(gen_letter_body(name, total_gifts)) my_file.close() except IOError: return ("\n" + "File error!") return ("*** Files saved! ***")