diff --git a/stricher/week_10/mailroom/Address.py b/stricher/week_10/mailroom/Address.py new file mode 100644 index 0000000..ee545af --- /dev/null +++ b/stricher/week_10/mailroom/Address.py @@ -0,0 +1,62 @@ + +from EmailAddress import EmailAddress + + +class Address: + + def __init__(self, street: str, zipcode: str, city: str, + country: str, phone: str, mail: EmailAddress): + self.__street = street + self.__zipcode = zipcode + self.__city = city + self.__country = country + self.__phone = phone + self.__mail = mail + + def __repr__(self): + return 'Address("{}","{}","{}","{}","{}","{}")'.\ + format(self.street, self.zipcode, + self.city, self.country, + self.phone, self.mail) + + def __str__(self): + return self.street + "\n" \ + + self.zipcode + " - " \ + + self.city + "\n" \ + + self.country + "\n" \ + + self.phone + "\n" \ + + str(self.mail) + "\n" + + def __eq__(self, other): + return self.street == other.street \ + and self.zipcode == other.zipcode \ + and self.city == other.city \ + and self.country == other.country \ + and self.phone == other.phone \ + and self.mail == other.mail + + @property + def street(self) -> str: + return self.__street + + @property + def zipcode(self) -> str: + return self.__zipcode + + @property + def city(self) -> str: + return self.__city + + @property + def country(self) -> str: + return self.__country + + @property + def phone(self) -> str: + return self.__phone + + from EmailAddress import EmailAddress + + @property + def mail(self) -> EmailAddress: + return self.__mail diff --git a/stricher/week_10/mailroom/BenchData.py b/stricher/week_10/mailroom/BenchData.py new file mode 100644 index 0000000..607c260 --- /dev/null +++ b/stricher/week_10/mailroom/BenchData.py @@ -0,0 +1,286 @@ + +class DonorsDBBenchData: + + def _init_helper(self, donors: tuple) -> None: + + from Name import PersonName + from Address import Address + from Donor import Donor + + for don in donors: + name = PersonName(prefix=don["prefix"], first=don["first_name"], last=don["last_name"]) + address = Address(street=don["street"], zipcode=don["zipcode"], + city=don["city"], country=don["country"], + phone=don["phone"], mail=don["email"]) + donor = Donor(name=name, address=address, birth_date=don["birth_date"]) + self.__data[donor.id.value] = donor + self.__data_list.append(donor) + + def __init__(self): + + self.__data = {} + self.__data_list = [] + + from MyDate import MyDate + from EmailAddress import EmailAddress + donors = \ + ( + dict(prefix="M", first_name="Charles", last_name="Ives", + street="14, Avenue Foch", zipcode="75016", city="Paris", + country="France", phone="+33 1 45 67 83 45", + email=EmailAddress("charles.ives@centralparkinthedark.com"), + birth_date=MyDate(1874, 10, 20)), + dict(prefix="M", first_name="Jean S", last_name="Bach", + street="4, rue de la paix", zipcode="75008", city="Paris", + country="France", phone="+33 1 42 67 78 34", + email=EmailAddress("js.bach@google.com"), + birth_date=MyDate(1685, 3, 31)), + dict(prefix="M", first_name="Lee", last_name="Morgan", + street="153, boulevard Saint-Germain", zipcode="75006", city="Paris", + country="France", phone="+33 1 46 78 09 45", + email=EmailAddress("lee.morgan@jazzmessengers.com"), + birth_date=MyDate(1938, 7, 10)), + dict(prefix="M", first_name="Miles", last_name="Davis", + street="154, boulevard Saint-Germain", zipcode="75006", city="Paris", + country="France", phone="+33 1 45 67 33 12", + email=EmailAddress("miles.davis@myself.com"), + birth_date=MyDate(1926, 5, 26)), + dict(prefix="M", first_name="Wynton", last_name="Kelly", + street="3, place du Tertre", zipcode="75018", city="Paris", + country="France", phone="+33 1 45 87 34 25", + email=EmailAddress("wynton.kelly@quintet.com"), + birth_date=MyDate(1931, 12, 2)), + dict(prefix="Ms", first_name="Eliane", last_name="Radigue", + street="23, rue de Seine", zipcode="75006", city="Paris", + country="France", phone="+33 1 46 78 94 67", + email=EmailAddress("eliane.radigue@ircam.com"), + birth_date=MyDate(1932, 1, 24)) + ) + self._init_helper(donors) + + @property + def data(self) -> dict: + return self.__data + + @property + def data_list(self) -> list: + return self.__data_list + + def __repr__(self) -> str: + counter = 0 + str = "" + for donor_id, donor_name in self.data.items(): + str += "" if counter == 0 else "\n" + counter += 1 + str += "{}: {} {}".format(donor_id, donor_name[0], donor_name[1]) + return str + + +class DonationsDBBenchData: + + from Donor import Donor + from MyDate import MyDate + + def __init_helper(self, donor: Donor, donation_date: MyDate, amount: float) -> None: + + from Donation import Donation + + donation = Donation(donor, donation_date, amount, DatabaseBench.db) + + def __helper(donation: Donation): + if donation.donor.id.value not in self.__data: + self.__data[donation.donor.id.value] = {} + if donation.date not in self.__data[donation.donor.id.value]: + self.__data[donation.donor.id.value][donation.date] = [] + self.__data[donation.donor.id.value][donation.date].append(donation) + + __helper(donation) + + def __init__(self): + + self.__data = {} + + from PersonId import PersonId + from MyDate import MyDate + from EmailAddress import EmailAddress + + donors_db = DonorsDBBenchData().data + + donor = donors_db[PersonId(EmailAddress("charles.ives@centralparkinthedark.com")).value] + donation_dates = [MyDate(2012, 4, 12), MyDate(2014, 2, 8)] + amounts = [[567, 1000], 7654] + self.__init_helper(donor, donation_dates[0], amounts[0][0]) + self.__init_helper(donor, donation_dates[0], amounts[0][1]) + self.__init_helper(donor, donation_dates[1], amounts[1]) + + donor = donors_db[PersonId(EmailAddress("js.bach@google.com")).value] + donation_dates = [MyDate(2014, 8, 6), MyDate(2011, 12, 25), MyDate(2016, 4, 1)] + amounts = [2897, 4567, 876] + self.__init_helper(donor, donation_dates[0], amounts[0]) + self.__init_helper(donor, donation_dates[1], amounts[1]) + self.__init_helper(donor, donation_dates[2], amounts[2]) + + donor = donors_db[PersonId(EmailAddress("lee.morgan@jazzmessengers.com")).value] + donation_dates = [MyDate(2015, 10, 23), MyDate(2016, 10, 14)] + amounts = [3567, 7167] + self.__init_helper(donor, donation_dates[0], amounts[0]) + self.__init_helper(donor, donation_dates[1], amounts[1]) + + donor = donors_db[PersonId(EmailAddress("miles.davis@myself.com")).value] + donation_dates = [MyDate(2011, 5, 19)] + amounts = [67000, 15000] + self.__init_helper(donor, donation_dates[0], amounts[0]) + self.__init_helper(donor, donation_dates[0], amounts[1]) + + donor = donors_db[PersonId(EmailAddress("wynton.kelly@quintet.com")).value] + donation_dates = [MyDate(2009, 2, 24), MyDate(2007, 6, 18), MyDate(2013, 4, 5)] + amounts = [7894, 6666, 657] + self.__init_helper(donor, donation_dates[0], amounts[0]) + self.__init_helper(donor, donation_dates[1], amounts[1]) + self.__init_helper(donor, donation_dates[2], amounts[2]) + + donor = donors_db[PersonId(EmailAddress("eliane.radigue@ircam.com")).value] + donation_dates = [MyDate(2016, 1, 7)] + amounts = [8000] + self.__init_helper(donor, donation_dates[0], amounts[0]) + + @staticmethod + def get_donations_list_from_raw_data(email: str, + donation_dates: list, + donation_amounts: list) -> list: + + from PersonId import PersonId + from EmailAddress import EmailAddress + from Donation import Donation + + db = DatabaseBench.db + donor = DonorsDBBenchData().data[PersonId(EmailAddress(email)).value] + donations_list = [] + for date, amounts in zip(donation_dates, donation_amounts): + if type(amounts) == list: + for amount in amounts: + donations_list.append(Donation(donor, date, amount, db)) + else: + donations_list.append(Donation(donor, date, amounts, db)) + return donations_list + + def get_donations_list_from_bench_db(self, email: str): + + from PersonId import PersonId + from EmailAddress import EmailAddress + + def get_donor_id_str(): + return PersonId(EmailAddress(email)).value + + donations_from_donor = self.data[get_donor_id_str()] + donations_list = [] + for donations_per_date in donations_from_donor.values(): + if type(donations_per_date) == list: + for donation in donations_per_date: + donations_list.append(donation) + else: + donations_list.append(donations_per_date) + return donations_list + + @property + def data(self): + return self.__data + + +class DatabaseBench: + + from Database import Database + db = Database() + + def __init__(self): + donations_bench = DonationsDBBenchData() + donors_bench_data = DonorsDBBenchData().data + for donor in donors_bench_data.values(): + email = donor.mail.get + donations_bench_list = donations_bench.get_donations_list_from_bench_db(email) + for donation in donations_bench_list: + donation.add_to_database() + + +class OrganisationBenchData: + + def __init__(self): + self.__data = OrganisationBenchData.__init_helper() + + @staticmethod + def __init_helper(): + from Organisation import OrganisationMember + from Name import OrganisationName + from Organisation import Organisation + from Address import Address + from Database import Database + from Name import PersonName + from MyDate import MyDate + from EmailAddress import EmailAddress + + db = Database() + + name = OrganisationName("MusicFactory") + address = Address("14, rue Beaune", "75007", + "Paris", "France", + "+33 1 43 56 78 23", + EmailAddress("music.factory@drummachine.net")) + music_factory = Organisation(name=name, address=address, database=db) + + cat_anderson = OrganisationMember(name=PersonName("M", "Cat", "Anderson"), + address=address, birth_date=MyDate(1916, 9, 12), + organisation=music_factory) + + clifford_brown = OrganisationMember(name=PersonName("M", "Clifford", "Brown"), + address=address, birth_date=MyDate(1930, 10, 30), + organisation=music_factory) + + return music_factory + + @property + def data(self): + return self.__data + + +class MenuBenchData: + @staticmethod + def get_quit_message() -> str: + return "\n\nThe program quits. Goodbye!\n" + + +class HomeMenuBenchData(MenuBenchData): + + @staticmethod + def get_request_to_user() -> str: + return "\nPlease choose one of the following actions (1-2-3-4):\n\n" + + @staticmethod + def get_menu() -> str: + return "(1) Display the donations menu\n" \ + "(2) Send a thank you email\n" \ + "(3) Create a report\n" \ + "(4) quit\n\n" + + @staticmethod + def get_input_str() -> str: + return "Enter: " + + @staticmethod + def get_menu_error_message() -> str: + return "\nInvalid input. Please enter a number between 1 and 4!\n" + + +class TestThat: + __doTest = False + __registered = dict(thank_you="in send_a_thank_you_email()\n", + create_report="in create_report()\n", + quit_program="in quit_program()\n") + + def register(self, method, trace): + self.__registered[method] = trace + + def get_trace(self, method) -> str: + return self.__registered[method] + + def do(self) -> bool: + return self.__doTest diff --git a/stricher/week_10/mailroom/Database.py b/stricher/week_10/mailroom/Database.py new file mode 100644 index 0000000..1f17f45 --- /dev/null +++ b/stricher/week_10/mailroom/Database.py @@ -0,0 +1,124 @@ +from enum import Enum + +from DatabaseError import DatabaseError + + +class DBWriterType(Enum): + + sql_lite = 0 + sql_server = 1 + + +class Database: + + from Person import Person + from PersonId import PersonId + from MyDate import MyDate + from Donation import Donation + from DonorsTable import DonorsTable + from DonationsTable import DonationsTable + from OrganisationMembersTable import OrganisationMembersTable + + def __init__(self): + from DonorsTable import DonorsTable + from DonationsTable import DonationsTable + from OrganisationMembersTable import OrganisationMembersTable + self.__donors_table = DonorsTable() + self.__donations_table = DonationsTable() + self.__organisation_members_table = OrganisationMembersTable() + + @property + def donors_table(self) -> DonorsTable: + return self.__donors_table + + @property + def donations_table(self) -> DonationsTable: + return self.__donations_table + + @property + def organisation_members_table(self) -> OrganisationMembersTable: + return self.__organisation_members_table + + def __str__(self): + return "Database: TO_IMPLEMENT" + + def add_donation(self, donation: Donation) -> None: + if self.donation_exists(donation): + raise DatabaseError("Database.add_donation(donation)" + " - donation already exists in the database") + self.donations_table.add_donation(donation) + if not self.donors_table.person_exists_in_database(donation.donor.id.value): + self.donors_table.add_person(donation.donor) + + def get_donation(self, donor_id: PersonId, donation_date: MyDate) -> list: + return self.donations_table.get_per_donor_per_date(donor_id.value, donation_date) + + def get_donors_names_sorted_per_total_hist_donation_amount(self) -> list: + id_sorted = self.donations_table.get_donors_id_sorted_per_total_hist_donation_amount() + return [self.donors_table.get_person_name(donor_id) for donor_id in id_sorted] + + def donor_exists(self, donor_id: str) -> bool: + return self.donors_table.person_exists_in_database(donor_id) + + def get_donor(self, donor_id=None, mail=None) -> Person: + if donor_id is None or mail is None: + raise DatabaseError("Database.get_donor(donor_id, mail). " + "Missing argument donor_id or mail") + if donor_id is not None: + return self.donors_table.get_person(donor_id) + from PersonId import PersonId + from EmailAddress import EmailAddress + return self.donors_table.get_person(PersonId(EmailAddress(mail)).value) + + def donation_exists(self, donation: Donation) -> bool: + return self.donations_table.exists_previous_donation(donation) + + def write(self, writer_type: DBWriterType) -> None: + factory = DatabaseWriterFactory() + db_writer = factory.get_writer(writer_type) + db_writer.write(self) + + +class DatabaseWriter: + @staticmethod + def write(database: Database) -> None: + pass + + +class DatabaseWriterToFile(DatabaseWriter): + def __init__(self): + super(DatabaseWriterToFile, self).__init__() + + +class DatabaseWriterToRDBMS(DatabaseWriter): + def __init__(self): + super(DatabaseWriterToRDBMS, self).__init__() + + +class DatabaseWriterToSQLite(DatabaseWriterToRDBMS): + def __init__(self): + super(DatabaseWriterToSQLite, self).__init__() + + @staticmethod + def write(database: Database) -> None: + # TODO: implement + pass + + +class DatabaseWriterToSQLServer(DatabaseWriterToRDBMS): + def __init__(self): + super(DatabaseWriterToSQLServer, self).__init__() + + @staticmethod + def write(database: Database) -> None: + # TODO: implement + pass + + +class DatabaseWriterFactory: + def __init__(self): + self.__writers = dict(sql_lite=DatabaseWriterToSQLite(), + sql_server=DatabaseWriterToSQLServer()) + + def get_writer(self, writer_type: DBWriterType) -> DatabaseWriter: + return self.__writers[writer_type.name] diff --git a/stricher/week_10/mailroom/DatabaseError.py b/stricher/week_10/mailroom/DatabaseError.py new file mode 100644 index 0000000..00a0bc3 --- /dev/null +++ b/stricher/week_10/mailroom/DatabaseError.py @@ -0,0 +1,5 @@ +class DatabaseError(ValueError): + + def __init__(self, message: str): + super(DatabaseError, self).\ + __init__("DatabaseError: {}".format(message)) \ No newline at end of file diff --git a/stricher/week_10/mailroom/Donation.py b/stricher/week_10/mailroom/Donation.py new file mode 100644 index 0000000..7dadfb4 --- /dev/null +++ b/stricher/week_10/mailroom/Donation.py @@ -0,0 +1,68 @@ + + +class DonationError(ValueError): + + def __init__(self, message: str): + super(DonationError, self).__init__("DonationError: " + message) + + +class Donation: + + from Donor import Donor + from MyDate import MyDate + + def __init__(self, donor: Donor, donation_date, amount: float, + database): + + from MyDate import MyDate + + if isinstance(donation_date, str): + splitted_str = donation_date.split("-") + try: + year = int(splitted_str[0]) + month = int(splitted_str[1]) + day = int(splitted_str[2]) + except ValueError as ve: + raise DonationError("Donation(donor," + " donation_date, amount)" + " - {}".format(str(ve))) + donation_date = MyDate(year, month, day) + + elif not isinstance(donation_date, MyDate): + raise DonationError("Donation(donor, donation_date, amount) - " + "donation_date must be either of type string or MyDate") + + if amount <= 0: + raise DonationError("Donation(donor, donation_date, amount)" + " - amount must be strictly positive") + self.__donor = donor + self.__date = donation_date + self.__amount = amount + self.__database = database + + def __eq__(self, other): + return self.donor == other.donor\ + and self.date == other.date\ + and self.amount == other.amount + + def __str__(self): + return "\nDonor:\n------\n{}\nDonation:\n---------\n{}: {}".\ + format(self.donor, self.date, self.amount) + + @property + def donor(self) -> Donor: + return self.__donor + + @property + def date(self) -> MyDate: + return self.__date + + @property + def amount(self) -> float: + return self.__amount + + def get_attr(self, attr_str: str): + return getattr(self, attr_str) + + def add_to_database(self): + self.__database.add_donation(self) diff --git a/stricher/week_10/mailroom/DonationsMenu.py b/stricher/week_10/mailroom/DonationsMenu.py new file mode 100644 index 0000000..2dea6b0 --- /dev/null +++ b/stricher/week_10/mailroom/DonationsMenu.py @@ -0,0 +1,30 @@ + +from MultipleChoicesMenu import MultipleChoicesMenu + + +class DonationsMenu(MultipleChoicesMenu): + + from Database import Database + from sys import stdin, stdout + + def __init__(self, donations_db: Database, max_menu_calls=100, + max_trials=3, istream=stdin, ostream=stdout): + + from DonationsMenuActions import DonationsMenuActions + self._donations_menu_act = DonationsMenuActions(donations_db, ostream) + super(DonationsMenu, self).__init__(donations_db, max_menu_calls, + max_trials, istream, + ostream) + + def get_menu_choices_actions(self) -> dict: + return self._init_menu_choices_actions() + + def _init_menu_choices_actions(self) -> dict: + # TODO: Add get donations from a donor + from MenuActions import MenuActions + return { + "1": dict(message="Get the list of donors", fun=self._donations_menu_act.get_list_of_donors), + "2": dict(message="Record a donation", fun=self._donations_menu_act.record_donation), + "3": dict(message="Get back to the main menu", fun=MenuActions.back_to_home_menu), + "4": dict(message="quit", fun=MenuActions.go_to_quit_menu) + } diff --git a/stricher/week_10/mailroom/DonationsMenuActions.py b/stricher/week_10/mailroom/DonationsMenuActions.py new file mode 100644 index 0000000..f412fdd --- /dev/null +++ b/stricher/week_10/mailroom/DonationsMenuActions.py @@ -0,0 +1,35 @@ +from MenuActions import MenuActions + + +class DonationsMenuActions(MenuActions): + from Database import Database + from sys import stdout + from Donation import Donation + from Donor import Donor + from io import IOBase + + # TODO: complete/update + # TODO: Add get donations from a donor + + def __init__(self, donations_db, ostream: IOBase): + super(DonationsMenuActions, self).__init__(donations_db, ostream) + + def get_list_of_donors(self) -> None: + donors_names = self.donations_db.donors_table.get_persons_names() + self._ostream.write("\n") + for name in donors_names: + self._ostream.write(str(name) + "\n") + + # TODO: Call RecordDonationMenu + + def record_donation(self, donations_db: Database, + donation: Donation, donor: Donor, + ostream=stdout) -> None: + # self.db.add_donation(donation, donor) + pass + + + @staticmethod + def back_to_home_menu(): + # TODO: implement + pass diff --git a/stricher/week_10/mailroom/DonationsTable.py b/stricher/week_10/mailroom/DonationsTable.py new file mode 100644 index 0000000..c945cc9 --- /dev/null +++ b/stricher/week_10/mailroom/DonationsTable.py @@ -0,0 +1,174 @@ +from Table import Table + + +class DonationsTable(Table): + from MyDate import MyDate + from Donation import Donation + from sys import stdout + + def __init__(self, table=None): + super(DonationsTable, self).__init__(table) + + def _donor_exists_in_database(self, donor_id: str) -> bool: + return donor_id in self.table + + def get_per_donor(self, donor_id: str) -> dict: + from DatabaseError import DatabaseError + if self._donor_exists_in_database(donor_id): + return self.table[donor_id] + raise DatabaseError("DatabaseError: get_per_donor(donor_id): - " + "donor does not exist in the database") + + def _get_dates_per_donor(self, donor_id: str) -> list: + dates = list(self.get_per_donor(donor_id).keys()) + dates.sort() + return dates + + def get_per_donor_per_date(self, donor_id: str, date: MyDate) -> list: + from DatabaseError import DatabaseError + if date in self._get_dates_per_donor(donor_id): + return self.get_per_donor(donor_id)[date] + raise DatabaseError("DatabaseError - " + "get_per_donor_per_date(donor_id, date): " + "invalid argument. date not in database") + + def _add_date(self, donation: Donation) -> None: + self.get_per_donor(donation.donor.id.value)[donation.date] = list() + + def exists_previous_from_donor_same_date(self, donation: Donation) -> bool: + return donation.date in \ + self._get_dates_per_donor(donation.donor.id.value) + + def exists_previous_donation(self, donation: Donation) -> bool: + from DatabaseError import DatabaseError + try: + exists = donation in \ + self.get_per_donor_per_date(donation.donor.id.value, donation.date) + except DatabaseError: + exists = False + return exists + + def _add_donation_existing_date(self, donation: Donation) -> None: + self.get_per_donor_per_date(donation.donor.id.value, donation.date) \ + .append(donation) + + def _add_donation_new_date(self, donation: Donation) -> None: + self._add_date(donation) + self._add_donation_existing_date(donation) + + def _add_donor(self, donor_id: str) -> None: + donor_id = donor_id + self.table[donor_id] = dict() + + def _add_donation_from_new_donor(self, donation: Donation) -> None: + self._add_donor(donation.donor.id.value) + self._add_date(donation) + self._add_donation_existing_date(donation) + + def _add_donation_from_existing_donor(self, donation: Donation) -> None: + donation_new_date = not self.exists_previous_from_donor_same_date(donation) + if donation_new_date: + self._add_donation_new_date(donation) + else: + self._add_donation_existing_date(donation) + + def add_donation(self, donation: Donation) -> None: + new_donor = not self._donor_exists_in_database(donation.donor.id.value) + if new_donor: + self._add_donation_from_new_donor(donation) + else: + self._add_donation_from_existing_donor(donation) + + def _sum_donations_per_donor_per_date(self, donor_id: str, date: MyDate) -> float: + sum_donations = 0 + for donation in self.get_per_donor_per_date(donor_id, date): + sum_donations += donation.amount + return sum_donations + + def get_total_donation_amount_per_donor(self, donor_id: str) -> float: + total_donation_amount = 0 + for date in self.get_per_donor(donor_id).keys(): + total_donation_amount += self._sum_donations_per_donor_per_date(donor_id, date) + return total_donation_amount + + def get_total_donations_amounts(self) -> dict: + tot_don_amounts = dict() + for donor_id in self.table: + tot_don_amounts[donor_id] = \ + self.get_total_donation_amount_per_donor(donor_id) + return tot_don_amounts + + def _get_num_gifts_per_donor_per_date(self, donor_id: str, date: MyDate) -> int: + return len(self.get_per_donor_per_date(donor_id, date)) + + def get_num_gifts_per_donor(self, donor_id: str) -> int: + num_gifts = 0 + for date in self.get_per_donor(donor_id).keys(): + num_gifts += self._get_num_gifts_per_donor_per_date(donor_id, date) + return num_gifts + + def get_num_gifts(self) -> dict: + num_gifts = dict() + for donor_id in self.table: + num_gifts[donor_id] = self.get_num_gifts_per_donor(donor_id) + return num_gifts + + def get_average_gift_per_donor(self, donor_id: str) -> float: + return self.get_total_donation_amount_per_donor(donor_id) / \ + self.get_num_gifts_per_donor(donor_id) + + def get_average_gift(self) -> dict: + avge_gift = dict() + for donor_id in self.table: + avge_gift[donor_id] = self.get_average_gift_per_donor(donor_id) + return avge_gift + + def get_donors_id_sorted_per_total_hist_donation_amount(self) -> list: + total_donations = self.get_total_donations_amounts() + return sorted(total_donations, key=total_donations.__getitem__) + +#TODO: fix bug + def write_donations_given_donor(self, email=None, donor_id=None, ostream=stdout, + border_char='.', sep_char='.', counter=0) -> None: + + from Donation import Donation + from PersonId import PersonId + + if email is None and donor_id is None: + raise ValueError('ValueError: DonationsTable.' + 'write_donations_given_donor' + '(email, donor_id, ostream) - ' + 'missing argument email or donor_id') + if donor_id is None: + donor_id = PersonId(email).value + + donations_from_donor = self.table[donor_id] + num_donations = self.get_num_gifts_per_donor(donor_id) + + def __donation_string(donation: Donation, border_char: str, + sep_char: str, counter: int) -> str: + header_border = '{} {} {}'.format(15 * '*', donation.donor.name, 15 * '*') + border = 40 * border_char + sep = '{}{}{}'.format(18 * ' ', counter + 1, 18 * ' ') + if counter == 0: + return '\n{}\n{}\n\n{}\n{}'.format(header_border, sep, str(donation), border) + elif counter == num_donations - 1: + return '\n{}\n{}\n{}'.format(sep, border, str(donation)) + else: + return '\n\n{}\n{}\n\n{}\n{}'.format(sep, border, str(donation), border) + + for donations_per_date in donations_from_donor.values(): + if type(donations_per_date) == list: + for donation in donations_per_date: + ostream.write(__donation_string(donation, border_char=border_char, + sep_char=sep_char, counter=counter)) + counter += 1 + else: + ostream.write(__donation_string(donation, border_char=border_char, + sep_char=sep_char, counter=counter)) + counter += 1 + + def write(self, ostream=stdout, border_char='.', sep_char='.') -> None: + for counter, donor_id in enumerate(self.table.keys()): + self.write_donations_given_donor(donor_id=donor_id, ostream=ostream, + border_char=border_char, sep_char=sep_char) diff --git a/stricher/week_10/mailroom/Donor.py b/stricher/week_10/mailroom/Donor.py new file mode 100644 index 0000000..08ad6e7 --- /dev/null +++ b/stricher/week_10/mailroom/Donor.py @@ -0,0 +1,13 @@ + +from Person import Person + + +class Donor(Person): + + from Name import PersonName + from Address import Address + from MyDate import MyDate + + def __init__(self, name: PersonName, address: Address, + birth_date: MyDate): + super(Donor, self).__init__(name, address, birth_date) \ No newline at end of file diff --git a/stricher/week_10/mailroom/DonorsTable.py b/stricher/week_10/mailroom/DonorsTable.py new file mode 100644 index 0000000..d293417 --- /dev/null +++ b/stricher/week_10/mailroom/DonorsTable.py @@ -0,0 +1,8 @@ + +from PersonTable import PersonTable + + +class DonorsTable(PersonTable): + + def __init__(self, table=None): + super(DonorsTable, self).__init__(table) diff --git a/stricher/week_10/mailroom/EmailAddress.py b/stricher/week_10/mailroom/EmailAddress.py new file mode 100644 index 0000000..abe55c0 --- /dev/null +++ b/stricher/week_10/mailroom/EmailAddress.py @@ -0,0 +1,24 @@ + +class EmailAddressError(ValueError): + def __init__(self, message: str): + super(EmailAddressError, self).__init__(message) + + +class EmailAddress: + + def __init__(self, address: str): + from re import compile + if not bool(compile(r'^[A-Za-z\\.@]*$').search(address)): + raise EmailAddressError("ValueError: EmailAddress - " + "invalid address format") + self.__address = address + + @property + def get(self): + return self.__address + + def __str__(self): + return self.get + + def __eq__(self, other): + return other.get == self.get diff --git a/stricher/week_10/mailroom/EmailGenerator.py b/stricher/week_10/mailroom/EmailGenerator.py new file mode 100644 index 0000000..8f36f35 --- /dev/null +++ b/stricher/week_10/mailroom/EmailGenerator.py @@ -0,0 +1,71 @@ + +from TextGenerator import TextGenerator + + +class EmailGenerator(TextGenerator): + def __init__(self): + super(EmailGenerator, self).__init__() + + +class EmailDonationsGenerator(EmailGenerator): + + from Database import Database + from Donation import Donation + from PersonId import PersonId + from Person import Person + from Organisation import Organisation + + def __init__(self, donations_db: Database, donation: Donation, + author_id: PersonId, organisation: Organisation): + + if not donations_db.donors_table.person_exists_in_database(donation.donor_id.value): + raise ValueError("ValueError - " + "EmailDonationsGenerator(db," + " donation, author_id): donor not in database") + if not organisation.members.person_exists_in_database(author_id.value): + raise ValueError("ValueError - " + "EmailDonationsGenerator(db," + " donation, author_id): author not in database") + if not donations_db.donations_table.exists_previous_from_donor_same_date(donation): + raise ValueError("ValueError - " + "EmailDonationsGenerator(db," + " donation, author_id): donation not in database") + self.__db = donations_db + self.__donation = donation + self.__author_id = author_id + self.__organisation = organisation + super(EmailDonationsGenerator, self).__init__() + + @property + def db(self): + return self.__db + + @property + def donation(self): + return self.__donation + + @property + def author_id(self): + return self.__author_id + + @property + def organisation(self): + return self.__organisation + + def author(self) -> Person: + return self.organisation.get_member(self.author_id.value) + + def _donor_id(self) -> str: + return self.donation.donor_id.value + + def recipient(self) -> Person: + return self.db.get_donor(self._donor_id()) + + def _generate(self) -> str: + recipient = self.recipient() + name = str(recipient.name) + address = str(recipient.address) + message = "Dear {}, \n\nThank you very much for your donation.\n\n" \ + "Yours sincerely,\n\n{}\n".format(name, self.author) + string = "\n{}\n{}\n{}\n".format(name, address, message) + return string diff --git a/stricher/week_10/mailroom/GoBackToHomeMenu.py b/stricher/week_10/mailroom/GoBackToHomeMenu.py new file mode 100644 index 0000000..6e98d1f --- /dev/null +++ b/stricher/week_10/mailroom/GoBackToHomeMenu.py @@ -0,0 +1,4 @@ + +class GoBackToHomeMenu(Exception): + def __init__(self, message=""): + super(Exception, self).__init__(message) diff --git a/stricher/week_10/mailroom/GoToQuitMenu.py b/stricher/week_10/mailroom/GoToQuitMenu.py new file mode 100644 index 0000000..ceebde8 --- /dev/null +++ b/stricher/week_10/mailroom/GoToQuitMenu.py @@ -0,0 +1,4 @@ + +class GoToQuitMenu(Exception): + def __init__(self, message=""): + super(Exception, self).__init__(message) diff --git a/stricher/week_10/mailroom/HomeMenu.py b/stricher/week_10/mailroom/HomeMenu.py new file mode 100644 index 0000000..ddf143b --- /dev/null +++ b/stricher/week_10/mailroom/HomeMenu.py @@ -0,0 +1,69 @@ +from MultipleChoicesMenu import MultipleChoicesMenu + + +class HomeMenu(MultipleChoicesMenu): + from Database import Database + from sys import stdin, stdout + + def __init__(self, donations_db: Database, max_menu_calls=100, + max_trials=3, istream=stdin, ostream=stdout): + + from HomeMenuActions import HomeMenuActions + from DonationsMenu import DonationsMenu + from ThankYouEmailMenu import ThankYouEmailMenu + from QuitMenu import QuitMenu + + self._home_menu_act = HomeMenuActions(donations_db, ostream) + self._donations_menu = DonationsMenu(donations_db, + max_menu_calls, + max_trials, + istream, ostream) + self._thank_you_email_menu = ThankYouEmailMenu(donations_db, + max_menu_calls, + max_trials, + istream, ostream) + self.quit_menu = QuitMenu(donations_db, + max_menu_calls, + max_trials, + istream, ostream) + self._menu_choices_actions = self._init_menu_choices_actions() + super(HomeMenu, self).__init__(donations_db, max_menu_calls, + max_trials, istream, ostream) + + def get_menu_choices_actions(self) -> dict: + return self._menu_choices_actions + + def get_action_from_user_and_perform(self): + + def __recurs(counter, go_to_quit_menu=False): + + from GoToQuitMenu import GoToQuitMenu + from GoBackToHomeMenu import GoBackToHomeMenu + + try: + if counter == self._max_menu_calls or go_to_quit_menu: + self._get_quit_menu() + else: + action = self._get_action_from_user() + MultipleChoicesMenu.perform_action_from_user(action) + go_to_quit_menu = False + except GoToQuitMenu: + go_to_quit_menu = True + pass + except GoBackToHomeMenu: + go_to_quit_menu = False + pass + return __recurs(counter + 1, go_to_quit_menu) + + return __recurs(counter=0) + + def _init_menu_choices_actions(self) -> dict: + # TODO: add "Access database" + return {"1": dict(message="Display the donations menu", + fun=self._donations_menu.get_action_from_user_and_perform), + "2": dict(message="Send a thank you email", + fun=self._thank_you_email_menu.get_input_from_user), + "3": dict(message="Create a report", + fun=self._home_menu_act.create_report), + "4": dict(message="quit", + fun=self.quit_menu.get_action_from_user_and_perform)} diff --git a/stricher/week_10/mailroom/HomeMenuActions.py b/stricher/week_10/mailroom/HomeMenuActions.py new file mode 100644 index 0000000..8486469 --- /dev/null +++ b/stricher/week_10/mailroom/HomeMenuActions.py @@ -0,0 +1,23 @@ + +#TODO: complete/update + +from MenuActions import MenuActions + + +class HomeMenuActions(MenuActions): + + from io import IOBase + from Database import Database + + def __init__(self, donations_db: Database, ostream: IOBase): + super(HomeMenuActions, self).__init__(donations_db, ostream) + + def create_report(self): + from BenchData import TestThat + test_that = TestThat() + if test_that.do(): + self._ostream.write(test_that.get_trace("create_report")) + else: + from ReportGenerator import ReportDonationsDBGenerator + report_gen = ReportDonationsDBGenerator(self.donations_db) + report_gen.write(self._ostream) diff --git a/stricher/week_10/mailroom/Id.py b/stricher/week_10/mailroom/Id.py new file mode 100644 index 0000000..fcb1e48 --- /dev/null +++ b/stricher/week_10/mailroom/Id.py @@ -0,0 +1,7 @@ + +class Id: + + @staticmethod + def get_hash(string: str) -> str: + from hashlib import md5 + return md5(string.encode()).hexdigest() diff --git a/stricher/week_10/mailroom/InvalidInput.py b/stricher/week_10/mailroom/InvalidInput.py new file mode 100644 index 0000000..56d3dd1 --- /dev/null +++ b/stricher/week_10/mailroom/InvalidInput.py @@ -0,0 +1,5 @@ + +class InvalidInput(ValueError): + + def __init__(self, message: str): + super(InvalidInput, self).__init__(message) \ No newline at end of file diff --git a/stricher/week_10/mailroom/InvalidUserChoice.py b/stricher/week_10/mailroom/InvalidUserChoice.py new file mode 100644 index 0000000..95ec3c3 --- /dev/null +++ b/stricher/week_10/mailroom/InvalidUserChoice.py @@ -0,0 +1,5 @@ + +class InvalidUserChoice(ValueError): + + def __init__(self, message: str): + super(InvalidUserChoice, self).__init__(message) \ No newline at end of file diff --git a/stricher/week_10/mailroom/LoginMenu.py b/stricher/week_10/mailroom/LoginMenu.py new file mode 100644 index 0000000..fc29879 --- /dev/null +++ b/stricher/week_10/mailroom/LoginMenu.py @@ -0,0 +1,6 @@ + +from UserInputMenu import UserInputMenu + + +class LoginMenu(UserInputMenu): + pass \ No newline at end of file diff --git a/stricher/week_10/mailroom/LoginMenuActions.py b/stricher/week_10/mailroom/LoginMenuActions.py new file mode 100644 index 0000000..a3a3652 --- /dev/null +++ b/stricher/week_10/mailroom/LoginMenuActions.py @@ -0,0 +1,6 @@ + +from MenuActions import MenuActions + + +class LoginMenuActions(MenuActions): + pass diff --git a/stricher/week_10/mailroom/Menu.py b/stricher/week_10/mailroom/Menu.py new file mode 100644 index 0000000..92f7931 --- /dev/null +++ b/stricher/week_10/mailroom/Menu.py @@ -0,0 +1,74 @@ +# TODO: complete/update + +from enum import Enum + + +class WhichLimit(Enum): + trials = "max_trials" + menu_calls = "max_menu_calls" + + +class Menu: + from Database import Database + + def __init__(self, donations_db: Database, max_menu_calls, max_trials, istream, ostream): + self._donations_db = donations_db + self._max_menu_calls = max_menu_calls + self._max_trials = max_trials + self.istream = istream + self.ostream = ostream + self._messages = dict() + self._messages["request"] = self._make_request_string() + self._messages["menu"] = self._make_menu_string() + self._messages["input"] = self._make_input_string() + self._messages["error"] = self._make_error_string() + self._messages["quit_user"] = self._make_quit_string_from_user() + self._messages["quit_max_trials"] = self._make_quit_string_limit_exceeded("trials") + self._messages["quit_max_menu_calls"] = self._make_quit_string_limit_exceeded("menu calls") + + @property + def donations_db(self): + return self._donations_db + + @property + def max_menu_calls(self) -> int: + return self._max_menu_calls + + @property + def max_trials(self) -> int: + return self._max_trials + + @property + def messages(self) -> dict: + return self._messages + + def _make_request_string(self) -> str: + pass + + def _make_menu_string(self) -> str: + pass + + @staticmethod + def _make_input_string() -> str: + return "Enter: " + + def _make_error_string(self) -> str: + return "\n\nInvalid input\n" + + @staticmethod + def _make_quit_string_from_user() -> str: + return "\n\nThe program quits. Goodbye!\n" + + def _make_quit_string_limit_exceeded(self, which_limit: str) -> str: + store_limits = {"trials": self.max_trials, "menu calls": self.max_menu_calls} + return "Maximum number of allowed {} reached ({})." \ + " The program is about to quit.\n"\ + .format(which_limit, store_limits[which_limit]) + + def _print_request_and_menu(self) -> str: + from Utilities import Utilities + return Utilities.request(self.ostream, + self.istream, + self.messages["request"] + + self.messages["menu"] + + self.messages["input"]) diff --git a/stricher/week_10/mailroom/MenuActions.py b/stricher/week_10/mailroom/MenuActions.py new file mode 100644 index 0000000..0bdc3f0 --- /dev/null +++ b/stricher/week_10/mailroom/MenuActions.py @@ -0,0 +1,23 @@ + +class MenuActions: + + from Database import Database + from io import IOBase + + def __init__(self, donations_db: Database, ostream: IOBase): + self.__donations_db = donations_db + self._ostream = ostream + + @property + def donations_db(self): + return self.__donations_db + + @staticmethod + def back_to_home_menu(message=""): + from GoBackToHomeMenu import GoBackToHomeMenu + raise GoBackToHomeMenu(message) + + @staticmethod + def go_to_quit_menu(message=""): + from GoToQuitMenu import GoToQuitMenu + raise GoToQuitMenu(message) diff --git a/stricher/week_10/mailroom/MultipleChoicesMenu.py b/stricher/week_10/mailroom/MultipleChoicesMenu.py new file mode 100644 index 0000000..bd85b8a --- /dev/null +++ b/stricher/week_10/mailroom/MultipleChoicesMenu.py @@ -0,0 +1,107 @@ +from Menu import Menu + + +class MultipleChoicesMenu(Menu): + + from Database import Database + from io import IOBase + + def __init__(self, donations_db: Database, max_menu_calls: int, + max_trials: int, istream: IOBase, ostream: IOBase): + super(MultipleChoicesMenu, self).__init__(donations_db=donations_db, + max_menu_calls=max_menu_calls, + max_trials=max_trials, + istream=istream, + ostream=ostream) + self._user_choice = "" + + def get_menu_choices_actions(self) -> dict: + pass + + def _get_num_choices_actions(self) -> int: + return len(self.get_menu_choices_actions()) + + #TODO: change insertion method + def register_choice(self, message: str, fun) -> None: + index = str(self._get_num_choices_actions() + 1) + self.get_menu_choices_actions()[index] = {} + self.get_menu_choices_actions()[index] = dict(message=message, fun=fun) + + def _is_valid_choice(self, ans: str) -> bool: + return ans in self.get_menu_choices_actions().keys() + + def _validate_user_choice(self, ans: str) -> str: + if not self._is_valid_choice(ans): + from InvalidUserChoice import InvalidUserChoice + raise InvalidUserChoice(self.messages["error"]) + self._user_choice = ans + return ans + + def _make_request_string(self, add_message="") -> str: + request = "\nPlease choose one of the following actions (" + for index in range(self._get_num_choices_actions()): + sep = "" if index == 0 else "-" + request += sep + str(index + 1) + return request + "){}:".format(add_message) + 2 * "\n" + + def _make_menu_string(self) -> str: + menu = "" + for index in range(self._get_num_choices_actions()): + menu += "({}) ".format(index + 1) + \ + self.get_menu_choices_actions()[str(index + 1)]["message"] + \ + "\n" + return menu + "\n" + + def _make_error_string(self) -> str: + return "\n" + "Invalid input. Please enter a number between {} and {}!\n" \ + .format(1, self._get_num_choices_actions()) + + def _get_action(self, ans): + return self.get_menu_choices_actions()[self._validate_user_choice(ans)]["fun"] + + def _get_quit_menu(self): + from QuitMenu import QuitMenu + quit_menu = QuitMenu(self.donations_db, max_trials=self._max_trials, + max_menu_calls=self.max_menu_calls, + istream=self.istream, ostream=self.ostream) + quit_menu.get_action_from_user_and_perform() + + def _get_action_from_user(self): + from io import StringIO + + def __reset_ostream() -> None: + self.ostream.truncate(0) + self.ostream.seek(0) + + def __recurs(counter): + from InvalidUserChoice import InvalidUserChoice + from GoToQuitMenu import GoToQuitMenu + except_str = "From MultipleChoicesMenu" + ans = self._print_request_and_menu() + try: + ans = self._validate_user_choice(ans) + return self._get_action(ans) + except InvalidUserChoice as inv_uc: + if type(self.ostream) == StringIO: + __reset_ostream() + self.ostream.write(str(inv_uc)) + if counter == self.max_trials: + raise GoToQuitMenu(except_str) + return __recurs(counter + 1) + + return __recurs(counter=1) + + @staticmethod + def perform_action_from_user(fun): + fun() + + def get_action_from_user_and_perform(self): + + def __recurs(counter): + if counter == self._max_menu_calls: + self._get_quit_menu() + action = self._get_action_from_user() + MultipleChoicesMenu.perform_action_from_user(action) + return __recurs(counter + 1) + + return __recurs(counter=0) diff --git a/stricher/week_10/mailroom/MyDate.py b/stricher/week_10/mailroom/MyDate.py new file mode 100644 index 0000000..efeaa93 --- /dev/null +++ b/stricher/week_10/mailroom/MyDate.py @@ -0,0 +1,43 @@ +from datetime import date + + +class InvalidDateError(ValueError): + def __init__(self, message: str): + super(InvalidDateError, self).__init__("InvalidDateError: {}".format(message)) + + +class MyDate(date): + + def __new__(cls, year: int, month: int, day: int, future_date=False): + from datetime import datetime + if not future_date and (date(year, month, day) > datetime.now().date()): + raise InvalidDateError("MyDate(year, month, day," + " future_date=False) - " + "date can't be in the future (date > now)") + instance = super(MyDate, cls).__new__(cls, year, month, day) + instance._future_date = future_date + return instance + + @classmethod + def from_datetime(cls, the_date, future_date=False): + return MyDate(the_date.year, the_date.month, the_date.day, future_date) + + @property + def future_date(self): + return self._future_date + + @classmethod + def from_string(cls, date_str: str): + splitted = date_str.split("-") + if len(splitted) != 3: + raise InvalidDateError("InvalidDateError: MyDate.from_string(date_str)" + " - Argument must be formatted as YYYY-MM-DD") + try: + year = int(splitted[0]) + month = int(splitted[1]) + day = int(splitted[2]) + except ValueError: + raise InvalidDateError("InvalidDateError: MyDate.from_string(date_str)" + " - Invalid string format") + return cls(int(splitted[0]), int(splitted[1]), + int(splitted[2]), future_date=False) diff --git a/stricher/week_10/mailroom/Name.py b/stricher/week_10/mailroom/Name.py new file mode 100644 index 0000000..860f06e --- /dev/null +++ b/stricher/week_10/mailroom/Name.py @@ -0,0 +1,135 @@ +class InvalidNameError(ValueError): + def __init__(self, message: str): + super(InvalidNameError, self).__init__(message) + + +class Name: + def name(self) -> str: + pass + + def __str__(self) -> str: + return "{}".format(self.name) + + def __eq__(self, other) -> bool: + return self.name == other.name + + def __lt__(self, other) -> bool: + return self.name < other.name + + def __le__(self, other) -> bool: + return self.__eq__(other) or self.__lt__(other) + + def __gt__(self, other) -> bool: + return not self.__le__(other) + + def __ge__(self, other) -> bool: + return self.__gt__(other) or self.__eq__(other) + + +class PersonName(Name): + from io import IOBase + + def __init__(self, prefix: str, first: str, last: str): + super(PersonName, self).__init__() + self.__prefix = PersonName.validate_prefix(prefix) + self.__first = PersonName.validate_name_component(first) + self.__last = PersonName.validate_name_component(last) + self.__full_name = self.__name_builder() + + @staticmethod + def validate_prefix(val: str) -> str: + if val in ("M", "Ms"): + return val + raise InvalidNameError("InvalidNameError: " + "NameValidation.is_valid_prefix(val: str)" + " - prefix must be either M or Ms") + + @staticmethod + def validate_name_component(val: str) -> str: + from re import compile + if len(val) >= 2 and bool(compile(r'^[A-Za-z -]*$').search(val)): + return val + raise InvalidNameError('InvalidNameError: NameValidation.is_valid_name_component(val)' + ' - "val" must contain at least two characters ' + 'and characters must be in the range [A-Za-z]') + + def __name_builder(self) -> str: + return self.first + " " + self.last + + def __repr__(self): + return 'PersonName("{}","{}","{}")'.format(self.prefix, self.first, self.last) + + def __lt__(self, other): + return self.last < other.last + + def __le__(self, other): + return self.__lt__(other) or self.last == other.last + + @property + def prefix(self): + return self.__prefix + + @property + def first(self): + return self.__first + + @property + def last(self): + return self.__last + + @property + def full_name(self) -> str: + return self.__full_name + + @property + def name(self) -> str: + return self.prefix + " " + self.full_name + + @classmethod + def from_string(cls, name: str): + splitted = name.split(" ") + if len(splitted) != 3: + error_message = 'InvalidNameError: PersonName.get_from_string(name: str).' \ + ' String must be formatted as "prefix first_name last_name"' + raise InvalidNameError(error_message) + return cls(splitted[0], splitted[1], splitted[2]) + + @staticmethod + def get_from_stream(ostream: IOBase, istream: IOBase, message=None, max_attempt_message=None, max_trials=3): + from Utilities import Utilities + if message is None: + message = "Please enter the person's name [prefix first_name last_name]:" + + error_message = "Invalid input. Please try again!\n" + trials_counter = 1 + while trials_counter <= max_trials: + user_input = Utilities.request(ostream, istream, message) + try: + splitted = user_input.split(" ") + if len(splitted) != 3: + raise InvalidNameError(error_message) + prefix = PersonName.validate_prefix(splitted[0]) + first_name = PersonName.validate_name_component(splitted[1]) + last_name = PersonName.validate_name_component(splitted[2]) + return PersonName(prefix, first_name, last_name) + except InvalidNameError: + if trials_counter == max_trials: + break + ostream.write(error_message) + trials_counter += 1 + if max_attempt_message is None: + max_attempt_message = "Max number of trials. The program quits!\n" + ostream.write(max_attempt_message) + + +class OrganisationName(Name): + def __init__(self, name: str): + super(OrganisationName, self).__init__() + self.__name = name + + def __repr__(self): + return 'OrganisationName("{}")'.format(self.name) + + @property + def name(self): + return self.__name diff --git a/stricher/week_10/mailroom/Organisation.py b/stricher/week_10/mailroom/Organisation.py new file mode 100644 index 0000000..e9bb3a8 --- /dev/null +++ b/stricher/week_10/mailroom/Organisation.py @@ -0,0 +1,65 @@ + +from Person import Person + + +class OrganisationMember(Person): + pass + + +class Organisation: + + from Address import Address + from Name import OrganisationName + from OrganisationMembersTable import OrganisationMembersTable + from Database import Database + + def __init__(self, name: OrganisationName, address: Address, database: Database): + self.__name = name + self.__address = address + self.__db = database + + def __repr__(self) -> str: + return "Organisation({},{})".\ + format(self.name.__repr__(), self.address.__repr__()) + + @property + def name(self) -> OrganisationName: + return self.__name + + @property + def address(self) -> Address: + return self.__address + + @property + def members(self) -> OrganisationMembersTable: + return self.__db.organisation_members_table + + def __eq__(self, other) -> bool: + return self.name == other.name and self.address == other.address + + def add_member(self, member: OrganisationMember) -> None: + self.members.add_person(member) + + def get_member(self, member_id: str) -> Person: + return self.members.get_person(member_id) + + +class OrganisationMember(Person): + + from Name import PersonName + from Address import Address + from MyDate import MyDate + + def __init__(self, name: PersonName, address: Address, + birth_date: MyDate, organisation: Organisation): + super(OrganisationMember, self).__init__(name, address, birth_date) + self.__organisation = organisation + self.__organisation.add_member(self) + + @property + def organisation(self): + return self.__organisation + + def __eq__(self, other) -> bool: + return super(OrganisationMember, self).__eq__(other)\ + and self.organisation == other.organisation diff --git a/stricher/week_10/mailroom/OrganisationMembersTable.py b/stricher/week_10/mailroom/OrganisationMembersTable.py new file mode 100644 index 0000000..dec7e51 --- /dev/null +++ b/stricher/week_10/mailroom/OrganisationMembersTable.py @@ -0,0 +1,9 @@ + + +from PersonTable import PersonTable + + +class OrganisationMembersTable(PersonTable): + + def __init__(self, table=None): + super(OrganisationMembersTable, self).__init__(table) diff --git a/stricher/week_10/mailroom/Person.py b/stricher/week_10/mailroom/Person.py new file mode 100644 index 0000000..e627984 --- /dev/null +++ b/stricher/week_10/mailroom/Person.py @@ -0,0 +1,47 @@ + +class Person: + + from Name import PersonName + from Address import Address + from EmailAddress import EmailAddress + from MyDate import MyDate + from PersonId import PersonId + + def __init__(self, name: PersonName, address: Address, + birth_date: MyDate): + from PersonId import PersonId + self.__name = name + self.__address = address + self.__birth_date = birth_date + self.__id = PersonId(address.mail) + + def __repr__(self): + return 'Person({},{},{})'.format(self.name.__repr__(), + self.address.__repr__(), + self.birth_date.__repr__()) + + def __str__(self): + return "{}\n{}".format(self.name, self.address) + + def __eq__(self, other) -> bool: + return self.id == other.id + + @property + def name(self) -> PersonName: + return self.__name + + @property + def address(self) -> Address: + return self.__address + + @property + def mail(self) -> EmailAddress: + return self.address.mail + + @property + def birth_date(self) -> MyDate: + return self.__birth_date + + @property + def id(self) -> PersonId: + return self.__id diff --git a/stricher/week_10/mailroom/PersonId.py b/stricher/week_10/mailroom/PersonId.py new file mode 100644 index 0000000..a59bd97 --- /dev/null +++ b/stricher/week_10/mailroom/PersonId.py @@ -0,0 +1,17 @@ + +from Id import Id + + +class PersonId(Id): + + from EmailAddress import EmailAddress + + def __init__(self, email: EmailAddress): + self.__value = self.get_hash(email.get.upper()) + + def __eq__(self, other): + return self.value == other.value + + @property + def value(self): + return self.__value diff --git a/stricher/week_10/mailroom/PersonTable.py b/stricher/week_10/mailroom/PersonTable.py new file mode 100644 index 0000000..4b12d11 --- /dev/null +++ b/stricher/week_10/mailroom/PersonTable.py @@ -0,0 +1,34 @@ + +from Table import Table + + +class PersonTable(Table): + + from Person import Person + from Name import PersonName + + def __init__(self, table=None): + super(PersonTable, self).__init__(table) + + def person_exists_in_database(self, person_id: str) -> bool: + return person_id in self.table + + def _get_person_id(self) -> tuple: + return tuple(self.table.keys()) + + def get_person(self, person_id: str) -> Person: + if self.person_exists_in_database(person_id): + return self.table[person_id] + raise ValueError("ValueError: get_person(person_id): - " + "person does not exist in the database") + + def get_person_name(self, person_id: str) -> PersonName: + return self.get_person(person_id).name + + def get_persons_names(self) -> list: + donors = [person.name for person in self._table.values()] + donors.sort() + return donors + + def add_person(self, person: Person) -> None: + self.table[person.id.value] = person \ No newline at end of file diff --git a/stricher/week_10/mailroom/QuitMenu.py b/stricher/week_10/mailroom/QuitMenu.py new file mode 100644 index 0000000..be3c007 --- /dev/null +++ b/stricher/week_10/mailroom/QuitMenu.py @@ -0,0 +1,39 @@ +from MultipleChoicesMenu import MultipleChoicesMenu + + +class QuitMenu(MultipleChoicesMenu): + + from Database import Database + from sys import stdin, stdout + + def __init__(self, donations_db: Database, max_menu_calls=100, + max_trials=3, istream=stdin, ostream=stdout): + + from QuitMenuActions import QuitMenuActions + + self._quit_menu_act = QuitMenuActions(donations_db, ostream) + self._menu_choices_actions = self._init_menu_choices_actions() + super(QuitMenu, self).__init__(donations_db, max_menu_calls, + max_trials, istream, ostream) + + def _make_request_string(self, add_message=" before the program quits"): + add_message = "{} {}".\ + format(add_message, super(MultipleChoicesMenu, self) + ._make_request_string()) + return super(QuitMenu, self)._make_request_string(add_message) + + def get_menu_choices_actions(self) -> dict: + return self._menu_choices_actions + + def _init_menu_choices_actions(self) -> dict: + from MenuActions import MenuActions + return { + "1": dict(message="Save the database to a SQLite database", + fun=self._quit_menu_act.save_database_sql_lite), + "2": dict(message="Save the database to a SQL server database", + fun=self._quit_menu_act.save_database_sql_server), + "3": dict(message="Exit without saving the database", + fun=self._quit_menu_act.quit_program), + "4": dict(message="Get back to the main menu", + fun=MenuActions.back_to_home_menu) + } diff --git a/stricher/week_10/mailroom/QuitMenuActions.py b/stricher/week_10/mailroom/QuitMenuActions.py new file mode 100644 index 0000000..d48ffc3 --- /dev/null +++ b/stricher/week_10/mailroom/QuitMenuActions.py @@ -0,0 +1,28 @@ +from MenuActions import MenuActions + + +class QuitMenuActions(MenuActions): + from Database import Database, DBWriterType + from io import IOBase + + def __init__(self, donations_db: Database, ostream: IOBase): + super(QuitMenuActions, self).__init__(donations_db, ostream) + + def _save_database(self, writer_type: DBWriterType) -> None: + self._ostream.write("Writing the database to a" + " {} database\n".format(writer_type.name)) + self.donations_db.write(writer_type) + self.quit_program() + + def save_database_sql_lite(self) -> None: + from Database import DBWriterType + self._save_database(DBWriterType.sql_lite) + + def save_database_sql_server(self) -> None: + from Database import DBWriterType + self._save_database(DBWriterType.sql_server) + + def quit_program(self) -> None: + self._ostream.write("The program quits. Goodbye !") + from sys import exit + exit(0) diff --git a/stricher/week_10/mailroom/ReportGenerator.py b/stricher/week_10/mailroom/ReportGenerator.py new file mode 100644 index 0000000..b4bba43 --- /dev/null +++ b/stricher/week_10/mailroom/ReportGenerator.py @@ -0,0 +1,45 @@ + +from TextGenerator import TextGenerator + +class ReportGenerator(TextGenerator): + + def __init__(self): + super(ReportGenerator, self).__init__() + + +class ReportDonationsDBGenerator(ReportGenerator): + + from Database import Database + + def __init__(self, donations_db: Database): + self.__db = donations_db + super(ReportDonationsDBGenerator, self).__init__() + + def _generate(self) -> str: + donors_id = self.__db.donations_table.get_donors_id_sorted_per_total_hist_donation_amount() + + total_donation_per_donor = self.__db.donations_table.get_total_donations_amounts() + num_donations_per_donor = self.__db.donations_table.get_num_gifts() + avge_donation_per_donor = self.__db.donations_table.get_average_gift() + + string = "\nList of donors:\n\n" + string += 99 * '*' + '\n' + string += '{:^20}'.format('PersonName') + '|'\ + + '{:^30}'.format('Total amount') + '|' \ + + '{:^25}'.format('Number of donations') + '|' \ + + '{:^20}'.format('Average donation') + '|'\ + + '\n' + string += 99 * '*' + '\n' + ii = 0 + for don_id in donors_id: + string += "" if ii == 0 else "\n" + name = str(self.__db.donors_table.get_person_name(don_id)) + string += '{:20}'.format(name) + '|' + string += '{:>30.2f}'.format(total_donation_per_donor[don_id]) + '|' + string += '{:>25}'.format(num_donations_per_donor[don_id]) + '|' + string += '{:>20.2f}'.format(avge_donation_per_donor[don_id]) + '|' + ii += 1 + string += '\n' + string += 99 * '*' + '\n' + return string + diff --git a/stricher/week_10/mailroom/Signals.py b/stricher/week_10/mailroom/Signals.py new file mode 100644 index 0000000..096aaa3 --- /dev/null +++ b/stricher/week_10/mailroom/Signals.py @@ -0,0 +1,7 @@ + +from enum import Enum + + +class Signals(Enum): + QUIT = "Q" + HOME_MENU = "H" diff --git a/stricher/week_10/mailroom/Table.py b/stricher/week_10/mailroom/Table.py new file mode 100644 index 0000000..6a90c25 --- /dev/null +++ b/stricher/week_10/mailroom/Table.py @@ -0,0 +1,13 @@ + + +class Table: + + def __init__(self, table=None): + if table is not None: + self._table = table.copy() + else: + self._table = dict() + + @property + def table(self) -> dict: + return self._table diff --git a/stricher/week_10/mailroom/TextGenerator.py b/stricher/week_10/mailroom/TextGenerator.py new file mode 100644 index 0000000..80c9a66 --- /dev/null +++ b/stricher/week_10/mailroom/TextGenerator.py @@ -0,0 +1,17 @@ + + +class TextGenerator: + + def __init__(self): + self.__str = self._generate() + + def __str__(self): + return self.__str + + def _generate(self) -> str: + pass + + from sys import stdout + + def write(self, ostream=stdout) -> None: + ostream.write(self.__str) diff --git a/stricher/week_10/mailroom/ThankYouEmailMenu.py b/stricher/week_10/mailroom/ThankYouEmailMenu.py new file mode 100644 index 0000000..d0c342f --- /dev/null +++ b/stricher/week_10/mailroom/ThankYouEmailMenu.py @@ -0,0 +1,114 @@ +from UserInputMenu import UserInputMenu + +#TODO: Work on ThankYouEmailMenu + +class ThankYouEmailMenu(UserInputMenu): + from Database import Database + from sys import stdin, stdout + + def __init__(self, donations_db: Database, max_menu_calls=100, + max_trials=3, istream=stdin, ostream=stdout): + from datetime import datetime + super(ThankYouEmailMenu, self).__init__(donations_db, max_menu_calls, + max_trials, istream, ostream) + self._name_menu = ["Email:", + "birth date [YYYY-mm-dd]", + "donation date [YYYY-mm-dd]", + "donation amount"] + self._max_calls = len(self._name_menu) + self._now = datetime.now() + self._donor_email = None + self._donation_date = None + self._donation_amount = None + self._donor_id = None + self._organisation_member = None + self._thank_you_email_actions = None + + def _make_request_string(self) -> str: + return "\nSending a thank you email " \ + "to the donor {}\n\n". \ + format(super(ThankYouEmailMenu, self). + _make_request_string()) + + def _make_menu_string(self) -> str: + return "Please enter the " + ("donor's " if self._index <= 1 else "donations's ") + + def _update_menu(self) -> None: + self._messages["menu"] = self._make_menu_string() + + def _do_continue(self) -> bool: + if self._num_calls < self._max_calls: + return True + + def _end_input(self) -> bool: + return self._num_calls == self._max_calls - 1 + + def _valid_and_record_email(self, input: str) -> bool: + from EmailAddress import EmailAddress, EmailAddressError + try: + self._donor_email = EmailAddress(input) + except EmailAddressError: + return False + else: + return True + + def _valid_and_record_donor_id(self) -> bool: + from PersonId import PersonId + person_id = PersonId(self._donor_email) + if self.donations_db.donor_exists(person_id.value): + self._donor_id = person_id + return True + return False + + def _valid_and_record_donation_date(self, input: str) -> bool: + from MyDate import MyDate, InvalidDateError + try: + self._donation_date = MyDate.from_string(input) + except InvalidDateError: + return False + else: + return True + + def _valid_and_record_donation_amount(self, amount: str) -> bool: + try: + amount = float(amount) + except ValueError: + return False + else: + if amount >= 0: + self._donation_amount = amount + return True + return False + + def _valid_and_record_donation(self) -> bool: + from Donation import Donation + donation = Donation(self._donor_id, self._donation_date, + self._donation_amount) + if self.donations_db.donation_exists(donation): + self._donation = donation + return True + return False + + def _is_valid_input(self, input: str) -> bool: + if self._index == 0: + return self._valid_and_record_email(input) and \ + self._valid_and_record_donor_id() + if self._index == 1: + return self._valid_and_record_donation_date(input) + if self._index == 2: + return self._valid_and_record_donation_amount(input) and \ + self._valid_and_record_donation() + + def _print_request_and_menu(self) -> str: + from Utilities import Utilities + return Utilities.request(self.ostream, + self.istream, + self.messages["request"] + + self.messages["menu"] + + self._name_menu[self._index] + "\n" + + self.messages["input"]) + + def _process(self): + from ThankYouEmailMenuActions import ThankYouEmailMenuActions + thank_you_email = ThankYouEmailMenuActions(self.donations_db) + # TODO: complete diff --git a/stricher/week_10/mailroom/ThankYouEmailMenuActions.py b/stricher/week_10/mailroom/ThankYouEmailMenuActions.py new file mode 100644 index 0000000..c30ada8 --- /dev/null +++ b/stricher/week_10/mailroom/ThankYouEmailMenuActions.py @@ -0,0 +1,30 @@ + +from MenuActions import MenuActions + + +class ThankYouEmailMenuActions(MenuActions): + from io import IOBase + from Database import Database + from Donation import Donation + from PersonId import PersonId + + def __init__(self, donations_db: Database, ostream: IOBase, + donation: Donation, + organisation_member_id: PersonId): + super(ThankYouEmailMenuActions, self).__init__(donations_db, ostream) + self._donation = donation + self._organisation_member_id = organisation_member_id + + def send_a_thank_you_email(self): + from BenchData import TestThat + test_that = TestThat() + if test_that.do(): + self._ostream.write( + test_that.get_trace("ThankYouEmailMenuActions.send_a_thank_you_email") + ) + else: + from EmailGenerator import EmailDonationsGenerator + email_gen = EmailDonationsGenerator(self.donations_db, + self._donation, + self._organisation_member_id) + email_gen.write(self._ostream) diff --git a/stricher/week_10/mailroom/Types.py b/stricher/week_10/mailroom/Types.py new file mode 100644 index 0000000..2caf412 --- /dev/null +++ b/stricher/week_10/mailroom/Types.py @@ -0,0 +1,10 @@ + +#TODO: update + +from enum import Enum + +class InfoType(Enum): + DONOR = 'donor' + AMOUNT = 'amount' + + diff --git a/stricher/week_10/mailroom/UserInputMenu.py b/stricher/week_10/mailroom/UserInputMenu.py new file mode 100644 index 0000000..5357add --- /dev/null +++ b/stricher/week_10/mailroom/UserInputMenu.py @@ -0,0 +1,99 @@ + +from Menu import Menu + + +class UserInputMenu(Menu): + + from Database import Database + from io import IOBase + + def __init__(self, donations_db: Database, + max_menu_calls: int, max_trials: int, + istream: IOBase, ostream: IOBase): + self._index = 0 + self._num_calls = 0 + self._trials_counter = 0 + super(UserInputMenu, self).__init__(donations_db=donations_db, + max_menu_calls=max_menu_calls, + max_trials=max_trials, + istream=istream, + ostream=ostream) + + def _make_request_string(self) -> str: + from Signals import Signals + return "({} to get back to the home menu - {} to quit)".\ + format(Signals.HOME_MENU.value, Signals.QUIT.value) + + def _make_menu_string(self) -> str: + pass + + @staticmethod + def _switch_menu_or_process(input: str, message: str) -> str: + from Signals import Signals + input_upper = input.upper() + if input_upper == Signals.HOME_MENU.value: + from GoBackToHomeMenu import GoBackToHomeMenu + raise GoBackToHomeMenu(message) + if input_upper == Signals.QUIT.value: + from GoToQuitMenu import GoToQuitMenu + raise GoToQuitMenu(message) + return input + + def _is_valid_input(self, input: str) -> bool: + pass + + def _validate_user_input(self, input: str) -> None: + from InvalidInput import InvalidInput + if not self._is_valid_input(input): + raise InvalidInput("InvalidInput - " + "UserInputMenu._validate_user_input(input)") + + def _do_continue(self): + pass + + def _process(self): + pass + + def _record_input(self, user_input: str): + pass + + def _increment_counter(self): + self._index += 1 + self._num_calls += 1 + + def _reset_counter(self): + self._index = 0 + self._num_calls = 0 + + def get_input_from_user(self): + from io import StringIO + + def __reset_ostream() -> None: + self.ostream.truncate(0) + self.ostream.seek(0) + + def __recurs(counter): + from InvalidInput import InvalidInput + ans = self._print_request_and_menu() + try: + ans = self._switch_menu_or_process(ans, "From UserInputMenu") + self._validate_user_input(ans) + self._record_input(ans) + if not self._do_continue(): + self._process() + return + except InvalidInput: + self._trials_counter += 1 + if type(self.ostream) == StringIO: + __reset_ostream() + if self._trials_counter == self.max_trials: + from QuitMenu import QuitMenu + quit_menu = QuitMenu(self.donations_db, + max_trials=self._max_menu_calls) + quit_menu.get_action_from_user_and_perform() + else: + self._increment_counter() + self._trials_counter = 0 + return __recurs(counter + 1) + + return __recurs(counter=0) diff --git a/stricher/week_10/mailroom/Utilities.py b/stricher/week_10/mailroom/Utilities.py new file mode 100644 index 0000000..e07a57c --- /dev/null +++ b/stricher/week_10/mailroom/Utilities.py @@ -0,0 +1,23 @@ + +class Utilities: + + @staticmethod + def get_unique_items(container: tuple) -> tuple: + if type(container) != tuple: + raise TypeError("get_unique_items(container: tuple)" + " - container must be of tuple type") + if len(container) == 1: + return tuple(container) + ans = [] + for item in container: + if item not in ans: + ans.append(item) + return tuple(ans) + + @staticmethod + def request(ostream, istream, message: str) -> str: + numChars = ostream.write(message) + ostream.flush() + input = istream.readline().rstrip() + istream.flush() + return input \ No newline at end of file diff --git a/stricher/week_10/mailroom/mailroom.png b/stricher/week_10/mailroom/mailroom.png new file mode 100644 index 0000000..07cb81b Binary files /dev/null and b/stricher/week_10/mailroom/mailroom.png differ diff --git a/stricher/week_10/mailroom/mailroom.puml b/stricher/week_10/mailroom/mailroom.puml new file mode 100644 index 0000000..3ae0987 --- /dev/null +++ b/stricher/week_10/mailroom/mailroom.puml @@ -0,0 +1,452 @@ +@startuml + +namespace EntityN{ +Interface Name{ ++ pure virtual name(): str +- __str__(): str +- __eq__(): bool +- __lt__(): bool +- __le__(): bool +- __gt__(): bool +- __ge__(): bool +} + +class PersonName{ +- str prefix +- str first +- str last ++ __init__(prefix: str, first: str, last: str) +- __repr__(): str +- __lt__() override: bool +- __le__() override: bool ++ prefix(): str ++ first(): str ++ last(): str ++ full_name(): str ++ name() override: str +} + +Name <|-- PersonName + +class Address{ +- str street +- str zipcode +- str city +- str country +- str phone +- str mail ++ __eq__(other: Address): bool ++ street(): str ++ zipcode(): str ++ city(): str ++ country(): str ++ phone(): str ++ mail(): str +} + +class Organisation{ +- name: OrganisationName +- address: Address +- members: dict ++ __repr()__: str ++ name(): OrganisationName ++ address(): Address ++ members(): dict ++ add_member(member: OrganisationMember): None ++ get_member(member_id: PersonId): OrganisationMember +} + +class OrganisationName{ +- name: str ++ __init__(name: str) +- __repr__(): str ++ name() override: str +} +Name <|-- OrganisationName + +Organisation *--> OrganisationName + +Organisation o--> Address +Organisation o--> PersonName + +class PersonId{ +- value: str ++ __init__(name: PersonName, birth_date: MyDate) ++ __eq__(other: PersonId) -> bool ++ value(): str +} + +PersonId --> UtilitiesN.MyDate +PersonId --> PersonName + +class Person{ +- PersonName name +- Address address +- MyDate birth_date +- PersonId id ++ __eq__(other: Person): bool ++ name(): PersonName ++ address(): Address ++ birth_date(): MyDate ++ id(): PersonId +} + +Person *--> PersonName +Person o--> Address +Person *--> UtilitiesN.MyDate + +class OrganisationMember{ +- organisation: Organisation ++ __init__(self, name: PersonName, address: Address, + birth_date: MyDate, organisation: Organisation) ++ __eq__(other: OrganisationMember): bool +} + +Person <|-- OrganisationMember +OrganisationMember <--> Organisation + +class Donor{ ++ __init__(self, name: PersonName, address: Address, + birth_date: MyDate) ++ __eq__(other: PersonId): bool +} + +class Id{ ++ get_hash(string: str):str +} + +Id <|-- PersonId + +Person *--> PersonId + +Person <|-- Donor +} + +namespace DonationN{ +class Donation{ +- str donor_id +- MyDate donation_date +- float amount ++ donor_id(): PersonId ++ date(): MyDate ++ amount(): float +} + +Donation o--> UtilitiesN.MyDate +} + +namespace DatabaseN{ + +class DonorsTable{ +- dict table ++ table(): dict: ++ person_exists_in_database(donor_id: str): bool ++ get_person(donor_id: str): Donor ++ get_person_name(donor_id: str): PersonName ++ get_persons_names(self): list ++ add_person(self, donor: Donor): None +} + +class DonationsTable{ +- dict> table ++ table():dict> ++ get_per_donor(donor_id: str):dict> ++ add_donation(donation: Donation):None ++ get_total_donation_amount_per_donor(donor_id: str):float ++ get_total_donations_amounts():dict ++ get_num_gifts_per_donor(donor_id: str):int ++ get_num_gifts():dict ++ get_average_gift_per_donor(donor_id: str):float ++ get_average_gift():dict ++ get_donors_id_sorted_per_total_hist_donation_amount():list +} + +class Database{ +- DonorsTable donors_table +- DonationsTable donations_table ++ add_donation(donation: Donation, donor: Donor): None ++ get_donation(donor_id: PersonId, donation_date: MyDate): list ++ get_donors_names_sorted_per_total_hist_donation_amount(): list ++ write(db_writer: DatabaseWriter): None +} + +Database *--> DonationsTable +Database *--> DonorsTable +Database *--> DatabaseWriterFactory +DonorsTable *--> EntityN.Donor +DonationsTable *--> DonationN.Donation + +enum DBWriterType{ +sql_lite = 0 +sql_server = 1 +} + +Interface DatabaseWriter{ + +} +abstract class DatabaseWriterToFile +abstract class DatabaseWriterToRDBMS +class DatabaseWriterToSQLite{ ++ write(): None +} + +class DatabaseWriterToSQLServer{ ++ write(): None +} + +class DatabaseWriterFactory{ +- dict writers ++ get_writer(writer_type +} + +Database --> DBWriterType +DatabaseWriterFactory *--> DatabaseWriter +DatabaseWriter <|-- DatabaseWriterToRDBMS +DatabaseWriter <|-- DatabaseWriterToFile +DatabaseWriterToRDBMS <|-- DatabaseWriterToSQLite +DatabaseWriterToRDBMS <|-- DatabaseWriterToSQLServer + + + +DatabaseWriter <--> DatabaseN.Database +} + +namespace DocumentGeneratorN{ +Interface TextGenerator{ +} + +abstract class ReportGenerator{ +} + +TextGenerator <|-- ReportGenerator + +class ReportDonationsDBGenerator{ +} + +ReportGenerator <|-- ReportDonationsDBGenerator + +ReportDonationsDBGenerator o-- DatabaseN.Database + +class EmailGenerator{ +} + +TextGenerator <|-- EmailGenerator + +class EmailDonationsGenerator{ +} + +EmailGenerator <|-- EmailDonationsGenerator +} + +namespace MenuN{ + +class InvalidInput{ +} + +class InvalidUserChoice{ +} + +GlobalNamespace.ValueError <|-- InvalidInput +GlobalNamespace.ValueError <|-- InvalidUserChoice + +enum WhichLimit{ + + max_trials = "trials" + + max_menu_calls = "menu calls" +} + +Abstract class Menu{ +- Database db +- int max_menu_calls +- int max_trials +- IOStream istream +- IOStream ostream +- dict messages ++ __init__(db: Database, max_menu_calls: int, + max_trials: int, istream=stdin, ostream=stdout) ++ db(): Database ++ max_menu_calls(): int ++ max_trials(): int ++ messages(): dict +- pure virtual _make_request_string(): str +- pure virtual _make_menu_string(): str +- static _make_input_string(): str +- virtual _make_error_string(): str +- _make_quit_string_from_user(): str +- _make_quit_string_limit_exceeded(which_limit: WhichLimit) +- _print_request_and_menu(): str +} + +Menu --> WhichLimit +Menu o--> DatabaseN.Database +Menu o--> UtilitiesN.Utilities + +abstract class MenuActions{ +- Database db +- ostream: IOBase ++ db(): Database +} + +MenuActions --> DatabaseN.Database + +abstract class MultipleChoicesMenu{ ++ __init__(menu_choices_actions: dict, db: Database, + max_menu_calls: int, max_trials: int, istream=stdin, ostream=stdout) ++ virtual get_menu_choices_actions(): dict +- _get_num_choices_actions(self): int ++ register_choice(self, message: str, fun): None +- _is_valid_choice(self, ans: str): bool +- _validate_user_choice(self, ans: str): str +- _make_request_string(self): str +- _make_menu_string(self): str +- _make_error_string(self): str +- _get_action(self, ans): fun +- _get_action_from_user(self, istream: IOBase, ostream: IOBase) -> fun: +- perform_action_from_user(self, fun, ostream) ++ get_action_from_user_and_perform(self, istream: IOBase, ostream: IOBase) +} + +Menu <|-- MultipleChoicesMenu +MultipleChoicesMenu *--> InvalidUserChoice + +abstract class UserInputMenu{ ++ __init__(db: Database, max_menu_calls: int, + max_trials: int, istream=IOBase, ostream=IOBase) +- virtual _make_menu_string(): str +- virtual _is_valid_input(): bool +- _validate_user_input(input: str) +- virtual _do_continue() +- virtual _record_input(input: str) ++ get_input_from_user() +} + +Menu <|-- UserInputMenu +UserInputMenu *--> InvalidInput + +class HomeMenu{ +- HomeMenuActions home_menu_act +- QuitMenuActions quit_menu_act +- menu_choices_actions: dict ++ __init__(db: Database, max_menu_calls=100, + max_trials=3, istream=stdin, ostream=stdout) ++ get_menu_choices_actions(): dict +- _init_menu_choices_actions(): dict +} + +MultipleChoicesMenu <|-- HomeMenu + +class HomeMenuActions{ ++ __init__(db: Database, ostream: IOBase) ++ send_a_thank_you_email(donation: Donation): None ++ create_report(): None +} + +MenuActions <|- HomeMenuActions +HomeMenu *--> HomeMenuActions +HomeMenuActions *--> DocumentGeneratorN.ReportDonationsDBGenerator + +class QuitMenu{ +- menu_choices_actions: dict +- quit_menu_act: QuitMenuActions +- menu: MultipleChoicesMenu ++ __init__(self, db: Database, max_menu_calls=100, + max_trials=3, istream=stdin, ostream=stdout) ++ menu(): MultipleChoicesMenu ++ quit_menu_act(): QuitMenuActions +- _init_menu_choices_actions(): dict +} + +MultipleChoicesMenu <|-- QuitMenu +HomeMenu o--> QuitMenu + +class QuitMenuActions{ ++ __init__(self, db: Database, ostream: IOBase) +- _save_database(writer_type: DBWriterType): None ++ save_database_sql_lite(): None ++ save_database_sql_server(): None ++ quit_program(): None +} + +MenuActions <|-- QuitMenuActions +QuitMenu *--> QuitMenuActions +QuitMenuActions --> DatabaseN.DBWriterType + +class DonationsMenu{ +} + +MultipleChoicesMenu <|-- DonationsMenu +UserInputMenu <|-- DonationsMenu + +class DonationsMenuActions{ +} + +MenuActions <|-- DonationsMenuActions +DonationsMenu *--> DonationsMenuActions + +class ThankYouEmailMenu{ +- QuitMenu quit_menu +- list[str] name_menu +- list[fun] self._validate +- int index +- int num_calls +- int max_calls +- datetime now +- str prefix +- str first_name +- str last_name +- str year +- str month +- str day +- list[str] input ++ __init__(db: Database, max_menu_calls=100, + max_trials=3, istream=stdin, ostream=stdout) +} + +HomeMenuActions *--> ThankYouEmailMenu +UserInputMenu <|-- ThankYouEmailMenu + +class ThankYouEmailMenuActions{ ++ __init__(self, db: Database, ostream: IOBase, + donation: Donation, organisation_member: OrganisationMember) ++ send_a_thank_you_email(donation: Donation) +} + +MenuActions <|-- ThankYouEmailMenuActions +ThankYouEmailMenuActions *--> DocumentGeneratorN.EmailDonationsGenerator +ThankYouEmailMenu *--> ThankYouEmailMenuActions +} + +namespace UtilitiesN{ + +class InvalidDateError{ +- __init__(message: str): +} + +GlobalNamespace.ValueError <|-- InvalidDateError + +class MyDate{ +- bool future_date +- __new__(cls, year: int, month: int, day: int, future_date=False) +} + +MyDate --|> datetime.date +MyDate *--> InvalidDateError + +class Utilities{ ++ static get_unique_items(container: tuple): tuple ++ static request(ostream: IOBase, istream: IBase, message: str) -> str +} +} + +namespace GlobalNamespace{ + +class ValueError + +} + +namespace datetime{ +class date{ +- __init__(year: int, month: int, day: int) +} +} + +@enduml \ No newline at end of file diff --git a/stricher/week_10/mailroom/main.py b/stricher/week_10/mailroom/main.py new file mode 100755 index 0000000..278af3d --- /dev/null +++ b/stricher/week_10/mailroom/main.py @@ -0,0 +1,17 @@ +#!/usr/local/bin/python3.6 + + +def main(): + + from HomeMenu import HomeMenu + from BenchData import DatabaseBench + + print("Welcome to Mailroom") + + db = DatabaseBench.db + + menu = HomeMenu(db) + menu.get_action_from_user_and_perform() + +if __name__ == "__main__": + main() diff --git a/stricher/week_10/mailroom/test_Address.py b/stricher/week_10/mailroom/test_Address.py new file mode 100644 index 0000000..74f0058 --- /dev/null +++ b/stricher/week_10/mailroom/test_Address.py @@ -0,0 +1,42 @@ +from unittest import TestCase + + +class BenchDataAddress: + def __init__(self): + from Address import Address + from EmailAddress import EmailAddress + self.street = "15, rue de Lille" + self.zipcode = "75007" + self.city = "Paris" + self.country = "France" + self.phone = "+33 1 42 45 56 67" + self.mail = EmailAddress("nobody@nowhere.com") + self.address = Address(self.street, self.zipcode, self.city, self.country, self.phone, self.mail) + + +class TestAddress(TestCase): + def test___repr__(self): + from io import StringIO + bench = BenchDataAddress() + ostream = StringIO() + ostream.write(bench.address.__repr__()) + bench_str = 'Address("{}","{}","{}","{}","{}","{}")'.format(bench.street, + bench.zipcode, + bench.city, + bench.country, + bench.phone, + bench.mail.get) + self.assertEqual(bench_str, ostream.getvalue()) + + def test___str__(self): + bench = BenchDataAddress() + bench_str = "{}\n{} - {}\n{}\n{}\n{}\n".format(bench.street, + bench.zipcode, + bench.city, + bench.country, + bench.phone, + str(bench.mail)) + self.assertEqual(bench_str, str(bench.address)) + + def test___eq__(self): + self.assertEqual(BenchDataAddress().address, BenchDataAddress().address) diff --git a/stricher/week_10/mailroom/test_BackToMenu.py b/stricher/week_10/mailroom/test_BackToMenu.py new file mode 100644 index 0000000..a908cf9 --- /dev/null +++ b/stricher/week_10/mailroom/test_BackToMenu.py @@ -0,0 +1,20 @@ +from unittest import TestCase + +#TODO: update + +class TestBackToMenu(TestCase): + def test_raise_BackToMenu(self): + def __test_helper(): + from GoBackToHomeMenu import GoBackToHomeMenu + try: + raise GoBackToHomeMenu("GoBackToHomeMenu") + self.fail('test_raise_BackToMenu()') + except GoBackToHomeMenu as btm: + self.assertEqual('GoBackToHomeMenu', str(btm)) + raise + self.fail('test_raise_BackToMenu()') + try: + __test_helper() + self.fail('test_raise_BackToMenu()') + except Exception as exc: + self.assertEqual('GoBackToHomeMenu', str(exc)) \ No newline at end of file diff --git a/stricher/week_10/mailroom/test_Database.py b/stricher/week_10/mailroom/test_Database.py new file mode 100644 index 0000000..aca3899 --- /dev/null +++ b/stricher/week_10/mailroom/test_Database.py @@ -0,0 +1,118 @@ +from unittest import TestCase + + +class TestDatabase(TestCase): + def test___init__(self): + from Database import Database + from DonorsTable import DonorsTable + from DonationsTable import DonationsTable + from BenchData import DonorsDBBenchData, DonationsDBBenchData + from PersonId import PersonId + from Name import PersonName + from MyDate import MyDate + from Donation import Donation + + db = Database(DonorsTable(DonorsDBBenchData().data), + DonationsTable(DonationsDBBenchData().data)) + + donors_bench_data = DonorsDBBenchData().data + self.assertEqual(db.donors_table._get_person_id(), + tuple(donors_bench_data.keys())) + + donor_id = PersonId() + donation = Donation(donor_id, MyDate(2016, 1, 7), 8000) + donations_radigue = db.donations_table.get_per_donor(donor_id.value) + self.assertEqual(dict, type(donations_radigue)) + self.assertEqual(list, type(donations_radigue[MyDate(2016, 1, 7)])) + self.assertEqual(donation, donations_radigue[MyDate(2016, 1, 7)][0]) + + def test_add_donation(self): + from Database import Database + from BenchData import DonorsDBBenchData, DonationsDBBenchData + from DonationsTable import DonationsTable + from DonorsTable import DonorsTable + from PersonId import PersonId + from Name import PersonName + from MyDate import MyDate + from Donation import Donation + + db = Database(DonorsTable(DonorsDBBenchData().data), + DonationsTable(DonationsDBBenchData().data)) + donor_id = PersonId() + donations = db.donations_table.get_per_donor_per_date(donor_id.value, MyDate(2012, 4, 12)) + self.assertEqual(2, len(donations)) + donation = Donation(donor_id, MyDate(2012, 4, 12), 567585) + db.donations_table._add_donation_existing_date(donation) + donations = db.donations_table.get_per_donor_per_date(donor_id.value, MyDate(2012, 4, 12)) + self.assertEqual(3, len(donations)) + self.assertIn(donation, donations) + + def test_get_donation(self): + from Database import Database + from BenchData import DonorsDBBenchData, DonationsDBBenchData + from DonationsTable import DonationsTable + from DonorsTable import DonorsTable + from PersonId import PersonId + from Name import PersonName + from MyDate import MyDate + + db = Database(DonorsTable(DonorsDBBenchData().data), + DonationsTable(DonationsDBBenchData().data)) + + donations_ives = db.get_donation(donor_id=PersonId(), + donation_date=MyDate(2012, 4, 12)) + self.assertEqual(list, type(donations_ives)) + self.assertEqual(567, donations_ives[0].amount) + self.assertEqual(1000, donations_ives[1].amount) + + def test_get_total_donations_amounts(self): + from Database import Database + from BenchData import DonorsDBBenchData + from DonationsTable import DonationsTable + from DonorsTable import DonorsTable + from BenchData import DonationsDBBenchData + from PersonId import PersonId + from Name import PersonName + from MyDate import MyDate + + db = Database(DonorsTable(DonorsDBBenchData().data), + DonationsTable(DonationsDBBenchData().data)) + tot_don_amounts = db.donations_table.get_total_donations_amounts() + donor_id = PersonId() + self.assertEqual(3567 + 7167, tot_don_amounts[donor_id.value]) + + def test_get_average_gift_per_donor(self): + from Database import Database + from BenchData import DonorsDBBenchData + from DonationsTable import DonationsTable + from DonorsTable import DonorsTable + from BenchData import DonationsDBBenchData + from PersonId import PersonId + from Name import PersonName + from MyDate import MyDate + + db = Database(DonorsTable(DonorsDBBenchData().data), + DonationsTable(DonationsDBBenchData().data)) + + avge_gift_donor = db.donations_table.get_average_gift() + + donor_id_charles = PersonId() + donor_id_jsb = PersonId() + donor_id_miles = PersonId() + + self.assertEqual((567 + 1000 + 7654) / 3, avge_gift_donor[donor_id_charles.value]) + self.assertEqual((2897 + 4567 + 876) / 3, avge_gift_donor[donor_id_jsb.value]) + self.assertEqual((67000 + 15000) / 2, avge_gift_donor[donor_id_miles.value]) + + def test_get_donors_names_sorted_per_total_hist_donation_amount(self): + from Database import Database + from BenchData import DonorsDBBenchData + from DonationsTable import DonationsTable + from DonorsTable import DonorsTable + from BenchData import DonationsDBBenchData + + db = Database(DonorsTable(DonorsDBBenchData().data), + DonationsTable(DonationsDBBenchData().data)) + donors_names = db.get_donors_names_sorted_per_total_hist_donation_amount() + self.assertEqual("Ms Eliane Radigue", str(donors_names[0])) + self.assertEqual("M Jean S Bach", str(donors_names[1])) diff --git a/stricher/week_10/mailroom/test_DatabaseBench.py b/stricher/week_10/mailroom/test_DatabaseBench.py new file mode 100644 index 0000000..1cc9c90 --- /dev/null +++ b/stricher/week_10/mailroom/test_DatabaseBench.py @@ -0,0 +1,11 @@ +from unittest import TestCase + + +class TestDatabaseBench(TestCase): + + def test_init(self): + from BenchData import DatabaseBench + +#TODO: check database + db = DatabaseBench().db + print("Hello") diff --git a/stricher/week_10/mailroom/test_Donation.py b/stricher/week_10/mailroom/test_Donation.py new file mode 100644 index 0000000..f375c93 --- /dev/null +++ b/stricher/week_10/mailroom/test_Donation.py @@ -0,0 +1,47 @@ +from unittest import TestCase + + +class TestDonation(TestCase): + + def test_Donation(self): + + from Donation import Donation + from BenchData import DonorsDBBenchData + from PersonId import PersonId + from EmailAddress import EmailAddress + from MyDate import MyDate + + donors = DonorsDBBenchData().data + + donor1 = donors[PersonId(EmailAddress( + "charles.ives@centralparkinthedark.com" + )).value] + donor_str = 'M Charles Ives\n' \ + '14, Avenue Foch\n75016 - Paris\n' \ + 'France\n' \ + '+33 1 45 67 83 45\n' \ + 'charles.ives@centralparkinthedark.com\n' + self.assertEqual(donor_str, str(donor1)) + donation = Donation(donor1, MyDate(2012, 4, 12), 567) + donation_str = "\nDonor:\n------\nM Charles Ives\n14, Avenue Foch\n" \ + "75016 - Paris\nFrance\n+33 1 45 67 83 45\n" \ + "charles.ives@centralparkinthedark.com\n\n" \ + "Donation:\n---------\n2012-04-12: 567" + self.assertEqual(donation_str, str(donation)) + self.assertEqual(PersonId(EmailAddress(donation.donor.mail.get)), donation.donor.id) + self.assertEqual(PersonId(EmailAddress(donation.donor.mail.get)).value, donation.donor.id.value) + + def test_equality(self): + + from BenchData import DonorsDBBenchData + from PersonId import PersonId + from EmailAddress import EmailAddress + + donors = DonorsDBBenchData().data + donor1 = donors[PersonId(EmailAddress( + "charles.ives@centralparkinthedark.com" + )).value] + donor2 = donors[PersonId(EmailAddress("lee.morgan@jazzmessengers.com")).value] + self.assertEqual(donor1, donor1) + self.assertNotEqual(donor1, donor2) + self.assertEqual(donor2, donor2) \ No newline at end of file diff --git a/stricher/week_10/mailroom/test_DonationMenu.py b/stricher/week_10/mailroom/test_DonationMenu.py new file mode 100644 index 0000000..ddea3b3 --- /dev/null +++ b/stricher/week_10/mailroom/test_DonationMenu.py @@ -0,0 +1,18 @@ + +#TODO: complete/update + +from unittest import TestCase + + +class TestDonationMenu(TestCase): + + def test__init(self): + + from DonorsTable import DonorsTable + from DonationsTable import DonationsTable + from BenchData import DonorsDBBenchData, DonationsDBBenchData + from DonationsMenu import DonationsMenu + from Database import Database + db = Database(DonorsTable(DonorsDBBenchData().data), + DonationsTable(DonationsDBBenchData().data)) + menu = DonationsMenu(db) diff --git a/stricher/week_10/mailroom/test_DonationsDBBenchData.py b/stricher/week_10/mailroom/test_DonationsDBBenchData.py new file mode 100644 index 0000000..cfe3806 --- /dev/null +++ b/stricher/week_10/mailroom/test_DonationsDBBenchData.py @@ -0,0 +1,70 @@ +from unittest import TestCase + + +class TestDonationsDBBenchData(TestCase): + + def test_instantiate(self): + + from BenchData import DonationsDBBenchData + from MyDate import MyDate + + donations_db = DonationsDBBenchData().data + self.assertEqual(6, len(donations_db)) + + def __test_equal_donations(email: str, dates: list, amounts: list): + from BenchData import DonationsDBBenchData + + donations_bench = DonationsDBBenchData() + donations_raw_data = donations_bench.get_donations_list_from_raw_data(email=email, + donation_dates=dates, + donation_amounts=amounts) + donations_from_bench_db = donations_bench.get_donations_list_from_bench_db(email=email) + self.assertListEqual(donations_raw_data, donations_from_bench_db) + + # ---- Charles Ives ---- + email = "charles.ives@centralparkinthedark.com" + donation_dates = [MyDate(2012, 4, 12), MyDate(2014, 2, 8)] + amounts = [[567, 1000], 7654] + __test_equal_donations(email=email, + dates=donation_dates, + amounts=amounts) + + # ---- Jean S Bach ---- + email = "js.bach@google.com" + donation_dates = [MyDate(2014, 8, 6), MyDate(2011, 12, 25), MyDate(2016, 4, 1)] + amounts = [2897, 4567, 876] + __test_equal_donations(email=email, + dates=donation_dates, + amounts=amounts) + + # ---- Lee Morgan ---- + email = "lee.morgan@jazzmessengers.com" + donation_dates = [MyDate(2015, 10, 23), MyDate(2016, 10, 14)] + amounts = [3567, 7167] + __test_equal_donations(email=email, + dates=donation_dates, + amounts=amounts) + + # ---- Miles Davis ---- + email = "miles.davis@myself.com" + donation_dates = [MyDate(2011, 5, 19)] + amounts = [[67000, 15000]] + __test_equal_donations(email=email, + dates=donation_dates, + amounts=amounts) + + # ---- Wynton Kelly ---- + email = "wynton.kelly@quintet.com" + donation_dates = [MyDate(2009, 2, 24), MyDate(2007, 6, 18), MyDate(2013, 4, 5)] + amounts = [7894, 6666, 657] + __test_equal_donations(email=email, + dates=donation_dates, + amounts=amounts) + + # ---- Eliane Radigue ---- + email = "eliane.radigue@ircam.com" + donation_dates = [MyDate(2016, 1, 7)] + amounts = [8000] + __test_equal_donations(email=email, + dates=donation_dates, + amounts=amounts) diff --git a/stricher/week_10/mailroom/test_DonationsTable.py b/stricher/week_10/mailroom/test_DonationsTable.py new file mode 100644 index 0000000..569838f --- /dev/null +++ b/stricher/week_10/mailroom/test_DonationsTable.py @@ -0,0 +1,165 @@ +from unittest import TestCase + + +class TestDonationsTable(TestCase): + #TODO: refactor tests + + def test_donations_table_write(self): + + from DonationsTable import DonationsTable + from BenchData import DonationsDBBenchData + + from EmailAddress import EmailAddress + + donations_table = DonationsTable(DonationsDBBenchData().data) + donations_table.write_donations_given_donor( + email=EmailAddress("charles.ives@centralparkinthedark.com") + ) + #donations_table.write() + + + # def test_get_donations_per_donor(self): + # + # from DonationsTable import DonationsTable + # from BenchData import DonationsDBBenchData + # from PersonId import PersonId + # from Name import PersonName + # from MyDate import MyDate + # from Donation import Donation + # + # donations_table = DonationsTable(DonationsDBBenchData().data) + # + # donor_id = PersonId() + # donation = Donation(donor_id, MyDate(2016, 1, 7), 8000) + # donations_radigue = donations_table.get_per_donor(donor_id.value) + # self.assertEqual(dict, type(donations_radigue)) + # self.assertEqual(list, type(donations_radigue[MyDate(2016, 1, 7)])) + # self.assertEqual(donation, donations_radigue[MyDate(2016, 1, 7)][0]) + # + # donor_id = PersonId() + # donation_1 = Donation(donor_id, MyDate(2012, 4, 12), 567) + # donation_2 = Donation(donor_id, MyDate(2012, 4, 12), 1000) + # donation_3 = Donation(donor_id, MyDate(2014, 2, 8), 7654) + # donations_ives = donations_table.get_per_donor(donor_id.value) + # self.assertEqual(dict, type(donations_ives)) + # self.assertEqual(list, type(donations_ives[MyDate(2012, 4, 12)])) + # self.assertEqual(donation_1, donations_ives[MyDate(2012, 4, 12)][0]) + # self.assertEqual(donation_2, donations_ives[MyDate(2012, 4, 12)][1]) + # self.assertEqual(donation_3, donations_ives[MyDate(2014, 2, 8)][0]) + # + # def test__get_dates_per_donor(self): + # from DonationsTable import DonationsTable + # from BenchData import DonationsDBBenchData + # from PersonId import PersonId + # from Name import PersonName + # from MyDate import MyDate + # + # donations_table = DonationsTable(DonationsDBBenchData().data) + # + # donor_id = PersonId() + # donations_dates = donations_table._get_dates_per_donor(donor_id.value) + # self.assertEqual(MyDate(2007, 6, 18), donations_dates[0]) + # + # def test__get_per_donor_per_date(self): + # from DonationsTable import DonationsTable + # from BenchData import DonationsDBBenchData + # from PersonId import PersonId + # from Name import PersonName + # from MyDate import MyDate + # from Donation import Donation + # + # donations_table = DonationsTable(DonationsDBBenchData().data) + # + # donor_id = PersonId() + # donation_1 = Donation(donor_id, MyDate(2012, 4, 12), 567) + # donation_2 = Donation(donor_id, MyDate(2012, 4, 12), 1000) + # donation_3 = Donation(donor_id, MyDate(2014, 2, 8), 7654) + # + # donations_date_1 = donations_table.get_per_donor_per_date(donor_id.value, MyDate(2012, 4, 12)) + # self.assertEqual(donation_1, donations_date_1[0]) + # self.assertEqual(donation_2, donations_date_1[1]) + # self.assertEqual(2, len(donations_date_1)) + # donations_date_2 = donations_table.get_per_donor_per_date(donor_id.value, MyDate(2014, 2, 8)) + # self.assertEqual(donation_3, donations_date_2[0]) + # + # def test__add_date(self): + # from DonationsTable import DonationsTable + # from BenchData import DonationsDBBenchData + # from PersonId import PersonId + # from Name import PersonName + # from MyDate import MyDate + # from Donation import Donation + # + # donations_table = DonationsTable(DonationsDBBenchData().data) + # donor_id = PersonId() + # donation = Donation(donor_id, MyDate(2008, 1, 23), 5678.56) + # donations_table._add_date(donation) + # donations_dates = donations_table._get_dates_per_donor(donor_id.value) + # self.assertEqual(donation.date, donations_dates[0]) + # + # def test__exists_previous_from_donor_same_date(self): + # from DonationsTable import DonationsTable + # from BenchData import DonationsDBBenchData + # from PersonId import PersonId + # from Name import PersonName + # from MyDate import MyDate + # from Donation import Donation + # + # donations_table = DonationsTable(DonationsDBBenchData().data) + # + # donor_id = PersonId() + # donation_1 = Donation(donor_id, MyDate(2012, 4, 12), 567) + # donation_2 = Donation(donor_id, MyDate(2012, 4, 12), 1000) + # donation_3 = Donation(donor_id, MyDate(2014, 2, 8), 7654) + # + # self.assertEqual(True, donations_table.exists_previous_from_donor_same_date(donation_1)) + # self.assertEqual(True, donations_table.exists_previous_from_donor_same_date(donation_2)) + # self.assertEqual(True, donations_table.exists_previous_from_donor_same_date(donation_3)) + # + # def test__add_donation_existing_date(self): + # from DonationsTable import DonationsTable + # from BenchData import DonationsDBBenchData + # from PersonId import PersonId + # from Name import PersonName + # from MyDate import MyDate + # from Donation import Donation + # + # donations_table = DonationsTable(DonationsDBBenchData().data) + # + # donor_id = PersonId() + # donation = Donation(donor_id, MyDate(2012, 4, 12), 567585) + # donations_table._add_donation_existing_date(donation) + # donations = donations_table.get_per_donor_per_date(donor_id.value, MyDate(2012, 4, 12)) + # self.assertEqual(3, len(donations)) + # self.assertIn(donation, donations) + # + # def test__add_donation_new_date(self): + # from DonationsTable import DonationsTable + # from BenchData import DonationsDBBenchData + # from PersonId import PersonId + # from Name import PersonName + # from MyDate import MyDate + # from Donation import Donation + # + # donations_table = DonationsTable(DonationsDBBenchData().data) + # + # donor_id = PersonId() + # donation = Donation(donor_id, MyDate(2000, 6, 12), 4565) + # donations_table._add_donation_new_date(donation) + # donations = donations_table.get_per_donor_per_date(donor_id.value, MyDate(2000, 6, 12)) + # self.assertEqual(1, len(donations)) + # self.assertIn(donation, donations) + # + # def test__add_donor(self): + # from DonationsTable import DonationsTable + # from BenchData import DonationsDBBenchData + # from PersonId import PersonId + # from Name import PersonName + # from MyDate import MyDate + # + # db = DonationsTable(DonationsDBBenchData().data) + # + # donor_id = PersonId() + # self.assertEqual(False, db._donor_exists_in_database(donor_id.value)) + # db._add_donor(donor_id.value) + # self.assertEqual(True, db._donor_exists_in_database(donor_id.value)) diff --git a/stricher/week_10/mailroom/test_Donor.py b/stricher/week_10/mailroom/test_Donor.py new file mode 100644 index 0000000..54952f3 --- /dev/null +++ b/stricher/week_10/mailroom/test_Donor.py @@ -0,0 +1,58 @@ +from unittest import TestCase + + +class TestDonor(TestCase): + from Name import PersonName + from MyDate import MyDate + + @staticmethod + def __get_donor(donor_name: PersonName, donation_date: MyDate): + from BenchData import DonorsDBBenchData + from PersonId import PersonId + donors_bench_table = DonorsDBBenchData().data + return donors_bench_table[PersonId().value] + + # -- getters + + def test_name(self): + from Name import PersonName + from MyDate import MyDate + + donor = self.__get_donor(PersonName("M", "Charles", "Ives"), MyDate(1874, 10, 20)) + self.assertEqual("Charles", donor.name.first) + self.assertEqual("Ives", donor.name.last) + + donor = self.__get_donor(PersonName("M", "Jean S", "Bach"), MyDate(1685, 3, 31)) + self.assertEqual("Jean S", donor.name.first) + self.assertEqual("Bach", donor.name.last) + + def test_address(self): + from Address import Address + from Name import PersonName + from MyDate import MyDate + address = Address(street="154, boulevard Saint-Germain", zipcode="75006", city="Paris", + country="France", phone="+33 1 45 67 33 12", mail="miles.davis@myself.com") + donor = self.__get_donor(PersonName("M", "Miles", "Davis"), MyDate(1926, 5, 26)) + self.assertEqual(address, donor.address) + + def test_birth_date(self): + from MyDate import MyDate + from Name import PersonName + birthdate = MyDate(1931, 12, 2) + donor = self.__get_donor(PersonName("M", "Wynton", "Kelly"), MyDate(1931, 12, 2)) + self.assertEqual(birthdate, donor.birth_date) + + def test_id(self): + from PersonId import PersonId + from Name import PersonName + from MyDate import MyDate + donor_id = PersonId() + donor = self.__get_donor(PersonName("M", "Lee", "Morgan"), MyDate(1938, 7, 10)) + self.assertEqual(donor_id, donor.id) + + def test___eq__(self): + from Name import PersonName + from MyDate import MyDate + donor = self.__get_donor(PersonName("M", "Lee", "Morgan"), MyDate(1938, 7, 10)) + same_donor = self.__get_donor(PersonName("M", "Lee", "Morgan"), MyDate(1938, 7, 10)) + self.assertEqual(donor, same_donor) diff --git a/stricher/week_10/mailroom/test_DonorId.py b/stricher/week_10/mailroom/test_DonorId.py new file mode 100644 index 0000000..53e81fb --- /dev/null +++ b/stricher/week_10/mailroom/test_DonorId.py @@ -0,0 +1,13 @@ +from unittest import TestCase + + +class TestDonorId(TestCase): + + def test_equality(self): + from PersonId import PersonId + from MyDate import MyDate + from Name import PersonName + + donor1 = PersonId() + donor2 = PersonId() + self.assertEqual(donor1, donor2) \ No newline at end of file diff --git a/stricher/week_10/mailroom/test_DonorsDBBenchData.py b/stricher/week_10/mailroom/test_DonorsDBBenchData.py new file mode 100644 index 0000000..e1a2589 --- /dev/null +++ b/stricher/week_10/mailroom/test_DonorsDBBenchData.py @@ -0,0 +1,26 @@ +from unittest import TestCase + + +class TestDonorsDBBenchData(TestCase): + + def test_instantiate(self): + + from BenchData import DonorsDBBenchData + from PersonId import PersonId + from Name import PersonName + from MyDate import MyDate + + donors_db = DonorsDBBenchData().data + donor_id = PersonId() + donor = donors_db[donor_id.value] + self.assertEqual("M", donor.name.prefix) + self.assertEqual("Charles", donor.name.first) + self.assertEqual("Ives", donor.name.last) + self.assertEqual("14, Avenue Foch", donor.address.street) + self.assertEqual("75016", donor.address.zipcode) + self.assertEqual("Paris", donor.address.city) + self.assertEqual("France", donor.address.country) + self.assertEqual("+33 1 45 67 83 45", donor.address.phone) + self.assertEqual("charles.ives@centralparkinthedark.com", + donor.address.mail) + self.assertEqual(MyDate(1874, 10, 20), donor.birth_date) diff --git a/stricher/week_10/mailroom/test_DonorsTable.py b/stricher/week_10/mailroom/test_DonorsTable.py new file mode 100644 index 0000000..108d117 --- /dev/null +++ b/stricher/week_10/mailroom/test_DonorsTable.py @@ -0,0 +1,84 @@ +from unittest import TestCase + + +class TestDonorsTable(TestCase): + + def test__donor_exists_in_database(self): + + from DonorsTable import DonorsTable + from BenchData import DonorsDBBenchData + from PersonId import PersonId + from Name import PersonName + from MyDate import MyDate + + donors_table = DonorsTable(DonorsDBBenchData().data) + donor_id = PersonId() + self.assertEqual(True, donors_table.person_exists_in_database(donor_id.value)) + + def test__get_donors_id(self): + from DonorsTable import DonorsTable + from BenchData import DonorsDBBenchData + + bench_data = DonorsDBBenchData().data + donors_table = DonorsTable(bench_data) + self.assertEqual(donors_table._get_person_id(), tuple(bench_data.keys())) + + def test_get_donor(self): + from DonorsTable import DonorsTable + from BenchData import DonorsDBBenchData + from MyDate import MyDate + from Name import PersonName + from PersonId import PersonId + + bench_data = DonorsDBBenchData().data + donors_table = DonorsTable(bench_data) + donor_id = PersonId() + self.assertEqual(bench_data[donor_id.value], donors_table.get_person(donor_id.value)) + + def test_get_donor_name(self): + from DonorsTable import DonorsTable + from BenchData import DonorsDBBenchData + from MyDate import MyDate + from Name import PersonName + from PersonId import PersonId + + bench_data = DonorsDBBenchData().data + donors_table = DonorsTable(bench_data) + name = PersonName("M", "Jean S", "Bach") + donor_id = PersonId() + self.assertEqual(name, donors_table.get_person(donor_id.value).name) + + def test_get_donors_name(self): + from DonorsTable import DonorsTable + from BenchData import DonorsDBBenchData + from Name import PersonName + + bench_data = DonorsDBBenchData().data + donors_table = DonorsTable(bench_data) + donors_names = donors_table.get_persons_names() + self.assertEqual(6, len(donors_names)) + self.assertEqual(PersonName("M", "Jean S", "Bach"), donors_names[0]) + self.assertEqual(PersonName("Ms", "Eliane", "Radigue"), donors_names[-1]) + + def test_add_donor(self): + from DonorsTable import DonorsTable + from BenchData import DonorsDBBenchData + from Name import PersonName + from Address import Address + from Donor import Donor + from PersonId import PersonId + from MyDate import MyDate + + donors_table = DonorsTable(DonorsDBBenchData().data) + donor_name = PersonName("M", "Ron", "Carter") + street = "4, place Saint-Sulpice" + zipcode = "75006" + city = "Paris" + country = "France" + phone = "+33 1 45 43 89 23" + mail = "ron.carter@thequintet.com" + donor_address = Address(street=street, zipcode=zipcode, city=city, country=country, phone=phone, mail=mail) + birth_date = MyDate(1937, 5, 4) + donor = Donor(name=donor_name, address=donor_address, birth_date=birth_date) + donors_table.add_person(donor) + self.assertEqual(donor, donors_table.get_person(PersonId().value)) diff --git a/stricher/week_10/mailroom/test_EmailDonationsGenerator.py b/stricher/week_10/mailroom/test_EmailDonationsGenerator.py new file mode 100644 index 0000000..a027c3b --- /dev/null +++ b/stricher/week_10/mailroom/test_EmailDonationsGenerator.py @@ -0,0 +1,46 @@ +from unittest import TestCase + + +class TestData: + @staticmethod + def bench_str() -> str: + return \ + "M Miles Davis\n" \ + "154, boulevard Saint-Germain\n" \ + "75006 - Paris\n" \ + "France\n" \ + "+33 1 45 67 33 12\n" \ + "miles.davis@myself.com\n\n" \ + "Dear M Miles Davis,\n\n" \ + "Thank you very much for your donation.\n\n" \ + "Yours sincerely,\n\n" \ + "James Moody\n" + + +class TestEmailDonationsGenerator(TestCase): + def test_write(self): + + from test_ReportDonationsDBGenerator import TestReportDonationsDBGenerator + from EmailGenerator import EmailDonationsGenerator + from PersonId import PersonId + from Donation import Donation + from MyDate import MyDate + + self.maxDiff = None + + db = TestReportDonationsDBGenerator._init_db() + + donor_id = PersonId() + donation = Donation(donor_id, MyDate(2008, 1, 23), 5678.56) + + try: + email_gen = EmailDonationsGenerator(db, donation, "James Moody") + self.fail("Should generate an ValueError exception") + except ValueError as ve: + self.assertEqual("ValueError - " + "EmailDonationsGenerator(db," + " donor_id, donation_date):" + " donation not in database", str(ve)) + + donation = Donation(donor_id, MyDate(2011, 5, 19), 67000) + email_gen = EmailDonationsGenerator(db, donation, "James Moody") diff --git a/stricher/week_10/mailroom/test_HomeMenu.py b/stricher/week_10/mailroom/test_HomeMenu.py new file mode 100644 index 0000000..df63617 --- /dev/null +++ b/stricher/week_10/mailroom/test_HomeMenu.py @@ -0,0 +1,232 @@ +from unittest import TestCase + + +class TestMenu(TestCase): + @staticmethod + def _helper_make_db(): + from DonorsTable import DonorsTable + from DonationsTable import DonationsTable + from BenchData import DonorsDBBenchData, DonationsDBBenchData + from Database import Database + return Database(DonorsTable(DonorsDBBenchData().data), + DonationsTable(DonationsDBBenchData().data)) + + def test__make_request_string(self): + from HomeMenu import HomeMenu + from BenchData import HomeMenuBenchData + menu = HomeMenu(TestMenu._helper_make_db()) + menu_test_data = HomeMenuBenchData() + self.assertEqual(menu._make_request_string(), + menu_test_data.get_request_to_user()) + + def test__make_menu_string(self): + from HomeMenu import HomeMenu + from BenchData import HomeMenuBenchData + menu = HomeMenu(TestMenu._helper_make_db()) + menu_test_data = HomeMenuBenchData() + self.assertEqual(menu._make_menu_string(), + menu_test_data.get_menu()) + + def test__make_invalid_choice_error_string(self): + from HomeMenu import HomeMenu + from BenchData import HomeMenuBenchData + menu = HomeMenu(TestMenu._helper_make_db()) + menu_test_data = HomeMenuBenchData() + self.assertEqual(menu._make_error_string(), + menu_test_data.get_menu_error_message()) + + def test__make_quit_string(self): + from HomeMenu import HomeMenu + from BenchData import MenuBenchData + menu = HomeMenu(TestMenu._helper_make_db()) + menu_test_data = MenuBenchData() + self.assertEqual(menu._make_quit_string_from_user(), + menu_test_data.get_quit_message()) + + def test__get_request_to_user(self): + from HomeMenu import HomeMenu + from BenchData import HomeMenuBenchData + menu = HomeMenu(TestMenu._helper_make_db()) + menu_test_data = HomeMenuBenchData() + self.assertEqual(menu_test_data.get_request_to_user(), + menu.messages["request"]) + + def test__get_menu(self): + from HomeMenu import HomeMenu + from BenchData import HomeMenuBenchData + menu = HomeMenu(TestMenu._helper_make_db()) + self.assertEqual(HomeMenuBenchData.get_menu(), + menu._make_menu_string()) + + def test__get_error_message(self): + from HomeMenu import HomeMenu + from BenchData import HomeMenuBenchData + from DonorsTable import DonorsTable + from DonationsTable import DonationsTable + from BenchData import DonorsDBBenchData, DonationsDBBenchData + from Database import Database + db = Database(DonorsTable(DonorsDBBenchData().data), + DonationsTable(DonationsDBBenchData().data)) + menu = HomeMenu(db) + self.assertEqual(HomeMenuBenchData.get_menu_error_message(), + menu.messages["error"]) + + def test__get_quit_message(self): + from HomeMenu import HomeMenu + from BenchData import MenuBenchData + menu = HomeMenu(TestMenu._helper_make_db()) + self.assertEqual(MenuBenchData.get_quit_message(), + menu.messages["quit_user"]) + + def test__is_valid(self): + from HomeMenu import HomeMenu + menu = HomeMenu(TestMenu._helper_make_db()) + self.assert_(menu._is_valid_choice("1")) + self.assert_(menu._is_valid_choice("2")) + self.assert_(menu._is_valid_choice("3")) + self.assert_(menu._is_valid_choice("4")) + self.assert_(not menu._is_valid_choice("5")) + self.assert_(not menu._is_valid_choice("qwert")) + self.assert_(not menu._is_valid_choice("0")) + + def test__print_request_and_menu(self): + + def __test_helper(user_input: str) -> None: + from HomeMenu import HomeMenu + from io import StringIO + from BenchData import HomeMenuBenchData + ostream = StringIO() + istream = StringIO(user_input) + menu = HomeMenu(TestMenu._helper_make_db(), + istream=istream, + ostream=ostream) + user_ans = menu._print_request_and_menu() + bench_str = HomeMenuBenchData.get_request_to_user() \ + + HomeMenuBenchData.get_menu() \ + + HomeMenuBenchData.get_input_str() + self.assertEqual(bench_str, ostream.getvalue()) + self.assertNotEqual(bench_str + "\n", ostream.getvalue()) + self.assertEqual(user_input, user_ans) + self.assertNotEqual(user_input + "?", user_ans) + + __test_helper("1") + __test_helper("2") + __test_helper("qwerty") + + def test__validate_user_choice(self): + + def __test_helper(ans: str) -> None: + + from HomeMenu import HomeMenu + from io import StringIO + from InvalidUserChoice import InvalidUserChoice + + ostream = StringIO() + menu = HomeMenu(TestMenu._helper_make_db()) + try: + tested_ans = menu._validate_user_choice(ans) + except InvalidUserChoice as inv_uc: + self.assertEqual(menu.messages["error"], str(inv_uc)) + else: + self.assertEqual(ans, tested_ans) + self.assertNotEqual(ans, tested_ans + "e") + + __test_helper("1") + __test_helper("2") + __test_helper("3") + __test_helper("4") + __test_helper("") + __test_helper("0") + __test_helper("QWERT") + + def test_get_action(self): + + from HomeMenu import HomeMenu + from HomeMenuActions import HomeMenuActions + from io import StringIO + + donations_db = TestMenu._helper_make_db() + ostream_1 = StringIO() + ostream_2 = StringIO() + home_menu_act = HomeMenuActions(donations_db, ostream_1) + home_menu = HomeMenu(donations_db, ostream=ostream_2) + + home_menu_act.create_report() + home_menu._get_action("3")() + self.assertEqual(ostream_1.getvalue(), ostream_2.getvalue()) + + def test_get_action_from_user(self) -> None: + + from io import StringIO + + def __test_helper(user_input: str, bench_str: str, max_trials=3) -> None: + + from HomeMenu import HomeMenu + + ostream = StringIO() + istream = StringIO(user_input) + menu = HomeMenu(TestMenu._helper_make_db(), istream=istream, ostream=ostream) + ans = menu._get_action_from_user() + user_input = user_input.split('\n') + last_user_input = user_input[min(max_trials, len(user_input)) - 1] + self.assertEqual(ostream.getvalue(), bench_str) + try: + self.assertEqual(menu._get_action(last_user_input), ans) + except ValueError as ve: + self.assertEqual("HomeMenu._get_action(ans) - " + "Invalid argument ans", str(ve)) + + from BenchData import HomeMenuBenchData + + menu_tests_data = HomeMenuBenchData() + menu_bench_str = menu_tests_data.get_request_to_user() \ + + menu_tests_data.get_menu() \ + + menu_tests_data.get_input_str() + invalid_plus_menu_bench_str = menu_tests_data.get_menu_error_message() + \ + menu_tests_data.get_request_to_user() \ + + menu_tests_data.get_menu() \ + + menu_tests_data.get_input_str() + menu_quit_str = menu_tests_data.get_quit_message() + + __test_helper("1", menu_bench_str) + __test_helper("2", menu_bench_str) + __test_helper("4", menu_bench_str) + __test_helper("0\n2", invalid_plus_menu_bench_str) + __test_helper("erter\n2", invalid_plus_menu_bench_str) + __test_helper("0\n78\n3", invalid_plus_menu_bench_str) + + def test__perform_action_from_user(self) -> None: + + from BenchData import TestThat + + def __test_helper(user_choice, trace_ref) -> None: + from HomeMenu import HomeMenu + from io import StringIO + istream = StringIO(user_choice) + ostream = StringIO() + menu = HomeMenu(TestMenu._helper_make_db()) + requested_action = menu._menu._get_action_from_user(istream, ostream) + ostream = StringIO() + menu._menu.perform_action_from_user(fun=requested_action, ostream=ostream) + test = TestThat() + print("ostream value: {}".format(ostream.getvalue())) + self.assertEqual(test.get_trace(trace_ref), ostream.getvalue()) + + test_that = TestThat() + if test_that.do(): + __test_helper("1", "send_a_thank_you_email") + __test_helper("2", "create_report") + __test_helper("3", "quit_program") + + __test_helper("4\n2", "create_report") + __test_helper("0\n2", "create_report") + __test_helper("erter\n2", "create_report") + __test_helper("0\n4\n3", "quit_program") + __test_helper("0\n4\nTRE\n2", "quit_program") + __test_helper("0\n0\n0\n0\n0", "quit_program") + + else: + pass + + def test_get_action_from_user_and_perform(self) -> None: + pass diff --git a/stricher/week_10/mailroom/test_HomeMenuActions.py b/stricher/week_10/mailroom/test_HomeMenuActions.py new file mode 100644 index 0000000..9582298 --- /dev/null +++ b/stricher/week_10/mailroom/test_HomeMenuActions.py @@ -0,0 +1,11 @@ +from unittest import TestCase + + +class TestHomeMenuActions(TestCase): + def test_thank_you(self): + pass + #TODO: implement + + def test_create_report(self): + pass + #TODO: implement diff --git a/stricher/week_10/mailroom/test_InvalidUserChoice.py b/stricher/week_10/mailroom/test_InvalidUserChoice.py new file mode 100644 index 0000000..3e6ae7b --- /dev/null +++ b/stricher/week_10/mailroom/test_InvalidUserChoice.py @@ -0,0 +1,22 @@ +from unittest import TestCase + + +class TestInvalidUserChoice(TestCase): + + def test_instantiate(self): + + def __test_helper(): + from InvalidUserChoice import InvalidUserChoice + try: + raise InvalidUserChoice("test_instantiate() invalid argument") + self.fail("Should raise an exception") + except InvalidUserChoice as inv_uc: + except_str = "test_instantiate() invalid argument" + self.assertEqual(except_str, str(inv_uc)) + raise + try: + __test_helper() + self.fail("Should raise exception (reraised in the function)") + except ValueError as ve: + except_str = "test_instantiate() invalid argument" + self.assertEqual(except_str, str(ve)) diff --git a/stricher/week_10/mailroom/test_MyDate.py b/stricher/week_10/mailroom/test_MyDate.py new file mode 100644 index 0000000..56b8003 --- /dev/null +++ b/stricher/week_10/mailroom/test_MyDate.py @@ -0,0 +1,31 @@ +from unittest import TestCase + + +class TestMyDate(TestCase): + + def test_init(self): + from MyDate import MyDate, InvalidDateError + from datetime import datetime + try: + this_date = MyDate(2018, 3, 5) + self.fail("TestMyDate.test_init MyDate(2018, 3, 5)" + " should raise an error") + except InvalidDateError as inv_date_err: + bench_str = "InvalidDateError: " \ + "MyDate(year, month, day, future_date=False)" \ + " - date can't be in the future (date > now)" + self.assertEqual(bench_str, str(inv_date_err)) + try: + this_date = MyDate.from_datetime(datetime.now().date()) + except InvalidDateError: + self.fail("TestMyDate.test_init " + "MyDate.from_datetime" + " shouldn't raise an exception") + else: + self.assertEqual(datetime.now().date(), this_date) + + def test_eq(self): + from MyDate import MyDate + date = MyDate(2014, 5, 23) + self.assertEqual(MyDate(2014, 5, 23), date) + self.assertNotEqual(date, MyDate(2014, 5, 24)) diff --git a/stricher/week_10/mailroom/test_Name.py b/stricher/week_10/mailroom/test_Name.py new file mode 100644 index 0000000..ae6e8ca --- /dev/null +++ b/stricher/week_10/mailroom/test_Name.py @@ -0,0 +1,86 @@ +from unittest import TestCase + + +class BenchDataName: + def __init__(self): + from Name import PersonName + self.prefix = "M" + self.first = "Bud" + self.last = "Powell" + self.name = PersonName(self.prefix, self.first, self.last) + + +class TestPersonName(TestCase): + def test_prefix(self): + from Name import PersonName + try: + name = PersonName("Msss", "Billie", "Holiday") + self.fail("Should raise ValueError") + except ValueError as ve: + bench_str = "InvalidNameError: NameValidation.is_valid_prefix(val: str)" \ + " - prefix must be either M or Ms" + self.assertEqual(bench_str, str(ve)) + + def test___repr__(self): + bench = BenchDataName() + from io import StringIO + ostream = StringIO() + ostream.write(bench.name.__repr__()) + bench_str = 'PersonName("{}","{}","{}")'.format(bench.prefix, bench.first, bench.last) + self.assertEqual(bench_str, ostream.getvalue()) + + def test___str__(self): + bench = BenchDataName() + bench_str = "{} {} {}".format(bench.prefix, bench.first, bench.last) + self.assertEqual(bench_str, str(bench.name)) + + def test___eq__(self): + return self.assertEqual(BenchDataName().name, BenchDataName().name) + + def test___lt__(self): + from Name import PersonName + powell = PersonName("M", "Bud", "Powell") + radigue = PersonName("Ms", "Eliane", "Radigue") + self.assertEqual(True, powell < radigue) + + bach = PersonName("M", "Jean S", "Bach") + + names = [radigue, bach, powell] + names.sort() + self.assertEqual(bach, names[0]) + self.assertEqual(powell, names[1]) + self.assertEqual(radigue, names[-1]) + + def test_get_person_name(self): + from Name import PersonName + from io import StringIO + + bench_str_ans = "M Miles Davis" + bench_str_request = "Please enter the person's name [prefix first_name last_name]:" + ostream = StringIO() + istream = StringIO(bench_str_ans) + self.assertEqual(bench_str_ans, str(PersonName.get_from_stream(ostream, istream))) + self.assertEqual(bench_str_request, ostream.getvalue()) + + bench_str_ans = "Mr Miles Davis\nM Miles Davis" + bench_str_res = "M Miles Davis" + bench_str_request = "Please enter the person's name [prefix first_name last_name]:" \ + "Invalid input. Please try again!\n" \ + "Please enter the person's name [prefix first_name last_name]:" + ostream = StringIO() + istream = StringIO(bench_str_ans) + self.assertEqual(bench_str_res, str(PersonName.get_from_stream(ostream, istream))) + self.assertEqual(bench_str_request, ostream.getvalue()) + + bench_str_ans = "Mr Miles Davis\nMrr Miles Davis\nMrrr Miles Davis" + bench_str_res = "None" + bench_str_request = "Please enter the person's name [prefix first_name last_name]:" \ + "Invalid input. Please try again!\n" \ + "Please enter the person's name [prefix first_name last_name]:" \ + "Invalid input. Please try again!\n" \ + "Please enter the person's name [prefix first_name last_name]:" \ + "Max number of trials. The program quits!\n" + ostream = StringIO() + istream = StringIO(bench_str_ans) + self.assertEqual(bench_str_res, str(PersonName.get_from_stream(ostream, istream))) + self.assertEqual(bench_str_request, ostream.getvalue()) diff --git a/stricher/week_10/mailroom/test_Organisation.py b/stricher/week_10/mailroom/test_Organisation.py new file mode 100644 index 0000000..5e9cd9e --- /dev/null +++ b/stricher/week_10/mailroom/test_Organisation.py @@ -0,0 +1,11 @@ +from unittest import TestCase + + +class TestOrganisation(TestCase): + + def test___repr__(self): + from Organisation import Organisation + + name = "ALP" + address = "12, rue de Lille" + org = Organisation diff --git a/stricher/week_10/mailroom/test_OrganisationBenchData.py b/stricher/week_10/mailroom/test_OrganisationBenchData.py new file mode 100644 index 0000000..5a65b6d --- /dev/null +++ b/stricher/week_10/mailroom/test_OrganisationBenchData.py @@ -0,0 +1,29 @@ +from unittest import TestCase + + +class TestOrganisationBenchData(TestCase): + def test_init(self): + from BenchData import OrganisationBenchData + from PersonId import PersonId + + music_factory = OrganisationBenchData().data + cat_anderson = music_factory.get_member(PersonId().value) + + bench_str = r"""M Cat Anderson +14, rue Beaune +75007 - Paris +France ++33 1 43 56 78 23 +music.factory@drummachine.net +""" + self.assertEqual(bench_str, str(cat_anderson)) + + clifford_brown = music_factory.get_member(PersonId().value) + bench_str = r"""M Clifford Brown +14, rue Beaune +75007 - Paris +France ++33 1 43 56 78 23 +music.factory@drummachine.net +""" + self.assertEqual(bench_str, str(clifford_brown)) diff --git a/stricher/week_10/mailroom/test_OrganisationMember.py b/stricher/week_10/mailroom/test_OrganisationMember.py new file mode 100644 index 0000000..aaf134a --- /dev/null +++ b/stricher/week_10/mailroom/test_OrganisationMember.py @@ -0,0 +1,34 @@ +from unittest import TestCase + + +class TestOrganisationMember(TestCase): + + def test_init(self): + from Organisation import Organisation, OrganisationMember + from Name import PersonName, OrganisationName + from Address import Address + from MyDate import MyDate + + address = Address("4, place Saint-Michel", "75006", "Paris", "France", + "+33 1 43 57 68 35", "thebeat@soundfactory.com") + organisation = Organisation(OrganisationName("SoundFactory"), address) + member = OrganisationMember(PersonName("M", "Nicolas", "Jaar"), + address, + MyDate(1980, 4, 13), + organisation) + self.assertEqual("M", member.name.prefix) + self.assertEqual("Nicolas", member.name.first) + self.assertEqual("Jaar", member.name.last) + + def test_eq(self): + from Organisation import Organisation, OrganisationMember + from Name import PersonName, OrganisationName + from Address import Address + from MyDate import MyDate + + address = Address("4, place Saint-Michel", "75006", "Paris", "France", + "+33 1 43 57 68 35", "thebeat@soundfactory.com") + organisation = Organisation(OrganisationName("SoundFactory"), address) + member = OrganisationMember(PersonName("M", "Nicolas", "Jaar"), address, + MyDate(1980, 4, 13), organisation) + self.assertEqual(member, member) diff --git a/stricher/week_10/mailroom/test_OrganisationName.py b/stricher/week_10/mailroom/test_OrganisationName.py new file mode 100644 index 0000000..264f6dd --- /dev/null +++ b/stricher/week_10/mailroom/test_OrganisationName.py @@ -0,0 +1,23 @@ +from unittest import TestCase + + +class TestOrganisationName(TestCase): + + def test_manip(self): + + from Name import OrganisationName + + name = OrganisationName("MusicFactory") + self.assertEqual('OrganisationName("MusicFactory")', name.__repr__()) + self.assertEqual("MusicFactory", name.__str__()) + self.assertEqual(name, name) + + name2 = OrganisationName("Beats") + self.assertEqual(False, name == name2) + self.assertEqual(True, name2 < name) + self.assertEqual(True, name <= name) + self.assertEqual(True, name > name2) + self.assertEqual(False, name2 > name) + self.assertEqual(True, name > name2) + self.assertEqual(True, name2 >= name2) + diff --git a/stricher/week_10/mailroom/test_Person.py b/stricher/week_10/mailroom/test_Person.py new file mode 100644 index 0000000..89b9a68 --- /dev/null +++ b/stricher/week_10/mailroom/test_Person.py @@ -0,0 +1,42 @@ +from unittest import TestCase + + +class BenchDataPerson: + def __init__(self): + from test_Name import BenchDataName + from test_Address import BenchDataAddress + from MyDate import MyDate + from Person import Person + + self.name = BenchDataName().name + self.address = BenchDataAddress().address + self.birth_date = MyDate(1952, 5, 23) + self.person = Person(self.name, + self.address, + self.birth_date) + + +class TestPerson(TestCase): + + def test___repr__(self): + from io import StringIO + bench = BenchDataPerson() + ostream = StringIO() + ostream.write(bench.person.__repr__()) + bench_str = 'Person({},{},{})'.format(bench.name.__repr__(), + bench.address.__repr__(), + bench.birth_date.__repr__()) + self.assertEqual(bench_str, ostream.getvalue()) + + def test___str__(self): + bench = BenchDataPerson() + bench_str = "{}\n{}".format(bench.name, bench.address) + self.assertEqual(bench_str, str(bench.person)) + + def test__eq__(self): + self.assertEqual(BenchDataPerson().person, BenchDataPerson().person) + + def test_mail(self): + person = BenchDataPerson().person + email = BenchDataPerson().address.mail + self.assertEqual(person.mail(), email) diff --git a/stricher/week_10/mailroom/test_ReportDonationsDBGenerator.py b/stricher/week_10/mailroom/test_ReportDonationsDBGenerator.py new file mode 100644 index 0000000..c6ab645 --- /dev/null +++ b/stricher/week_10/mailroom/test_ReportDonationsDBGenerator.py @@ -0,0 +1,56 @@ +from unittest import TestCase + + +class TestData: + + @staticmethod + def bench_str() -> str: + return \ + '\nList of donors:\n\n' \ + '***************************************************************************************************\n' \ + ' PersonName | Total amount | Number of donations | Average donation |\n' \ + '***************************************************************************************************\n' \ + 'Ms Eliane Radigue | 8000.00| 1| 8000.00|\n' \ + 'M Jean S Bach | 8340.00| 3| 2780.00|\n' \ + 'M Charles Ives | 9221.00| 3| 3073.67|\n' \ + 'M Lee Morgan | 10734.00| 2| 5367.00|\n' \ + 'M Wynton Kelly | 15217.00| 3| 5072.33|\n' \ + 'M Miles Davis | 82000.00| 2| 41000.00|\n' \ + '***************************************************************************************************\n' + + +class TestReportDonationsDBGenerator(TestCase): + + from Database import Database + + @staticmethod + def _init_db() -> Database: + from Database import Database + from BenchData import DonorsDBBenchData + from DonationsTable import DonationsTable + from DonorsTable import DonorsTable + from BenchData import DonationsDBBenchData + + return Database(DonorsTable(DonorsDBBenchData().data), + DonationsTable(DonationsDBBenchData().data)) + + def test_write(self): + + self.maxDiff = None + + from ReportGenerator import ReportDonationsDBGenerator + from io import StringIO + + db = self._init_db() + report_gen = ReportDonationsDBGenerator(db) + + ostream = StringIO() + report_gen.write(ostream) + + self.assertEqual(TestData.bench_str(), ostream.getvalue()) + + def test___str___(self): + from ReportGenerator import ReportDonationsDBGenerator + + db = self._init_db() + self.assertEqual(TestData.bench_str(), str(ReportDonationsDBGenerator(db))) diff --git a/stricher/week_10/mailroom/test_Types.py b/stricher/week_10/mailroom/test_Types.py new file mode 100644 index 0000000..c18631a --- /dev/null +++ b/stricher/week_10/mailroom/test_Types.py @@ -0,0 +1,10 @@ +from unittest import TestCase + + +class TestInfoType(TestCase): + def test_InfoType(self): + from Types import InfoType + info_type = InfoType.DONOR + self.assertEqual('donor', info_type.value) + info_type = InfoType.AMOUNT + self.assertEqual('amount', info_type.value) diff --git a/stricher/week_10/mailroom/test_emailAddress.py b/stricher/week_10/mailroom/test_emailAddress.py new file mode 100644 index 0000000..38c76cd --- /dev/null +++ b/stricher/week_10/mailroom/test_emailAddress.py @@ -0,0 +1,14 @@ +from unittest import TestCase + + +class TestEmailAddress(TestCase): + + def test_get(self): + from EmailAddress import EmailAddress + mail = EmailAddress("nobody@nowhere.com") + self.assertEqual("nobody@nowhere.com", mail.get) + + def test_str(self): + from EmailAddress import EmailAddress + mail = EmailAddress("foo@bar.com") + self.assertEqual("foo@bar.com", str(mail)) diff --git a/stricher/week_10/mailroom/test_thankYouEmailMenu.py b/stricher/week_10/mailroom/test_thankYouEmailMenu.py new file mode 100644 index 0000000..0474f99 --- /dev/null +++ b/stricher/week_10/mailroom/test_thankYouEmailMenu.py @@ -0,0 +1,32 @@ +from unittest import TestCase + + +class TestThankYouEmailMenu(TestCase): + from Database import Database + + @staticmethod + def __init_db(self) -> Database: + + from Database import Database + from DonorsTable import DonorsTable + from DonationsTable import DonationsTable + from BenchData import DonorsDBBenchData, DonationsDBBenchData + + return Database(DonorsTable(DonorsDBBenchData().data), + DonationsTable(DonationsDBBenchData().data)) + + def test__is_valid_choice(self): + #TODO: implement + pass + + def test__validate_user_choice(self): + # TODO: implement + pass + + def test__make_request_string(self): + # TODO: implement + pass + + def test_get_action_from_user_and_perform(self): + # TODO: implement + pass diff --git a/stricher/week_10/tdd_using_pycharm.pdf b/stricher/week_10/tdd_using_pycharm.pdf new file mode 100644 index 0000000..5fbf800 Binary files /dev/null and b/stricher/week_10/tdd_using_pycharm.pdf differ