diff --git a/students/ericstreit/session07/html_render.py b/students/ericstreit/session07/html_render.py new file mode 100644 index 00000000..67f38535 --- /dev/null +++ b/students/ericstreit/session07/html_render.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +####stopped at step 4 +""" +A class-based system for rendering html. +""" + + +# This is the framework for the base class +class Element(object): + + tag = "html" + + def __init__(self, content=None, **kwargs): + self.contents = [content] + # print("contents is:", self.contents) + + def append(self, new_content): + self.contents.append(new_content) + + def render(self, out_file): + # Loop through the list of contents: + out_file.write("<{}>\n".format(self.tag)) + for content in self.contents: + if content is not None: + try: + content.render(out_file) + except AttributeError: + out_file.write(content) + out_file.write("\n") + out_file.write("\n".format(self.tag)) + +class Html(Element): + + tag = "html" + +class Body(Element): + + tag = "body" + +class P(Element): + + tag = "p" + +class Head(Element): + + tag = "head" + +class OneLineTag(Element): + + def render(self, out_file): + # One line render: + #removing my way and implementing the tutorial way + #out_file.write("<{}> {} \n".format(self.tag, self.contents, self.tag)) + out_file.write("<{}>".format(self.tag)) + out_file.write(self.contents[0]) + out_file.write("\n".format(self.tag)) + + def append(self, content): + raise NotImplementedError + +class Title(OneLineTag): + + tag = "title" diff --git a/students/ericstreit/session07/run_html_render.py b/students/ericstreit/session07/run_html_render.py new file mode 100644 index 00000000..9608a65f --- /dev/null +++ b/students/ericstreit/session07/run_html_render.py @@ -0,0 +1,231 @@ +#!/usr/bin/env python3 + +""" +a simple script can run and test your html rendering classes. + +Uncomment the steps as you add to your rendering. + +""" + +from io import StringIO + +# importing the html_rendering code with a short name for easy typing. +import html_render as hr + + +# writing the file out: +def render_page(page, filename, indent=None): + """ + render the tree of elements + + This uses StringIO to render to memory, then dump to console and + write to file -- very handy! + """ + + f = StringIO() + if indent is None: + page.render(f) + else: + page.render(f, indent) + + print(f.getvalue()) + with open(filename, 'w') as outfile: + outfile.write(f.getvalue()) + + +# Step 1 +######### + +page = hr.Element() + +page.append("Here is a paragraph of text -- there could be more of them, " + "but this is enough to show that we can do some text") + +page.append("And here is another piece of text -- you should be able to add any number") + +render_page(page, "test_html_output1.html") + +# The rest of the steps have been commented out. +# Uncomment them as you move along with the assignment. + +# ## Step 2 +# ########## + +# page = hr.Html() + +# body = hr.Body() + +# body.append(hr.P("Here is a paragraph of text -- there could be more of them, " +# "but this is enough to show that we can do some text")) + +# body.append(hr.P("And here is another piece of text -- you should be able to add any number")) + +# page.append(body) + +# render_page(page, "test_html_output2.html") + +# # Step 3 +# ########## + +# page = hr.Html() + +# head = hr.Head() +# head.append(hr.Title("PythonClass = Revision 1087:")) + +# page.append(head) + +# body = hr.Body() + +# body.append(hr.P("Here is a paragraph of text -- there could be more of them, " +# "but this is enough to show that we can do some text")) +# body.append(hr.P("And here is another piece of text -- you should be able to add any number")) + +# page.append(body) + +# render_page(page, "test_html_output3.html") + +# # Step 4 +# ########## + +# page = hr.Html() + +# head = hr.Head() +# head.append(hr.Title("PythonClass = Revision 1087:")) + +# page.append(head) + +# body = hr.Body() + +# body.append(hr.P("Here is a paragraph of text -- there could be more of them, " +# "but this is enough to show that we can do some text", +# style="text-align: center; font-style: oblique;")) + +# page.append(body) + +# render_page(page, "test_html_output4.html") + +# # Step 5 +# ######### + +# page = hr.Html() + +# head = hr.Head() +# head.append(hr.Title("PythonClass = Revision 1087:")) + +# page.append(head) + +# body = hr.Body() + +# body.append(hr.P("Here is a paragraph of text -- there could be more of them, " +# "but this is enough to show that we can do some text", +# style="text-align: center; font-style: oblique;")) + +# body.append(hr.Hr()) + +# page.append(body) + +# render_page(page, "test_html_output5.html") + +# # Step 6 +# ######### + +# page = hr.Html() + +# head = hr.Head() +# head.append(hr.Title("PythonClass = Revision 1087:")) + +# page.append(head) + +# body = hr.Body() + +# body.append(hr.P("Here is a paragraph of text -- there could be more of them, " +# "but this is enough to show that we can do some text", +# style="text-align: center; font-style: oblique;")) + +# body.append(hr.Hr()) + +# body.append("And this is a ") +# body.append( hr.A("http://google.com", "link") ) +# body.append("to google") + +# page.append(body) + +# render_page(page, "test_html_output6.html") + +# # Step 7 +# ######### + +# page = hr.Html() + +# head = hr.Head() +# head.append(hr.Title("PythonClass = Revision 1087:")) + +# page.append(head) + +# body = hr.Body() + +# body.append( hr.H(2, "PythonClass - Class 6 example") ) + +# body.append(hr.P("Here is a paragraph of text -- there could be more of them, " +# "but this is enough to show that we can do some text", +# style="text-align: center; font-style: oblique;")) + +# body.append(hr.Hr()) + +# list = hr.Ul(id="TheList", style="line-height:200%") + +# list.append( hr.Li("The first item in a list") ) +# list.append( hr.Li("This is the second item", style="color: red") ) + +# item = hr.Li() +# item.append("And this is a ") +# item.append( hr.A("http://google.com", "link") ) +# item.append("to google") + +# list.append(item) + +# body.append(list) + +# page.append(body) + +# render_page(page, "test_html_output7.html") + +# # Step 8 and 9 +# ############## + +# page = hr.Html() + + +# head = hr.Head() +# head.append( hr.Meta(charset="UTF-8") ) +# head.append(hr.Title("PythonClass = Revision 1087:")) + +# page.append(head) + +# body = hr.Body() + +# body.append( hr.H(2, "PythonClass - Example") ) + +# body.append(hr.P("Here is a paragraph of text -- there could be more of them, " +# "but this is enough to show that we can do some text", +# style="text-align: center; font-style: oblique;")) + +# body.append(hr.Hr()) + +# list = hr.Ul(id="TheList", style="line-height:200%") + +# list.append( hr.Li("The first item in a list") ) +# list.append( hr.Li("This is the second item", style="color: red") ) + +# item = hr.Li() +# item.append("And this is a ") +# item.append( hr.A("http://google.com", "link") ) +# item.append("to google") + +# list.append(item) + +# body.append(list) + +# page.append(body) + +# render_page(page, "test_html_output8.html") diff --git a/students/ericstreit/session07/sample_html.html b/students/ericstreit/session07/sample_html.html new file mode 100644 index 00000000..9c2e675d --- /dev/null +++ b/students/ericstreit/session07/sample_html.html @@ -0,0 +1,27 @@ + + + + + Python Class Sample page + + +

