|  | 
|  | 1 | +#!/usr/bin/env python | 
|  | 2 | +""" | 
|  | 3 | +Data model for mailroom package | 
|  | 4 | +""" | 
|  | 5 | +import json | 
|  | 6 | +import logging | 
|  | 7 | + | 
|  | 8 | +logging.getLogger(__name__) | 
|  | 9 | + | 
|  | 10 | +THANK_YOU_LETTER = """ | 
|  | 11 | +Dear {0:s}, | 
|  | 12 | +
 | 
|  | 13 | +Thank you for your donation of ${1:.2f}. | 
|  | 14 | +""" | 
|  | 15 | + | 
|  | 16 | +REPORT_HEADER = "{donor: <50}| {total: <12}| {num: <10}| {avg: <12}" | 
|  | 17 | +REPORT_LINE = "{0: <50} ${1: 12.2f}{2: 12d}{3: 14.2f}" | 
|  | 18 | + | 
|  | 19 | +class Donor(object): | 
|  | 20 | +    """ | 
|  | 21 | +    Donor class responsible for handling donor information | 
|  | 22 | +    """ | 
|  | 23 | +    _donorname = None | 
|  | 24 | + | 
|  | 25 | +    def __init__(self, donor_name, initial_donations=None): | 
|  | 26 | +        logging.info("Set up donor '%s'", donor_name) | 
|  | 27 | +        self.name = donor_name | 
|  | 28 | +        if initial_donations is None: | 
|  | 29 | +            logging.info("Donor %s has not initial donations", donor_name) | 
|  | 30 | +            initial_donations = [] | 
|  | 31 | +        self.donations = list(initial_donations) | 
|  | 32 | + | 
|  | 33 | + | 
|  | 34 | +    @property | 
|  | 35 | +    def name(self): | 
|  | 36 | +        """ donor name getter """ | 
|  | 37 | +        return self._donorname | 
|  | 38 | + | 
|  | 39 | + | 
|  | 40 | +    @name.setter | 
|  | 41 | +    def name(self, donor_name): | 
|  | 42 | +        """ donor name setter | 
|  | 43 | +        :param donor_name: donor name | 
|  | 44 | +        :type: str | 
|  | 45 | +        """ | 
|  | 46 | +        self._donorname = donor_name | 
|  | 47 | + | 
|  | 48 | + | 
|  | 49 | +    @property | 
|  | 50 | +    def total_donations(self): | 
|  | 51 | +        """ donation getter """ | 
|  | 52 | +        logging.info("Get the total donations for %s", self.name) | 
|  | 53 | +        return sum(self.donations) | 
|  | 54 | + | 
|  | 55 | + | 
|  | 56 | +    @property | 
|  | 57 | +    def avg_donations(self): | 
|  | 58 | +        """ get average donations """ | 
|  | 59 | +        logging.info("Get the average donations for %s", self.name) | 
|  | 60 | +        return self.total_donations / len(self.donations) | 
|  | 61 | + | 
|  | 62 | + | 
|  | 63 | +    @property | 
|  | 64 | +    def num_donations(self): | 
|  | 65 | +        """ get number of donations """ | 
|  | 66 | +        logging.info("Getting the number of donations for %s", self.name) | 
|  | 67 | +        return len(self.donations) | 
|  | 68 | + | 
|  | 69 | + | 
|  | 70 | +    @property | 
|  | 71 | +    def last_donation(self): | 
|  | 72 | +        """ get the last donation made by donor """ | 
|  | 73 | +        logging.info("Get the last donation for %s", self.name) | 
|  | 74 | +        try: | 
|  | 75 | +            return self.donations[-1] | 
|  | 76 | +        except IndexError: | 
|  | 77 | +            logging.warn("Donor %s has not made any donations", self.name) | 
|  | 78 | +            return None | 
|  | 79 | + | 
|  | 80 | + | 
|  | 81 | +    def add_donation(self, amount): | 
|  | 82 | +        """ add donation to the list of donations """ | 
|  | 83 | +        logging.info("Donor %s made a donation of %d", self.name, amount) | 
|  | 84 | +        self.donations.append(amount) | 
|  | 85 | + | 
|  | 86 | + | 
|  | 87 | +class DonorDB(object): | 
|  | 88 | +    """ | 
|  | 89 | +    Database that holds all donor information | 
|  | 90 | +    """ | 
|  | 91 | + | 
|  | 92 | +    def __init__(self, donors=None): | 
|  | 93 | +        if donors is None: | 
|  | 94 | +            donors = {} | 
|  | 95 | +        self.donor_data = {d.name: d for d in donors} | 
|  | 96 | + | 
|  | 97 | + | 
|  | 98 | +    def save_to_file(self, filename): | 
|  | 99 | +        """ | 
|  | 100 | +        output donor information to file | 
|  | 101 | +        """ | 
|  | 102 | +        output = {k: v.donations for k, v in self.donor_data.items()} | 
|  | 103 | +        with open(filename, "w") as outfile: | 
|  | 104 | +            json.dump(output, outfile) | 
|  | 105 | + | 
|  | 106 | + | 
|  | 107 | +    @classmethod | 
|  | 108 | +    def load_from_file(cls, filename): | 
|  | 109 | +        """ | 
|  | 110 | +        Load DB from JSON file | 
|  | 111 | +        """ | 
|  | 112 | +        with open(filename) as infile: | 
|  | 113 | +            donors = json.load(infile) | 
|  | 114 | + | 
|  | 115 | +        return cls([Donor(*d) for d in donors.items()]) | 
|  | 116 | + | 
|  | 117 | + | 
|  | 118 | +    @property | 
|  | 119 | +    def donors(self): | 
|  | 120 | +        """ | 
|  | 121 | +        get donation values | 
|  | 122 | +        """ | 
|  | 123 | +        return self.donor_data.values() | 
|  | 124 | + | 
|  | 125 | + | 
|  | 126 | +    def get_donor(self, name): | 
|  | 127 | +        """ | 
|  | 128 | +        get donor by name | 
|  | 129 | +
 | 
|  | 130 | +        :param name: name | 
|  | 131 | +
 | 
|  | 132 | +        :return: donor object; None if not found | 
|  | 133 | +        :rtype: Donor | 
|  | 134 | +        """ | 
|  | 135 | +        logging.info("Getting donor object for donor %s", name) | 
|  | 136 | +        return self.donor_data.get(name) | 
|  | 137 | + | 
|  | 138 | + | 
|  | 139 | +    def add_donor(self, name): | 
|  | 140 | +        """ | 
|  | 141 | +        Add new donor to DB | 
|  | 142 | +
 | 
|  | 143 | +        :param name: donor name | 
|  | 144 | +
 | 
|  | 145 | +        :return: donor object | 
|  | 146 | +        :rtype: Donor | 
|  | 147 | +        """ | 
|  | 148 | +        logging.info("Adding donor %s", name) | 
|  | 149 | +        donor = Donor(name) | 
|  | 150 | +        self.donor_data[name] = donor | 
|  | 151 | +        return donor | 
|  | 152 | + | 
|  | 153 | + | 
|  | 154 | +    @staticmethod | 
|  | 155 | +    def send_letter(donor): | 
|  | 156 | +        """ | 
|  | 157 | +        Generate thank you letter | 
|  | 158 | +
 | 
|  | 159 | +        :param donor: donor object | 
|  | 160 | +
 | 
|  | 161 | +        :return: formatted thank you letter | 
|  | 162 | +        :rtype: String | 
|  | 163 | +        """ | 
|  | 164 | +        logging.info("Generating thank you letter for %s", donor.name) | 
|  | 165 | +        return THANK_YOU_LETTER.format(donor.name, donor.last_donation) | 
|  | 166 | + | 
|  | 167 | + | 
|  | 168 | +    def get_donor_report(self): | 
|  | 169 | +        """ | 
|  | 170 | +        Generate sorted list of donors from largest donation total to the least | 
|  | 171 | +
 | 
|  | 172 | +        :return: formatted donation report | 
|  | 173 | +        :rtype: String | 
|  | 174 | +        """ | 
|  | 175 | +        logging.info("Generating the donor report") | 
|  | 176 | +        donor_dict = {} | 
|  | 177 | + | 
|  | 178 | +        for donor in self.donor_data.values(): | 
|  | 179 | +            donor_dict.setdefault(donor.total_donations, []).append(donor) | 
|  | 180 | + | 
|  | 181 | +        report = "\n" | 
|  | 182 | +        report += (REPORT_HEADER.format(donor="Donor Name", | 
|  | 183 | +                                        total="Total Given", | 
|  | 184 | +                                        num="Num Gifts", | 
|  | 185 | +                                        avg="Average Gift")) | 
|  | 186 | +        report += "\n" | 
|  | 187 | +        report += "-" * 90 | 
|  | 188 | +        report += "\n" | 
|  | 189 | + | 
|  | 190 | + | 
|  | 191 | +        for amount in reversed(sorted(donor_dict)): | 
|  | 192 | +            for donor in donor_dict[amount]: | 
|  | 193 | +                report += (REPORT_LINE.format(donor.name, | 
|  | 194 | +                                              donor.total_donations, | 
|  | 195 | +                                              donor.num_donations, | 
|  | 196 | +                                              donor.avg_donations)) | 
|  | 197 | +                report += "\n" | 
|  | 198 | + | 
|  | 199 | +        report += "\n\n" | 
|  | 200 | +        return report | 
0 commit comments