Python Class - Html rendering example

+

+ Here is a paragraph of text -- there could be more of them, but this is enough to show that we can do some text +

+
+ + + \ No newline at end of file diff --git a/students/ericstreit/session07/test_html_render.py b/students/ericstreit/session07/test_html_render.py new file mode 100644 index 00000000..0873e97a --- /dev/null +++ b/students/ericstreit/session07/test_html_render.py @@ -0,0 +1,330 @@ +""" +test code for html_render.py + +This is just a start -- you will need more tests! +""" + +import io +import pytest + +# import * is often bad form, but makes it easier to test everything in a module. +from html_render import * + + +# utility function for testing render methods +# needs to be used in multiple tests, so we write it once here. +def render_result(element, ind=""): + """ + calls the element's render method, and returns what got rendered as a + string + """ + # the StringIO object is a "file-like" object -- something that + # provides the methods of a file, but keeps everything in memory + # so it can be used to test code that writes to a file, without + # having to actually write to disk. + outfile = io.StringIO() + # this so the tests will work before we tackle indentation + if ind: + element.render(outfile, ind) + else: + element.render(outfile) + return outfile.getvalue() + +######## +# Step 1 +######## + +def test_init(): + """ + This only tests that it can be initialized with and without + some content -- but it's a start + """ + e = Element() + + e = Element("this is some text") + + +def test_append(): + """ + This tests that you can append text + + It doesn't test if it works -- + that will be covered by the render test later + """ + e = Element("this is some text") + e.append("some more text") + + +def test_render_element(): + """ + Tests whether the Element can render two pieces of text + So it is also testing that the append method works correctly. + + It is not testing whether indentation or line feeds are correct. + """ + e = Element("this is some text") + e.append("and this is some more text") + + # This uses the render_results utility above + file_contents = render_result(e).strip() + + # making sure the content got in there. + assert("this is some text") in file_contents + assert("and this is some more text") in file_contents + + # make sure it's in the right order + assert file_contents.index("this is") < file_contents.index("and this") + + # making sure the opening and closing tags are right. + assert file_contents.startswith("") + assert file_contents.endswith("") + + #see the results + #print(file_contents) + #assert False + + # count how many html tags are there + assert file_contents.count("") == 1 + assert file_contents.count("") == 1 + +# # Uncomment this one after you get the one above to pass +# # Does it pass right away? +def test_render_element2(): + """ + Tests whether the Element can render two pieces of text + So it is also testing that the append method works correctly. + + It is not testing whether indentation or line feeds are correct. + """ + e = Element() + e.append("this is some text") + e.append("and this is some more text") + + # This uses the render_results utility above + file_contents = render_result(e).strip() + + # making sure the content got in there. + assert("this is some text") in file_contents + assert("and this is some more text") in file_contents + + # make sure it's in the right order + assert file_contents.index("this is") < file_contents.index("and this") + + # making sure the opening and closing tags are right. + assert file_contents.startswith("") + assert file_contents.endswith("") + + + +# # ######## +# # # Step 2 +# # ######## + +# # tests for the new tags +def test_html(): + e = Html("this is some text") + e.append("and this is some more text") + + file_contents = render_result(e).strip() + + assert("this is some text") in file_contents + assert("and this is some more text") in file_contents + print(file_contents) + assert file_contents.endswith("") + +def test_body(): + e = Body("this is some text") + e.append("and this is some more text") + + file_contents = render_result(e).strip() + + assert("this is some text") in file_contents + assert("and this is some more text") in file_contents + + assert file_contents.startswith("") + assert file_contents.endswith("") + + +def test_p(): + e = P("this is some text") + e.append("and this is some more text") + + file_contents = render_result(e).strip() + + assert("this is some text") in file_contents + assert("and this is some more text") in file_contents + + assert file_contents.startswith("

") + assert file_contents.endswith("

") + + +def test_sub_element(): + """ + tests that you can add another element and still render properly + """ + page = Html() + page.append("some plain text.") + page.append(P("A simple paragraph of text")) + page.append("Some more plain text.") + + file_contents = render_result(page) + print(file_contents) # so we can see it if the test fails + + # note: The previous tests should make sure that the tags are getting + # properly rendered, so we don't need to test that here. + assert "some plain text" in file_contents + assert "A simple paragraph of text" in file_contents + assert "Some more plain text." in file_contents + assert "some plain text" in file_contents + # but make sure the embedded element's tags get rendered! + assert "

" in file_contents + assert "

" in file_contents + + + + +######## +# Step 3 +######## + +# Add your tests here! +def test_head(): + e = Head("this is some text") + e.append("and this is some more text") + + file_contents = render_result(e).strip() + + assert("this is some text") in file_contents + assert("and this is some more text") in file_contents + print(file_contents) + assert file_contents.startswith("") + assert file_contents.endswith("") + +def test_title(): + e = Title("PythonClass - title example") + #e.append("title example") + + file_contents = render_result(e).strip() + + assert("PythonClass - title example") in file_contents + + assert file_contents.startswith("") + assert file_contents.endswith("") + + assert "\n" not in file_contents + +def test_one_line_tag_append(): + """ + You should not be able to append content to a OneLineTag + """ + e = OneLineTag("the initial content") + with pytest.raises(NotImplementedError): + e.append("some more content") + + file_contents = render_result(e).strip() + print(file_contents) + +def test_attributes(): + e = P("A paragraph of text", style="text-align: center", id="intro") + + file_contents = render_result(e).strip() + print(file_contents) # so we can see it if the test fails + + # note: The previous tests should make sure that the tags are getting + # properly rendered, so we don't need to test that here. + # so using only a "P" tag is fine + assert "A paragraph of text" in file_contents + # but make sure the embedded element's tags get rendered! + # first test the end tag is there -- same as always: + assert file_contents.endswith("

") + + # but now the opening tag is far more complex + # but it starts the same: + assert file_contents.startswith(" tag +# assert lines[i + 1].startswith(i * Element.indent + "<") + +# assert lines[4].startswith(3 * Element.indent + "some") + + +# def test_element_indent1(): +# """ +# Tests whether the Element indents at least simple content + +# we are expecting to to look like this: + +# +# this is some text +# <\html> + +# More complex indentation should be tested later. +# """ +# e = Element("this is some text") + +# # This uses the render_results utility above +# file_contents = render_result(e).strip() + +# # making sure the content got in there. +# assert("this is some text") in file_contents + +# # break into lines to check indentation +# lines = file_contents.split('\n') +# # making sure the opening and closing tags are right. +# assert lines[0] == "" +# # this line should be indented by the amount specified +# # by the class attribute: "indent" +# assert lines[1].startswith(Element.indent + "thi") +# assert lines[2] == "" +# assert file_contents.endswith("") diff --git a/students/ericstreit/session08/circle_class.py b/students/ericstreit/session08/circle_class.py new file mode 100644 index 00000000..9e5b6e41 --- /dev/null +++ b/students/ericstreit/session08/circle_class.py @@ -0,0 +1,68 @@ +#Lesson08 +#Circle Class Exercise ## +# +#!/usr/bin/env python3 +import math +#define class +class Circle(object): + """ + Description of class + + :param arg1: The first very important parameter. And a bit about + what it means. + :param arg2: The second very important parameter. And now some + description of how this is used + etc + """ + def __init__(self, rad): + self.radius = rad + + def diameter(self): + return self.radius * 2 + + def area(self): + return 3.14 * (self.radius * self.radius) + +### make an alternate constructor! + + def __repr__(self): + return "Circle({})".format(self.radius) + + def __str__(self): + return "Circle with a radius of: {}".format(self.radius) + + def __eq__(self, other): + if type(other) == int: + return (self.radius == other) + else: + return (self.radius == other.radius) + + def __lt__(self, other): + return (self.radius < other.radius) + + def __add__(self, other): + return Circle(self.radius + other.radius) + + def __mul__(self, other): + if type(other) == int: + return Circle(self.radius * other) + else: + return Circle(self.radius * other.radius) + +class Sphere(Circle): + + def volume(self): + return int((4 / 3) * 3.14 * (self.radius ** 3)) + + def __repr__(self): + return "Sphere({})".format(self.radius) + + def __str__(self): + return "Sphere with a volume of: {}".format(self.volume()) + + def area(self): + raise NotImplementedError('Please run the surface_area function instead') + +#for testing +if __name__=="__main__": + pass diff --git a/students/ericstreit/session08/test_circle_class.py b/students/ericstreit/session08/test_circle_class.py new file mode 100644 index 00000000..be540ffb --- /dev/null +++ b/students/ericstreit/session08/test_circle_class.py @@ -0,0 +1,62 @@ +#Lesson## +#XXXXX Exercise ## +# +#!/usr/bin/env python3 +from circle_class import * +import pytest + + + +def test_radius(): + c = Circle(4) + assert c.radius == 4 + +def test_diameter(): + c = Circle(4) + assert c.diameter() == 8 + +def test_area(): + c = Circle(4) + assert c.area() == 50.24 + +def test_repr(): + c = Circle(4) + assert repr(c) == "Circle(4)" + +def test_equals(): + c = Circle(4) + d = Circle(4) + assert c == d + +def test_add(): + c = Circle(4) + d = Circle(5) + assert c + d == Circle(9) + +def test_multiply(): + assert Circle(10) * Circle(3) == Circle(30) + +def test_sort(): + circles = [Circle(6), Circle(5), Circle(4), Circle(3), Circle(0), Circle(1), Circle(2)] + circles.sort() + assert circles == [Circle(0), Circle(1), Circle(2), Circle(3), Circle(4), Circle(5), Circle(6)] + +def test_multiply_int(): + #c1 = Circle(50) + assert Circle(50) * 3 == 150 + +# def test_multiply_int_reverse(): +# I don't know why this one does not work :( +# #c1 = Circle(50) +# assert 3 * Circle(50) == 150 + +def test_volume(): + s1 = Sphere(5) + assert s1.volume() == 523 + + + + +#for testing +if __name__=="__main__": + pass diff --git a/students/ericstreit/session09/cli_main.py b/students/ericstreit/session09/cli_main.py new file mode 100644 index 00000000..c98933cf --- /dev/null +++ b/students/ericstreit/session09/cli_main.py @@ -0,0 +1,84 @@ +#Lesson09 +#Objected Oriented Mailroom Exercise 09 - CLI Menu +# +#!/usr/bin/env python3 +#importe modules +from donor_models import * +from pathlib import Path +from sys import exit +import os + +## OK so this ended up turning more into a config file than the menu cli since +# the menus were all created as classes in the donor_models.py file. + + + +#define variables + +data_folder = r'C:\pythonuw\Fall2018-PY210A\students\ericstreit\files' +# note to self: make another one like above for the instructor that points to the current folder? + +#define menus +# This creates the menu as a class + +#should the other menus be subclasses??? probably! (DONE!) + +main_menu = Menu("MAIN MENU") +report_menu = Report("REPORT MENU") +thankyou_menu = ThankYou("SEND A THANK YOU") + + +# define menu options +# These are the choices that will display to the user in a menu +# Be sure to create a dictionary below that will tie to the choices + +main_menu.menu_options = ["(R)eport Menu", "(S)end a Thank You", + "(A)dd a new donation", + "(Q)uit"] + + +report_menu.menu_options = ["Create report of (A)ll donors", + "Create report of a (S)ingle donor", + "(B)ack to Main Menu", + "(Q)uit"] + + + +thankyou_menu.menu_options = ["Send a Thank You to a (S)ingle donor", + "Send a Thank You to (A)ll donors", + "(B)ack to Main Menu", + "(Q)uit"] + + + +#define the menu dictionaries +#each selection should point to a function + +main_menu.dict = {"r": report_menu.menu, "s": thankyou_menu.menu, "a": main_menu.new_donation, "q": main_menu.quit} +report_menu.dict = {"a": report_menu.full_donor_report, "s": report_menu.single_donor_report, "b": main_menu.menu, "q": report_menu.quit} +thankyou_menu.dict = {"a": thankyou_menu.all_donor_thankyou, "s": thankyou_menu.single_donor_thankyou, "b": main_menu.menu, "q": thankyou_menu.quit} + +#let's put some donors in here for testing! + +hestershaw = Donors("Hester Shaw") +donor_dict['hestershaw'] = hestershaw +hestershaw.add_donations(500) +hestershaw.add_donations(34) + +grike = Donors("Grike") +donor_dict['grike'] = grike +grike.add_donations(3) + +tomnatsworthy = Donors("Tom Natsworthy") +donor_dict['tomnatsworthy'] = tomnatsworthy +tomnatsworthy.add_donations(1003) +tomnatsworthy.add_donations(745) +tomnatsworthy.add_donations(10) + +# run the program! + +main_menu.menu() + +#for testing +if __name__=="__main__": + main_menu.menu diff --git a/students/ericstreit/session09/donor_models.py b/students/ericstreit/session09/donor_models.py new file mode 100644 index 00000000..4cdf7581 --- /dev/null +++ b/students/ericstreit/session09/donor_models.py @@ -0,0 +1,316 @@ +#Lesson09 +#Objected Oriented Mailroom Exercise 09 - Classes +# +#!/usr/bin/env python3 +from textwrap import dedent +from pathlib import Path +from sys import exit +import os + + +# define any global variables or datasets +donor_dict = {} +data_folder = r'C:\pythonuw\Fall2018-PY210A\students\ericstreit\files' + + + +# define classes and functions + +class Donors(): + def __init__(self, name, donations=0): + self.name = name + self.donations = [donations] + self.last_donation = 0 + + def __str__(self): + # format the name so that it is all one lowercase string without spaces. + # return this value. Note to self, it seems like this could be used for + # something useful but I'm not really taking advantage of it. + return self.str_name(self.name) + + def __repr__(self): + # omg I should have added this earlier!!! really killed some time trying + # to troubleshoot something. Hey, now I know! + return self.str_name(self.name) + + def str_name(self, name): + # yeah, I could prob incorporate this all in the __str__ function above couldn't I? + # Also, I don't think I'm using this anymore......I think donor_name_format + # replaced this. Why did I do that again? + name = name.lower() + name = name.replace(" ", "") + return name + + # so some of these may be better as class methods? I don't 100% understand + # those yet but this works for now. + + def add_donations(self, new_donation): + self.donations.append(new_donation) + self.last_donation = new_donation + return self.donations + + def sum_donations(self): + # yeah, this seems unneeded? Do I use it anywhere??? + return sum(self.donations) + + def avg_donations(self): + # I don't think I ended up using this function + return self.sum_donations() / (len(self.donations) -1) + + def thank_you(self): + # generates the snarky thank you email text + """This function composes the thank you email txt""" + return dedent(''' + \n\nEMAIL SENT:\n + Dear {},\n + Thank you for your recent donation of ${:.2f}! We will be sure to ask you for more money again soon. + + Sincerely, + Donations, Inc'''.format(self.name, self.last_donation)) + + +class Menu(): + """ + Root Menu class containg display name and pointers to dictionary containing functions. + """ + def __init__(self, name): + self.menu_name = name + self.menu_options = {} + self.dict = {} + + def menu(self): + """ Takes no arguments. This is the menu screen that folks see. It calls upon the menu class object + which in turn displays the menu choices available and then calls the safe + input function to take the choice """ + + print("-------------------------------------------\n") + print("\n{}\n".format(self.menu_name)) + print("\nPlease select from the following choices\n") + print("-------------------------------------------\n") + for option in self.menu_options: + print(option) + # use the safe input function here + self.menu_input() + + + def menu_input(self): + """ Takes no arguments. Menu choice input with built in try function to handle error handlingself. + Formats the choice to a single lower case letter and passes that letter to the + menus dictionary which should contain a matching key with a corresponding + function to call as a value """ + try: + #the formmating could be made as its own function? + choice = input(": ") + #removes any spaces in the choice + choice = choice.strip() + #takes the first letter of the choice and makes it lower case + choice = choice[0:1].lower() + #except KeyboardInterrupt as the_error: + #print("I believe that is an error") + #print(the_error) + self.menu_choice(choice) + #return choice + except IOError: + print("error") + except KeyboardInterrupt: + quit() + + + def menu_choice(self, choice): + """ function that calls the menu providing the argument choice which should + correspond to a key/value pair in the menu dict""" + try: + self.dict.get(choice)() + except TypeError: + print("That is not a valid selection!") + self.menu() + + + def quit(self): + """Takes no arguments. Function to quit the program, self explanatory""" + print("Goodbye!") + exit() + + def new_donation(self): + """ this calls the donor input function """ + #this function feels pretty long, I feel like I could clean up and make some of + #this stuff additional functions? + print("Enter the donors name") + display_name = self.name_input() + #pass the user imput to our formatter to make the string our standard + #db naming convention + format_name = self.donor_name_format(display_name) + #now check to see if the donor exists in the db. If not, then pass to + #the function that creates new donor objects + if not self.donor_exists(format_name): + self.donor_add(format_name, display_name) + #now let's update that donor object with the amount they donated + print("Please enter the amount the donor donated") + money = self.donation_input() + #ok now we need to convert that donor string to the instnace object + obj_name = self.get_obj_name(format_name) + #we can now pass that object as an argument to the next function which + #will update the instance attribute list that contains donations + obj_name.add_donations(money) + self.menu() + + def name_input(self): + name = input(": ") + if name != "": + return name + else: + print("Nothing entered, back to main menu!") + self.menu() + #self.donor_name_format(name) + + def donation_input(self): + # takes the input as a float. if an error kicks back to the menu because + # I haven't figured out how to loop back to the input w/out some weird + # behavior + try: + money = float(int(input(": "))) + except ValueError: + print("Please enter a valid ammount (ie $100, or $99.99)") + # if I loop this back to self.donation_input() it get's weird + self.menu() + except: + print("Please enter a valid ammount (ie $100, or $99.99)") + # if I loop this back to self.donation_input() it get's weird + self.menu() + return money + + + def donor_name_format(self, name): + # format the name so that it is all one lowercase string without any spaces. + # return this value + name = name.lower() + name = name.replace(" ", "") + return name + + def donor_exists(self, format_name): + #return True or False if the argument (which should be the formatted + #name of the donor) is in the donor_dict db as a key + return format_name in donor_dict + + def donor_add(self, format_name, display_name): + #take the formatted name value and create an instance of it + format_name = Donors(display_name) + #convert back that + donor_dict[str(format_name)] = format_name + + def add_donations(self, obj_name, money): + # simple runs the class function that adds money to the object attribute + # that is a list of all donations + obj_name.add_donations(money) + + def get_obj_name(self, format_name): + # the donor_dict db contains a key value of the donor. The key being the + # formatted name 'johnsmith' (which is a string) and the value being the + # object instance name johnsmith. This function is to basically convert + # a variable string into the object. It does this by returning the value + # (which is an object) of the key (which is the string) + # + # it took me hours to figure out that I needed to do things this way + # and how to get it to work + return donor_dict.get(format_name) + +class ThankYou(Menu): + + def single_donor_thankyou(self): + print("Please enter the name of the donor you would like to thank.") + display_name = self.name_input() + format_name = self.donor_name_format(display_name) + if not self.donor_exists(format_name): + print("I was not able to find that donor!") + self.menu() + obj_name = self.get_obj_name(format_name) + self.file_write(format_name, obj_name) + print("Thank you sent to {}!".format(format_name)) + self.menu() + + def all_donor_thankyou(self): + for donor in donor_dict: + obj_name = self.get_obj_name(donor) + self.file_write(donor, obj_name) + print("A Thank You email has been sent to all donors!") + self.menu() + + + def file_write(self, format_name, obj_name): + """This function will call upon the compose_email function and then write + the contents to a file with the donors name""" + #replaces whitespace with underscores and add the .txt extension to the end. Stolen from teacher :) + filename = format_name.replace(" ", "_") + ".txt" + data_file = os.path.join(data_folder, filename) + with open(data_file, 'w') as outfile: + outfile.write(obj_name.thank_you()) + outfile.close + + +class Report(Menu): + + def full_donor_report(self): + """ This menu function will print out an entire report consisting of all + donors. + """ + self.donor_report_header_display() + # loop thru a function that will create a sorted list of donors + for obj_name in self.sorted_donors(donor_dict, True): + # print out the donors details + self.donor_report_detail(obj_name) + self.menu() + + def sorted_donors(self, dict, rev): + """ This function will create a new sorted dict. The first argument picks + the dict to use, the second argument whether the sort value should be + reversed or not + """ + # create a temporary dict to work with + temp_dict = {} + # lets loop thru our donor dict, grabbing the value which is the donor object + for donor_name, donor_obj in dict.items(): + # this appends to the temp dict with the donor name obj as the key + # and their total sum of donations as the value + temp_dict[donor_obj] = donor_obj.sum_donations() + # return this dict back to the function that called it + return sorted((temp_dict), key = temp_dict.get, reverse = rev) + + + def single_donor_report(self): + """ This menu function will print out a single report for a donor.""" + print("Please enter the name of the donor you are looking for") + #grabs the + display_name = self.name_input() + #formats the inputted string to our standard db naming convention + format_name = self.donor_name_format(display_name) + #checks to see if the donor exists + if not self.donor_exists(format_name): + print("I was not able to find that donor!") + self.menu() + #if it exists we now need to convert the formatted string to the object instance + obj_name = self.get_obj_name(format_name) + #now let's print the report, first showing the header + self.donor_report_header_display() + #and now printing the object instances values + self.donor_report_detail(obj_name) + input("Press any key to continue") + self.menu() + + def donor_report_header_display(self): + #prints out the header on a report + print("\n", "\n", "{:<25}{:5}{:5}{:5}{}".format("Donor Name", "| Total Given", "| Num Gifts", "| Average Gift", "| Last Gift")) + print("{:-<80}".format("")) + + def donor_report_detail(self, obj_name): + #prints out a donors contributions details, which coincide with the + #donor_report_header_display function + print("{:<26} ${:<11.2f} {:<11} ${:<11.2f} ${:.2f}" + .format(obj_name.name, sum(obj_name.donations), (len(obj_name.donations) -1), + sum(obj_name.donations) / (len(obj_name.donations) -1), + obj_name.last_donation)) + + + +#for testing +if __name__=="__main__": + pass diff --git a/students/ericstreit/session09/test_mailroom_oo.py b/students/ericstreit/session09/test_mailroom_oo.py new file mode 100644 index 00000000..c9fa9e6a --- /dev/null +++ b/students/ericstreit/session09/test_mailroom_oo.py @@ -0,0 +1,66 @@ +#Lesson09 +#Objected Oriented Mailroom Exercise 09 - Test Code +# +#!/usr/bin/env python3 +from donor_models import * +#define function +def test_name(): + bob = Donors("Bob Smith") + assert bob.name == "Bob Smith" + +def test_add_donations(): + bob = Donors("Bob Smith") + assert bob.add_donations(400) == [0, 400] + assert bob.add_donations(600) ==[0, 400, 600] + assert bob.add_donations(844) ==[0, 400, 600, 844] + +def test_sum_donations(): + bob = Donors("Bob Smith") + assert bob.add_donations(400) == [0, 400] + assert bob.add_donations(600) ==[0, 400, 600] + assert bob.add_donations(500) ==[0, 400, 600, 500] + assert bob.sum_donations() == 1500 + +def test_str(): + bob = Donors("Bob Smith") + assert bob.add_donations(400) == [0, 400] + assert bob.add_donations(600) ==[0, 400, 600] + assert bob.add_donations(500) ==[0, 400, 600, 500] + assert "Donor, Bob Smith, with a total donation amount of: $1500" + +def test_avg_donations(): + bob = Donors("Bob Smith") + assert bob.add_donations(400) == [0, 400] + assert bob.add_donations(600) ==[0, 400, 600] + assert bob.add_donations(500) ==[0, 400, 600, 500] + assert bob.avg_donations() == 500 + +def test_main_menu(): + mainmenu = Menu("Main Menu") + assert mainmenu.menu_name == "Main Menu" + +def test_donor_name_format(): + Test = Menu("Test Menu") + assert Test.donor_name_format("Bob Smith") == "bobsmith" + +def test_donor_list(): + test_menu = Menu("Test Menu") + #assert test_menu.donor_exists("hestershaw") == True + +def test_donor_add(): + test_menu = Menu("Test Menu") + test_menu.donor_add('joebob', "Joe Bob") + assert test_menu.donor_exists("joebob") + +def test_get_obj_name(): + # doesn't work - I've coded my project in a way that is hard to test. prob + # means it is not a good design? + test_menu = Menu("Test Menu") + test_menu.donor_add('joebob', "Joe Bob") + #assert test_menu.get_obj_name('joebob') == joebob + + + +#for testing +if __name__=="__main__": + pass