From fd52bde61d5bdcf0607f17fb702ae6b6d84fe3e6 Mon Sep 17 00:00:00 2001 From: Mercrist Date: Tue, 12 Apr 2022 18:27:58 -0400 Subject: [PATCH 01/13] Cleared up confusing reviews tests with comments --- tests/test_reviews.py | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/tests/test_reviews.py b/tests/test_reviews.py index 8d52bb1..f2152b5 100644 --- a/tests/test_reviews.py +++ b/tests/test_reviews.py @@ -15,36 +15,46 @@ def setUp(self): def test_init_required(self): 'Type Errors' + # not a company object self.assertRaises(TypeError, Review, 'Boeing', "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4) + # title is of the wrong type self.assertRaises(TypeError, Review, self.comp1, {"Title"}, 'Software Engineering', "Position", 4, "M.S.", "Interview", 4) + # Position is None self.assertRaises(TypeError, Review, self.comp1, "Title", 'Software Engineering', None, 4, "M.S.", "Interview", 4) + # job is of the wrong type self.assertRaises(TypeError, Review, self.comp1, "Title", ['Software Engineering'], "Position", 4, "M.S.", "Interview", 4) + # position is of the wrong type self.assertRaises(TypeError, Review, self.comp1, "Title", 'Software Engineering', ["Position"], 4, "M.S.", "Interview", 4) + # score is of the wrong type self.assertRaises(TypeError, Review, self.comp1, "Title", 'Software Engineering', "Position", hex(4), "M.S.", "Interview", 4) + # score cant be a float self.assertRaises(TypeError, Review, self.comp1, "Title", 'Software Engineering', "Position", 3.90, "M.S.", "Interview", 4) self.assertRaises(TypeError, Review, self.comp1, "Title", 'Software Engineering', "Position", 0.30, "M.S.", "Interview", 4) + # education is of the wrong type self.assertRaises(TypeError, Review, self.comp2, "Title", 'Software Engineering', "Position", 4, ord("M"), "Interview", 4) + # description is of the wrong time self.assertRaises(TypeError, Review, self.comp2, "Title", 'Software Engineering', "Position", 4, "M.S.", ["Review"], 4) + # score is of the wrong type self.assertRaises(TypeError, Review, self.comp2, "Title", 'Software Engineering', "Position", 4, "M.S.", "Review", hex(4)) @@ -57,31 +67,38 @@ def test_init_required(self): self.assertRaises(TypeError, Review, self.comp2, "Title", 'Software Engineering', "Position", 4, "M.S.", "Review", float(2)) 'Value Errors' + # title is empty self.assertRaises(ValueError, Review, self.comp1, "", 'Software Engineering', "Position", 4, "M.S.", "Review", 4) + # position is empty self.assertRaises(ValueError, Review, self.comp1, "Title", 'Software Engineering', "", 4, "M.S.", "Review", 4) - + # SWE is not a job category self.assertRaises(ValueError, Review, self.comp1, "Title", 'SWE', "Position", 4, "M.S.", "Review", 4) + # category is empty self.assertRaises(ValueError, Review, self.comp1, "Title", '', "Position", 4, "M.S.", "Review", 4) + # rating more or less than the max or min allowed self.assertRaises(ValueError, Review, self.comp1, "Title", 'Software Engineering', "Position", 10, "M.S.", "Review", 4) self.assertRaises(ValueError, Review, self.comp1, "Title", 'Software Engineering', "Position", -1, "M.S.", "Review", 4) + # invalid education self.assertRaises(ValueError, Review, self.comp2, "Title", 'Software Engineering', "Position", 4, "", "Review", 4) self.assertRaises(ValueError, Review, self.comp2, "Title", 'Software Engineering', "Position", 4, "A degree", "Review", 4) + # invalid description self.assertRaises(ValueError, Review, self.comp2, "Title", 'Software Engineering', "Position", 4, "M.S.", "", 4) + # rating is invalid self.assertRaises(ValueError, Review, self.comp2, "Title", 'Software Engineering', "Position", 4, "M.S.", "Review", -3) @@ -90,54 +107,63 @@ def test_init_required(self): def test_init_optional(self): 'Type Errors' + # offer not a bool self.assertRaises(TypeError, Review, self.comp1, "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4, offer=0, accepted=True, start_date="05-23-2022", intern_desc="desc", work_rat=4, culture_rat=4, location=("San Francisco", "California"), pay=35.25, bonuses="Bonus") + # accepted not a bool self.assertRaises(TypeError, Review, self.comp1, "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4, offer=True, accepted=None, start_date="05-23-2022", intern_desc="desc", work_rat=4, culture_rat=4, location=("San Francisco", "California"), pay=35.25, bonuses="Bonus") + # start date of the wrong type self.assertRaises(TypeError, Review, self.comp1, "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4, offer=True, accepted=True, start_date=["05", "23", "2022"], intern_desc="desc", work_rat=4, culture_rat=4, location=("San Francisco", "California"), pay=35.25, bonuses="Bonus") + # intern desc is None self.assertRaises(TypeError, Review, self.comp1, "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4, offer=True, accepted=True, start_date="05-23-2022", intern_desc=None, work_rat=4, culture_rat=4, location=("San Francisco", "California"), pay=35.25, bonuses="Bonus") + # work rating of invalid type self.assertRaises(TypeError, Review, self.comp1, "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4, offer=True, accepted=True, start_date="05-23-2022", intern_desc="desc", work_rat="10", culture_rat=4, location=("San Francisco", "California"), pay=35.25, bonuses="Bonus") + # culture rating of invalid type self.assertRaises(TypeError, Review, self.comp1, "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4, offer=True, accepted=True, start_date="05-23-2022", intern_desc="desc", work_rat=4, culture_rat=hex(20), location=("San Francisco", "California"), pay=35.25, bonuses="Bonus") + # location not a tuple self.assertRaises(TypeError, Review, self.comp1, "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4, offer=True, accepted=True, start_date="05-23-2022", intern_desc="desc", work_rat=4, culture_rat=4, location=["San Francisco", "California"], pay=35.25, bonuses="Bonus") + # pay is a string self.assertRaises(TypeError, Review, self.comp1, "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4, offer=True, accepted=True, start_date="05-23-2022", intern_desc="desc", work_rat=4, culture_rat=4, location=("San Francisco", "California"), pay="23.64", bonuses="Bonus") + # bonuses is an int self.assertRaises(TypeError, Review, self.comp1, "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4, offer=True, accepted=True, start_date="05-23-2022", intern_desc="desc", work_rat=4, @@ -145,6 +171,7 @@ def test_init_optional(self): pay=35.25, bonuses=32.40) + # state or city are of invalid types self.assertRaises(TypeError, Review, self.comp2, "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4, offer=True, accepted=True, start_date="05-23-2022", intern_desc="desc", work_rat=4, @@ -164,6 +191,7 @@ def test_init_optional(self): pay=35.25, bonuses="Bonus") 'Value Errors' + # invalid date format self.assertRaises(ValueError, Review, self.comp2, "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4, offer=True, accepted=True, start_date="23-05-2022", intern_desc="desc", work_rat=4, @@ -176,12 +204,14 @@ def test_init_optional(self): culture_rat=4, location=("San Francisco", "California"), pay=35.25, bonuses="Bonus") + # accepted an offer that wasn't given (offer = False) self.assertRaises(ValueError, Review, self.comp2, "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4, offer=False, accepted=True, start_date="05-22-2023", intern_desc="desc", work_rat=4, culture_rat=4, location=("San Francisco", "California"), pay=35.25, bonuses="Bonus") + # invalid artings self.assertRaises(ValueError, Review, self.comp2, "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4, offer=True, accepted=True, start_date="05-23-2022", intern_desc="desc", work_rat=-4, @@ -200,18 +230,21 @@ def test_init_optional(self): culture_rat=-4, location=("San Francisco", "California"), pay=35.25, bonuses="Bonus") + # invalid state self.assertRaises(ValueError, Review, self.comp2, "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4, offer=True, accepted=True, start_date="05-23-2022", intern_desc="desc", work_rat=4, culture_rat=4, location=("San Francisco", "Some State"), pay=35.25, bonuses="Bonus") + # invalid pay self.assertRaises(ValueError, Review, self.comp2, "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4, offer=True, accepted=True, start_date="05-23-2022", intern_desc="desc", work_rat=4, culture_rat=4, location=("San Francisco", "California"), pay=-35.25, bonuses="Bonus") + # invalid city (not alphabetical) self.assertRaises(ValueError, Review, self.comp2, "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4, offer=True, accepted=True, start_date="05-23-2022", intern_desc="desc", work_rat=4, From a20730b7ad352cd653943768992ac67da9a80ca1 Mon Sep 17 00:00:00 2001 From: XV1R Date: Tue, 12 Apr 2022 21:05:03 -0400 Subject: [PATCH 02/13] user class base attributes along with small helper methods; written tests for existing user class --- model.py | 49 ++++++++++++++++++++++++++++++++++++- tests/test_user.py | 60 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 1 deletion(-) diff --git a/model.py b/model.py index 6b7eb0f..0459624 100644 --- a/model.py +++ b/model.py @@ -1,4 +1,6 @@ from datetime import datetime +import re +import bcrypt company_categories = ["Software", "Hardware", "Computing", "Finance", "Government", "Defense", "Aerospace", "Restaurant", "Automobiles", "Aviation", "Retail", "Other"] @@ -100,6 +102,12 @@ def validate_date(date:str)->None: except ValueError: raise ValueError("Incorrect date format. Should be MM-DD-YYYY") +def validate_email(email:str): + regex = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b' + if re.fullmatch(regex,email): + return True + return False + class Company: def __init__(self, name:str, category:str, logo_img:str, description:str="", banner_img:str="")->None: #type checks @@ -153,7 +161,46 @@ def __init__(self, name:str, category:str, logo_img:str, description:str="", ban class User: def __init__(self, username:str, email:str, pswd:str, profile_pic:str="")->None: - pass + #type checks + if type(username) is not str: + raise TypeError(f"Error: username must a string. Given type '{type(username)}'.") + if type(email) is not str: + raise TypeError(f"Error: email must a string. Given type '{type(email)}'.") + if type(pswd) is not str: + raise TypeError(f"Error: password must a string. Given type '{type(pswd)}'.") + if type(profile_pic) is not str: + raise TypeError(f"Error: profile_pic must a string. Given type '{type(profile_pic)}'.") + + #value checks + if len(username) <= 3: + raise ValueError("Error: usernames must have more than 3 characters.") + if not validate_email(email): + raise ValueError("Error: Invalid email") + if len(pswd) < 8: + raise ValueError("Error: Password must be 6 or more characters.") + if not is_url(profile_pic): + raise ValueError("Error: Profile picture URL not valid!") + + + self.username = username + self.email = email + self.password = self.generate_password(pswd) + self.profile_pic = profile_pic + + def generate_password(self,unhashed:str): + salt = bcrypt.gensalt() + hashed = bcrypt.hashpw(unhashed.encode("utf-8"),salt) + return hashed + + def set_profile_pic(self,link:str) -> bool: + if type(link) is not str: + raise TypeError("Error: Link to profile picture must be a string.") + if not is_url(link): + return False + self.profile_pic = link + return True + + class Review: """Represents a review posted to a specific company. diff --git a/tests/test_user.py b/tests/test_user.py index e69de29..35f3058 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -0,0 +1,60 @@ +from model import User +import unittest + +class TestUser(unittest.TestCase): + def setUp(self): + self.valid_email1 = "example@gmail.com" + self.valid_username = "xavier" + self.valid_pic = "http://www.test.com" + self.valid_password = "password1" + + self.user = User( + self.valid_username, + self.valid_email1, + self.valid_password, + self.valid_pic) + + def test_types(self): + #username tests + self.assertRaises(TypeError,User,None,self.valid_email1,"password1",self.valid_pic) + self.assertRaises(TypeError,User,123,self.valid_email1,"password1",self.valid_pic) + self.assertRaises(TypeError,User,[],self.valid_email1,"password1",self.valid_pic) + #email tests + self.assertRaises(TypeError,User,self.valid_username,[],"password1",self.valid_pic) + self.assertRaises(TypeError,User,self.valid_username,None,"password1",self.valid_pic) + self.assertRaises(TypeError,User,self.valid_username,123,"password1",self.valid_pic) + #password tests + self.assertRaises(TypeError,User,self.valid_username,self.valid_email1,None,self.valid_pic) + self.assertRaises(TypeError,User,self.valid_username,self.valid_email1,[],self.valid_pic) + self.assertRaises(TypeError,User,self.valid_username,self.valid_email1,123,self.valid_pic) + #url tests + self.assertRaises(TypeError,User,self.valid_username,self.valid_email1,"password1",None) + self.assertRaises(TypeError,User,self.valid_username,self.valid_email1,"password1",[]) + self.assertRaises(TypeError,User,self.valid_username,self.valid_email1,"password1",123) + + def test_values(self): + #username tests + self.assertRaises(ValueError,User,"tes",self.valid_email1,"password1",self.valid_email1) + self.assertRaises(ValueError,User,"",self.valid_email1,"password1",self.valid_email1) + self.assertRaises(ValueError,User,"tes",self.valid_email1,"password1",self.valid_email1) + #email tests + self.assertRaises(ValueError,User,self.valid_username,"inv@test","password1",self.valid_email1) + self.assertRaises(ValueError,User,self.valid_username,"email.com","password1",self.valid_email1) + self.assertRaises(ValueError,User,self.valid_username,"@test.com","password1",self.valid_email1) + self.assertRaises(ValueError,User,self.valid_username,"inv@@@","password1",self.valid_email1) + #password tests + self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,"",self.valid_pic) + self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,"1",self.valid_pic) + self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,"apple",self.valid_pic) + self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,"test12",self.valid_pic) + #url tests + self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,"password1","test.com") + self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,"password1","") + self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,"password1","@") + self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,"password1","@gmail.com") + self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,"password1","user@") + + def test_set_profile_pic(self): + self.assertTrue(self.user.set_profile_pic("https://bit.ly/3KF5IT0")) + self.assertFalse(self.user.set_profile_pic("bit.ly/3KF5IT0")) + self.assertEqual(self.user.profile_pic,"https://bit.ly/3KF5IT0") \ No newline at end of file From a7b88433cac5a8eb94604a1f5e82d6d204763fbb Mon Sep 17 00:00:00 2001 From: XV1R Date: Tue, 12 Apr 2022 23:16:46 -0400 Subject: [PATCH 03/13] some extra tests and changed url validation to regex --- model.py | 42 ++++++++++++++++++++++++++------ tests/test_user.py | 60 +++++++++++++++++++++++++++------------------- 2 files changed, 71 insertions(+), 31 deletions(-) diff --git a/model.py b/model.py index 0459624..5e62585 100644 --- a/model.py +++ b/model.py @@ -1,4 +1,4 @@ -from datetime import datetime +from datetime import datetime,timezone import re import bcrypt @@ -75,13 +75,33 @@ def is_url(url:str)->bool: url (str): The url to validate Returns: bool indicating validity - """ - from urllib.parse import urlparse - try: - result = urlparse(url) - return all([result.scheme, result.netloc]) - except: + # """ + regex = ("((http|https)://)(www.)?" + + "[a-zA-Z0-9@:%._\\+~#?&//=]" + + "{2,256}\\.[a-z]" + + "{2,6}\\b([-a-zA-Z0-9@:%" + + "._\\+~#?&//=]*)") + + # Compile the ReGex + p = re.compile(regex) + + # If the string is empty + # return false + if (url is None): + return False + + # Return if the string + # matched the ReGex + if(re.search(p, url)): + return True + else: return False + # from urllib.parse import urlparse + # try: + # result = urlparse(url) + # return all([result.scheme, result.netloc]) + # except: + # return False def validate_date(date:str)->None: """Validates a given date. Raises @@ -186,6 +206,14 @@ def __init__(self, username:str, email:str, pswd:str, profile_pic:str="")->None: self.email = email self.password = self.generate_password(pswd) self.profile_pic = profile_pic + self.creation_time = self.set_creation_time() + + + def set_creation_time(self): + utc_now = datetime.now(timezone.utc) #gets the current time in utc + local_time_obj = utc_now.astimezone() #gets the specific utc timezone (from the computer system) + time_string = local_time_obj.strftime("%m-%d-%Y at %I:%M %p") #get current/local time as 12-hour string + return time_string def generate_password(self,unhashed:str): salt = bcrypt.gensalt() diff --git a/tests/test_user.py b/tests/test_user.py index 35f3058..30d86e3 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -1,4 +1,4 @@ -from model import User +from model import User import unittest class TestUser(unittest.TestCase): @@ -13,48 +13,60 @@ def setUp(self): self.valid_email1, self.valid_password, self.valid_pic) + def test_types(self): #username tests - self.assertRaises(TypeError,User,None,self.valid_email1,"password1",self.valid_pic) - self.assertRaises(TypeError,User,123,self.valid_email1,"password1",self.valid_pic) - self.assertRaises(TypeError,User,[],self.valid_email1,"password1",self.valid_pic) + self.assertRaises(TypeError,User,None,self.valid_email1,self.valid_password,self.valid_pic) + self.assertRaises(TypeError,User,123,self.valid_email1,self.valid_password,self.valid_pic) + self.assertRaises(TypeError,User,[],self.valid_email1,self.valid_password,self.valid_pic) #email tests - self.assertRaises(TypeError,User,self.valid_username,[],"password1",self.valid_pic) - self.assertRaises(TypeError,User,self.valid_username,None,"password1",self.valid_pic) - self.assertRaises(TypeError,User,self.valid_username,123,"password1",self.valid_pic) + self.assertRaises(TypeError,User,self.valid_username,[],self.valid_password,self.valid_pic) + self.assertRaises(TypeError,User,self.valid_username,None,self.valid_password,self.valid_pic) + self.assertRaises(TypeError,User,self.valid_username,123,self.valid_password,self.valid_pic) #password tests self.assertRaises(TypeError,User,self.valid_username,self.valid_email1,None,self.valid_pic) self.assertRaises(TypeError,User,self.valid_username,self.valid_email1,[],self.valid_pic) self.assertRaises(TypeError,User,self.valid_username,self.valid_email1,123,self.valid_pic) #url tests - self.assertRaises(TypeError,User,self.valid_username,self.valid_email1,"password1",None) - self.assertRaises(TypeError,User,self.valid_username,self.valid_email1,"password1",[]) - self.assertRaises(TypeError,User,self.valid_username,self.valid_email1,"password1",123) + self.assertRaises(TypeError,User,self.valid_username,self.valid_email1,self.valid_password,None) + self.assertRaises(TypeError,User,self.valid_username,self.valid_email1,self.valid_password,[]) + self.assertRaises(TypeError,User,self.valid_username,self.valid_email1,self.valid_password,123) def test_values(self): #username tests - self.assertRaises(ValueError,User,"tes",self.valid_email1,"password1",self.valid_email1) - self.assertRaises(ValueError,User,"",self.valid_email1,"password1",self.valid_email1) - self.assertRaises(ValueError,User,"tes",self.valid_email1,"password1",self.valid_email1) + self.assertRaises(ValueError,User,"tes",self.valid_email1,self.valid_password,self.valid_email1) + self.assertRaises(ValueError,User,"",self.valid_email1,self.valid_password,self.valid_email1) + self.assertRaises(ValueError,User,"tes",self.valid_email1,self.valid_password,self.valid_email1) + self.assertRaises(ValueError,User,"1",self.valid_email1,self.valid_password,self.valid_email1) + self.assertRaises(ValueError,User,"__",self.valid_email1,self.valid_password,self.valid_email1) #email tests - self.assertRaises(ValueError,User,self.valid_username,"inv@test","password1",self.valid_email1) - self.assertRaises(ValueError,User,self.valid_username,"email.com","password1",self.valid_email1) - self.assertRaises(ValueError,User,self.valid_username,"@test.com","password1",self.valid_email1) - self.assertRaises(ValueError,User,self.valid_username,"inv@@@","password1",self.valid_email1) + self.assertRaises(ValueError,User,self.valid_username,"inv@test",self.valid_password,self.valid_email1) + self.assertRaises(ValueError,User,self.valid_username,"email.com",self.valid_password,self.valid_email1) + self.assertRaises(ValueError,User,self.valid_username,"@test.com",self.valid_password,self.valid_email1) + self.assertRaises(ValueError,User,self.valid_username,"inv@@@",self.valid_password,self.valid_email1) + self.assertRaises(ValueError,User,self.valid_username,"t@",self.valid_password,self.valid_email1) + self.assertRaises(ValueError,User,self.valid_username,"@.com",self.valid_password,self.valid_email1) #password tests self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,"",self.valid_pic) self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,"1",self.valid_pic) self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,"apple",self.valid_pic) self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,"test12",self.valid_pic) #url tests - self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,"password1","test.com") - self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,"password1","") - self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,"password1","@") - self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,"password1","@gmail.com") - self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,"password1","user@") - + self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,self.valid_password,"test.com") + self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,self.valid_password,"") + self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,self.valid_password,"@") + self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,self.valid_password,"@gmail.com") + self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,self.valid_password,"user@") + self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,self.valid_password,"https://") + self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,self.valid_password,"http//www.google.com") def test_set_profile_pic(self): self.assertTrue(self.user.set_profile_pic("https://bit.ly/3KF5IT0")) self.assertFalse(self.user.set_profile_pic("bit.ly/3KF5IT0")) - self.assertEqual(self.user.profile_pic,"https://bit.ly/3KF5IT0") \ No newline at end of file + self.assertEqual(self.user.profile_pic,"https://bit.ly/3KF5IT0") + self.assertTrue(self.user.set_profile_pic("https://www.google.com")) + self.assertEqual(self.user.profile_pic, "https://www.google.com") + # self.assertTrue(self.user.set_profile_pic("https://www.com")) + + def test_account_creation_time(self): + self.assertIsNotNone(self.user.creation_time) \ No newline at end of file From 8e0e9639cd5ed5ea214138260c253c6c61bf1272 Mon Sep 17 00:00:00 2001 From: XV1R Date: Wed, 13 Apr 2022 00:30:56 -0400 Subject: [PATCH 04/13] some extra tests for Company class --- model.py | 3 +++ tests/test_company.py | 41 ++++++++++++++++++++++++++++++++--------- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/model.py b/model.py index 5e62585..3beba07 100644 --- a/model.py +++ b/model.py @@ -148,6 +148,9 @@ def __init__(self, name:str, category:str, logo_img:str, description:str="", ban if len(category) == 0: raise ValueError("Error: Company category cannot be empty.") + + if category not in company_categories: + raise ValueError("Error: Invalid Job category.") if not is_url(logo_img): raise ValueError("Error: Invalid URL given for company logo.") diff --git a/tests/test_company.py b/tests/test_company.py index c5e8b10..80c90d1 100644 --- a/tests/test_company.py +++ b/tests/test_company.py @@ -4,34 +4,57 @@ class TestCompany(unittest.TestCase): def setUp(self): self.valid_link = "http://somelink.com" #used for ease of writing tests + self.valid_category = "Software" self.company1 = Company( name="Microsoft", - category="software", + category=self.valid_category, logo_img="https://bit.ly/3uWfYzK", banner_img="https://bit.ly/3xfolJs" ) self.company2 = Company( name="Google", - category="Software", + category=self.valid_category, logo_img="http://somelink.com", banner_img="http://somelink.com" ) def test_types(self): - self.assertRaises(TypeError, Company, None, "software", self.valid_link,self.valid_link) + self.assertRaises(TypeError, Company, None, self.valid_category, self.valid_link,self.valid_link) self.assertRaises(TypeError, Company, "Netflix", 2, self.valid_link, self.valid_link) - self.assertRaises(TypeError, Company, "Home Depot", "software", [], self.valid_link) - self.assertRaises(TypeError, Company, "Netflix", "software", self.valid_link, None) - self.assertRaises(TypeError, Company, "Netflix", "software", self.valid_link, self.valid_link, + self.assertRaises(TypeError, Company, "Home Depot", self.valid_category, [], self.valid_link) + self.assertRaises(TypeError, Company, "Netflix", self.valid_category, self.valid_link, None) + self.assertRaises(TypeError, Company, "Netflix", self.valid_category, self.valid_link, self.valid_link, description=333) def test_values(self): - self.assertRaises(ValueError, Company, "", "software", self.valid_link, self.valid_link) + #name test + #it's kind of hard to determine what's a valid company name + self.assertRaises(ValueError, Company, "", self.valid_category, self.valid_link, self.valid_link) + #category test self.assertRaises(ValueError, Company, "Netfix", "", self.valid_link, self.valid_link) - self.assertRaises(ValueError, Company, "Netflix", "software", "htp/broken_link", self.valid_link) - self.assertRaises(ValueError, Company, "Netflix", "software", self.valid_link, "desc", "link") + self.assertRaises(ValueError, Company, "Netfix", "invalid", self.valid_link, self.valid_link) + self.assertRaises(ValueError, Company, "Netfix", "softwre", self.valid_link, self.valid_link) + self.assertRaises(ValueError, Company, "Netfix", "111", self.valid_link, self.valid_link) + #logo_img test + self.assertRaises(ValueError, Company, "Netflix", self.valid_category, "htp/broken_link", self.valid_link) + self.assertRaises(ValueError, Company, "Netflix", self.valid_category, "http://", self.valid_link) + self.assertRaises(ValueError, Company, "Netflix", self.valid_category, "htp/www.broken.com", self.valid_link) + self.assertRaises(ValueError, Company, "Netflix", self.valid_category, "broken.com", self.valid_link) + #banner_img test + self.assertRaises(ValueError, Company, "Netflix", self.valid_category, self.valid_link, banner_img="brkn") + self.assertRaises(ValueError, Company, "Netflix", self.valid_category, self.valid_link, banner_img="http://www.") + self.assertRaises(ValueError, Company, "Netflix", self.valid_category, self.valid_link, banner_img="http://") + self.assertRaises(ValueError, Company, "Netflix", self.valid_category, self.valid_link, banner_img="broken.com") + self.assertRaises(ValueError, Company, "Netflix", self.valid_category, self.valid_link, banner_img="http://invalid") + def test_attributes(self): + self.assertIsNone(self.company1.culture_rat) + self.assertIsNone(self.company1.work_rat) + self.assertIsNone(self.company1.company_rat) + self.assertEqual(self.company1.num_company_reviews,0) + self.assertEqual(self.company1.num_work_reviews,0) + self.assertEqual(self.company1.num_culture_reviews ,0) if __name__ == "__main__": unittest.main(failFast=True) \ No newline at end of file From 18ebdacaa5e7fdb23ee632676e5cd6159c742f11 Mon Sep 17 00:00:00 2001 From: XV1R Date: Wed, 13 Apr 2022 00:35:30 -0400 Subject: [PATCH 05/13] fixed placeholder company in app --- app.py | 2 +- model.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app.py b/app.py index c908969..b8b80e5 100644 --- a/app.py +++ b/app.py @@ -12,7 +12,7 @@ def index(): company1 = model.Company( name="Microsoft", - category="software", + category="Software", logo_img="https://bit.ly/3uWfYzK", banner_img="https://bit.ly/3xfolJs" ) diff --git a/model.py b/model.py index 3beba07..83d7b01 100644 --- a/model.py +++ b/model.py @@ -150,7 +150,7 @@ def __init__(self, name:str, category:str, logo_img:str, description:str="", ban raise ValueError("Error: Company category cannot be empty.") if category not in company_categories: - raise ValueError("Error: Invalid Job category.") + raise ValueError("Error: Invalid company category.") if not is_url(logo_img): raise ValueError("Error: Invalid URL given for company logo.") From 04c9f4b05abc33e34268745fa3122d0813811cc8 Mon Sep 17 00:00:00 2001 From: Mercrist Date: Wed, 13 Apr 2022 10:08:02 -0400 Subject: [PATCH 06/13] User and company changes --- model.py | 80 +++++++++++++++++++----------- tests/test_company.py | 38 ++++++++++++--- tests/test_reviews.py | 111 ++++++++++++++++++++++++------------------ tests/test_user.py | 18 ++++--- 4 files changed, 156 insertions(+), 91 deletions(-) diff --git a/model.py b/model.py index 83d7b01..eaaf1bc 100644 --- a/model.py +++ b/model.py @@ -1,6 +1,6 @@ -from datetime import datetime,timezone -import re +from datetime import datetime import bcrypt +import re company_categories = ["Software", "Hardware", "Computing", "Finance", "Government", "Defense", "Aerospace", "Restaurant", "Automobiles", "Aviation", "Retail", "Other"] @@ -81,15 +81,15 @@ def is_url(url:str)->bool: "{2,256}\\.[a-z]" + "{2,6}\\b([-a-zA-Z0-9@:%" + "._\\+~#?&//=]*)") - + # Compile the ReGex p = re.compile(regex) - + # If the string is empty # return false if (url is None): return False - + # Return if the string # matched the ReGex if(re.search(p, url)): @@ -125,7 +125,7 @@ def validate_date(date:str)->None: def validate_email(email:str): regex = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b' if re.fullmatch(regex,email): - return True + return True return False class Company: @@ -148,7 +148,7 @@ def __init__(self, name:str, category:str, logo_img:str, description:str="", ban if len(category) == 0: raise ValueError("Error: Company category cannot be empty.") - + if category not in company_categories: raise ValueError("Error: Invalid company category.") @@ -181,6 +181,29 @@ def __init__(self, name:str, category:str, logo_img:str, description:str="", ban # the previous entries dont count # as individual reviews self.total_reviews = 0 + # a company will have a color border + # around them depending on their rating. + # the status must be a supported CSS + # color keyword + self.status = "grey" + + def update_status(self)->None: + if self.company_rat is not None: + if self.company_rat == 5: + self.status = "green" + + elif self.company_rat >= 4 and self.company_rat < 5: + self.status = "lightgreen" + + elif self.company_rat >= 3 and self.company_rat < 4: + self.status = "orange" + + elif self.company_rat >= 2 and self.company_rat < 3: + self.status = "red" + + # rating of 0-1.99 + else: + self.status = "black" class User: def __init__(self, username:str, email:str, pswd:str, profile_pic:str="")->None: @@ -191,47 +214,40 @@ def __init__(self, username:str, email:str, pswd:str, profile_pic:str="")->None: raise TypeError(f"Error: email must a string. Given type '{type(email)}'.") if type(pswd) is not str: raise TypeError(f"Error: password must a string. Given type '{type(pswd)}'.") - if type(profile_pic) is not str: + if type(profile_pic) is not str: raise TypeError(f"Error: profile_pic must a string. Given type '{type(profile_pic)}'.") - #value checks + #value checks if len(username) <= 3: raise ValueError("Error: usernames must have more than 3 characters.") if not validate_email(email): raise ValueError("Error: Invalid email") - if len(pswd) < 8: - raise ValueError("Error: Password must be 6 or more characters.") - if not is_url(profile_pic): + if len(pswd) < 5: + raise ValueError("Error: Password must be 5 or more characters.") + if profile_pic != "" and not is_url(profile_pic): raise ValueError("Error: Profile picture URL not valid!") - + self.username = username self.email = email self.password = self.generate_password(pswd) self.profile_pic = profile_pic - self.creation_time = self.set_creation_time() - - - def set_creation_time(self): - utc_now = datetime.now(timezone.utc) #gets the current time in utc - local_time_obj = utc_now.astimezone() #gets the specific utc timezone (from the computer system) - time_string = local_time_obj.strftime("%m-%d-%Y at %I:%M %p") #get current/local time as 12-hour string - return time_string + self.creation_time = datetime.now() def generate_password(self,unhashed:str): salt = bcrypt.gensalt() - hashed = bcrypt.hashpw(unhashed.encode("utf-8"),salt) + hashed = bcrypt.hashpw(unhashed.encode("utf-8"), salt) return hashed - - def set_profile_pic(self,link:str) -> bool: + + def set_profile_pic(self,link:str)->bool: if type(link) is not str: raise TypeError("Error: Link to profile picture must be a string.") + if not is_url(link): return False - self.profile_pic = link - return True - + self.profile_pic = link + return True class Review: """Represents a review posted to a specific company. @@ -241,6 +257,7 @@ class Review: based on what the user rated. Attributes: + user: A string indicating which user account created the review. company: The Company object of the company to be rated by the user. job_cat: The general job category the internship falls under, as a string. position: A string representing the position the user had at the company. @@ -259,7 +276,7 @@ class Review: bonuses: A string of any extra bonuses the company might offer at the internship. date_posted: A datetime object, representing the date the internship was posted. """ - def __init__(self, company:'Company', title:str, job_cat:str, position:str, company_rating:int, education:str, + def __init__(self, user:str, company:'Company', title:str, job_cat:str, position:str, company_rating:int, education:str, interview_desc:str, interview_rat:int, offer:bool=False, accepted:bool=False, start_date:str="", intern_desc:str="", work_rat:int=None, culture_rat:int=None, location:tuple=("None", "Remote"), pay:float=None, bonuses:str="")->None: @@ -268,6 +285,7 @@ def __init__(self, company:'Company', title:str, job_cat:str, position:str, comp the fields the user decides to fill out. Args: + user(str): The username of the account that created the review. company (Company): The company to rate. job_cat (str): The general job category the internship falls under. position (str): The position held at the company. @@ -291,6 +309,11 @@ def __init__(self, company:'Company', title:str, job_cat:str, position:str, comp ValueError: Raised if the arguments are of the correct types but aren't supported by the website. """ + if type(user) != str: + raise TypeError("Username that created the review must be a string!") + + if not user or len(user) <= 3: + raise ValueError("Invalid username!") if type(company) != Company: raise TypeError("Company to review must be a company object!") @@ -407,6 +430,7 @@ def __init__(self, company:'Company', title:str, job_cat:str, position:str, comp if type(bonuses) != str: raise TypeError("Any additional info on bonuses must be a string!") + self.user = user self.company = company self.job_cat = job_cat self.position = position diff --git a/tests/test_company.py b/tests/test_company.py index 80c90d1..b81ef24 100644 --- a/tests/test_company.py +++ b/tests/test_company.py @@ -48,13 +48,37 @@ def test_values(self): self.assertRaises(ValueError, Company, "Netflix", self.valid_category, self.valid_link, banner_img="broken.com") self.assertRaises(ValueError, Company, "Netflix", self.valid_category, self.valid_link, banner_img="http://invalid") - def test_attributes(self): - self.assertIsNone(self.company1.culture_rat) - self.assertIsNone(self.company1.work_rat) - self.assertIsNone(self.company1.company_rat) - self.assertEqual(self.company1.num_company_reviews,0) - self.assertEqual(self.company1.num_work_reviews,0) - self.assertEqual(self.company1.num_culture_reviews ,0) + def test_equality(self): + # default status color + self.assertEqual(self.company1.status, "grey") + # perfect score + self.company1.company_rat = 5 + self.company1.update_status() + self.assertEqual(self.company1.status, "green") + # should be lightgreen + self.company1.company_rat = 4.99 + self.company1.update_status() + self.assertEqual(self.company1.status, "lightgreen") + # just at the edge of 4 + self.company1.company_rat = 3.9 + self.company1.update_status() + self.assertEqual(self.company1.status, "orange") + # at the edge of 3 + self.company1.company_rat = 2.99 + self.company1.update_status() + self.assertEqual(self.company1.status, "red") + # exact value + self.company1.company_rat = 2 + self.company1.update_status() + self.assertEqual(self.company1.status, "red") + # any score less than 2 + self.company1.company_rat = 1.99 + self.company1.update_status() + self.assertEqual(self.company1.status, "black") + # score of 0 + self.company1.company_rat = 0 + self.company1.update_status() + self.assertEqual(self.company1.status, "black") if __name__ == "__main__": unittest.main(failFast=True) \ No newline at end of file diff --git a/tests/test_reviews.py b/tests/test_reviews.py index f2152b5..e521466 100644 --- a/tests/test_reviews.py +++ b/tests/test_reviews.py @@ -1,12 +1,13 @@ -from model import Company, Review +from model import Company, Review, User import unittest class TestReviews(unittest.TestCase): def setUp(self): self.comp1 = Company("Amazon", "Software", "http://somelink.com") self.comp2 = Company("McDonald's", "Restaurant", "http://somelink.com") + self.user = User("Test", "somemail@gmail.com", "123323456") - self.review1 = Review(self.comp1, "Title", 'Software Engineering', + self.review1 = Review('User', self.comp1, "Title", 'Software Engineering', "Position", company_rating=4, education="M.S.", interview_desc="Interview", interview_rat=4, offer=True, accepted=True, start_date="05-23-2022", intern_desc="desc", work_rat=None, culture_rat=None, location=("San Francisco", "California"), @@ -15,156 +16,170 @@ def setUp(self): def test_init_required(self): 'Type Errors' + #invalid user + self.assertRaises(TypeError, Review, self.user, self.comp1, "Title", 'Software Engineering', + "Position", 4, "M.S.", "Interview", 4) + + self.assertRaises(TypeError, Review, ["User"], self.comp1, "Title", 'Software Engineering', + "Position", 4, "M.S.", "Interview", 4) + # not a company object - self.assertRaises(TypeError, Review, 'Boeing', "Title", 'Software Engineering', + self.assertRaises(TypeError, Review, 'User', 'Boeing', "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4) # title is of the wrong type - self.assertRaises(TypeError, Review, self.comp1, {"Title"}, 'Software Engineering', + self.assertRaises(TypeError, Review, 'User', self.comp1, {"Title"}, 'Software Engineering', "Position", 4, "M.S.", "Interview", 4) # Position is None - self.assertRaises(TypeError, Review, self.comp1, "Title", + self.assertRaises(TypeError, Review, 'User', self.comp1, "Title", 'Software Engineering', None, 4, "M.S.", "Interview", 4) # job is of the wrong type - self.assertRaises(TypeError, Review, self.comp1, "Title", ['Software Engineering'], + self.assertRaises(TypeError, Review, 'User', self.comp1, "Title", ['Software Engineering'], "Position", 4, "M.S.", "Interview", 4) # position is of the wrong type - self.assertRaises(TypeError, Review, self.comp1, "Title", 'Software Engineering', + self.assertRaises(TypeError, Review, 'User', self.comp1, "Title", 'Software Engineering', ["Position"], 4, "M.S.", "Interview", 4) # score is of the wrong type - self.assertRaises(TypeError, Review, self.comp1, "Title", 'Software Engineering', + self.assertRaises(TypeError, Review, 'User', self.comp1, "Title", 'Software Engineering', "Position", hex(4), "M.S.", "Interview", 4) # score cant be a float - self.assertRaises(TypeError, Review, self.comp1, "Title", 'Software Engineering', + self.assertRaises(TypeError, Review, 'User', self.comp1, "Title", 'Software Engineering', "Position", 3.90, "M.S.", "Interview", 4) - self.assertRaises(TypeError, Review, self.comp1, "Title", 'Software Engineering', + self.assertRaises(TypeError, Review, 'User', self.comp1, "Title", 'Software Engineering', "Position", 0.30, "M.S.", "Interview", 4) # education is of the wrong type - self.assertRaises(TypeError, Review, self.comp2, "Title", 'Software Engineering', + self.assertRaises(TypeError, Review, 'User', self.comp2, "Title", 'Software Engineering', "Position", 4, ord("M"), "Interview", 4) # description is of the wrong time - self.assertRaises(TypeError, Review, self.comp2, "Title", 'Software Engineering', + self.assertRaises(TypeError, Review, 'User', self.comp2, "Title", 'Software Engineering', "Position", 4, "M.S.", ["Review"], 4) # score is of the wrong type - self.assertRaises(TypeError, Review, self.comp2, "Title", 'Software Engineering', + self.assertRaises(TypeError, Review, 'User', self.comp2, "Title", 'Software Engineering', "Position", 4, "M.S.", "Review", hex(4)) - self.assertRaises(TypeError, Review, self.comp2, "Title", 'Software Engineering', + self.assertRaises(TypeError, Review, 'User', self.comp2, "Title", 'Software Engineering', "Position", 4, "M.S.", "Review", 4.90) - self.assertRaises(TypeError, Review, self.comp2, "Title", 'Software Engineering', + self.assertRaises(TypeError, Review, 'User', self.comp2, "Title", 'Software Engineering', "Position", 4, "M.S.", "Review", 3.50) - self.assertRaises(TypeError, Review, self.comp2, "Title", 'Software Engineering', + self.assertRaises(TypeError, Review, 'User', self.comp2, "Title", 'Software Engineering', "Position", 4, "M.S.", "Review", float(2)) 'Value Errors' + # user is empty or not the right length + self.assertRaises(ValueError, Review, "", self.comp1, "Title", 'Software Engineering', + "Position", 4, "M.S.", "Interview", 4) + + self.assertRaises(ValueError, Review, "chr", self.comp1, "Title", 'Software Engineering', + "Position", 4, "M.S.", "Interview", 4) + # title is empty - self.assertRaises(ValueError, Review, self.comp1, "", 'Software Engineering', + self.assertRaises(ValueError, Review, 'User', self.comp1, "", 'Software Engineering', "Position", 4, "M.S.", "Review", 4) # position is empty - self.assertRaises(ValueError, Review, self.comp1, "Title", 'Software Engineering', + self.assertRaises(ValueError, Review, 'User', self.comp1, "Title", 'Software Engineering', "", 4, "M.S.", "Review", 4) # SWE is not a job category - self.assertRaises(ValueError, Review, self.comp1, "Title", 'SWE', "Position", 4, "M.S.", "Review", 4) + self.assertRaises(ValueError, Review, 'User', self.comp1, "Title", 'SWE', "Position", 4, "M.S.", "Review", 4) # category is empty - self.assertRaises(ValueError, Review, self.comp1, "Title", '', "Position", 4, "M.S.", "Review", 4) + self.assertRaises(ValueError, Review, 'User', self.comp1, "Title", '', "Position", 4, "M.S.", "Review", 4) # rating more or less than the max or min allowed - self.assertRaises(ValueError, Review, self.comp1, "Title", 'Software Engineering', + self.assertRaises(ValueError, Review, 'User', self.comp1, "Title", 'Software Engineering', "Position", 10, "M.S.", "Review", 4) - self.assertRaises(ValueError, Review, self.comp1, "Title", 'Software Engineering', + self.assertRaises(ValueError, Review, 'User', self.comp1, "Title", 'Software Engineering', "Position", -1, "M.S.", "Review", 4) # invalid education - self.assertRaises(ValueError, Review, self.comp2, "Title", 'Software Engineering', + self.assertRaises(ValueError, Review, 'User', self.comp2, "Title", 'Software Engineering', "Position", 4, "", "Review", 4) - self.assertRaises(ValueError, Review, self.comp2, "Title", 'Software Engineering', + self.assertRaises(ValueError, Review, 'User', self.comp2, "Title", 'Software Engineering', "Position", 4, "A degree", "Review", 4) # invalid description - self.assertRaises(ValueError, Review, self.comp2, "Title", 'Software Engineering', + self.assertRaises(ValueError, Review, 'User', self.comp2, "Title", 'Software Engineering', "Position", 4, "M.S.", "", 4) # rating is invalid - self.assertRaises(ValueError, Review, self.comp2, "Title", 'Software Engineering', + self.assertRaises(ValueError, Review, 'User', self.comp2, "Title", 'Software Engineering', "Position", 4, "M.S.", "Review", -3) - self.assertRaises(ValueError, Review, self.comp2, "Title", 'Software Engineering', + self.assertRaises(ValueError, Review, 'User', self.comp2, "Title", 'Software Engineering', "Position", 4, "M.S.", "Review", 6) def test_init_optional(self): 'Type Errors' # offer not a bool - self.assertRaises(TypeError, Review, self.comp1, "Title", 'Software Engineering', + self.assertRaises(TypeError, Review, 'User', self.comp1, "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4, offer=0, accepted=True, start_date="05-23-2022", intern_desc="desc", work_rat=4, culture_rat=4, location=("San Francisco", "California"), pay=35.25, bonuses="Bonus") # accepted not a bool - self.assertRaises(TypeError, Review, self.comp1, "Title", 'Software Engineering', + self.assertRaises(TypeError, Review, 'User', self.comp1, "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4, offer=True, accepted=None, start_date="05-23-2022", intern_desc="desc", work_rat=4, culture_rat=4, location=("San Francisco", "California"), pay=35.25, bonuses="Bonus") # start date of the wrong type - self.assertRaises(TypeError, Review, self.comp1, "Title", 'Software Engineering', + self.assertRaises(TypeError, Review, 'User', self.comp1, "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4, offer=True, accepted=True, start_date=["05", "23", "2022"], intern_desc="desc", work_rat=4, culture_rat=4, location=("San Francisco", "California"), pay=35.25, bonuses="Bonus") # intern desc is None - self.assertRaises(TypeError, Review, self.comp1, "Title", 'Software Engineering', + self.assertRaises(TypeError, Review, 'User', self.comp1, "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4, offer=True, accepted=True, start_date="05-23-2022", intern_desc=None, work_rat=4, culture_rat=4, location=("San Francisco", "California"), pay=35.25, bonuses="Bonus") # work rating of invalid type - self.assertRaises(TypeError, Review, self.comp1, "Title", 'Software Engineering', + self.assertRaises(TypeError, Review, 'User', self.comp1, "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4, offer=True, accepted=True, start_date="05-23-2022", intern_desc="desc", work_rat="10", culture_rat=4, location=("San Francisco", "California"), pay=35.25, bonuses="Bonus") # culture rating of invalid type - self.assertRaises(TypeError, Review, self.comp1, "Title", 'Software Engineering', + self.assertRaises(TypeError, Review, 'User', self.comp1, "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4, offer=True, accepted=True, start_date="05-23-2022", intern_desc="desc", work_rat=4, culture_rat=hex(20), location=("San Francisco", "California"), pay=35.25, bonuses="Bonus") # location not a tuple - self.assertRaises(TypeError, Review, self.comp1, "Title", 'Software Engineering', + self.assertRaises(TypeError, Review, 'User', self.comp1, "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4, offer=True, accepted=True, start_date="05-23-2022", intern_desc="desc", work_rat=4, culture_rat=4, location=["San Francisco", "California"], pay=35.25, bonuses="Bonus") # pay is a string - self.assertRaises(TypeError, Review, self.comp1, "Title", 'Software Engineering', + self.assertRaises(TypeError, Review, 'User', self.comp1, "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4, offer=True, accepted=True, start_date="05-23-2022", intern_desc="desc", work_rat=4, culture_rat=4, location=("San Francisco", "California"), pay="23.64", bonuses="Bonus") # bonuses is an int - self.assertRaises(TypeError, Review, self.comp1, "Title", 'Software Engineering', + self.assertRaises(TypeError, Review, 'User', self.comp1, "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4, offer=True, accepted=True, start_date="05-23-2022", intern_desc="desc", work_rat=4, culture_rat=4, location=("San Francisco", "California"), @@ -172,19 +187,19 @@ def test_init_optional(self): # state or city are of invalid types - self.assertRaises(TypeError, Review, self.comp2, "Title", 'Software Engineering', + self.assertRaises(TypeError, Review, 'User', self.comp2, "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4, offer=True, accepted=True, start_date="05-23-2022", intern_desc="desc", work_rat=4, culture_rat=4, location=("San Francisco", 333), pay=35.25, bonuses="Bonus") - self.assertRaises(TypeError, Review, self.comp2, "Title", 'Software Engineering', + self.assertRaises(TypeError, Review, 'User', self.comp2, "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4, offer=True, accepted=True, start_date="05-23-2022", intern_desc="desc", work_rat=4, culture_rat=4, location=(33, "California"), pay=35.25, bonuses="Bonus") - self.assertRaises(TypeError, Review, self.comp2, "Title", 'Software Engineering', + self.assertRaises(TypeError, Review, 'User', self.comp2, "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4, offer=True, accepted=True, start_date="05-23-2022", intern_desc="desc", work_rat=4, culture_rat=4, location=(33, True), @@ -192,60 +207,60 @@ def test_init_optional(self): 'Value Errors' # invalid date format - self.assertRaises(ValueError, Review, self.comp2, "Title", 'Software Engineering', + self.assertRaises(ValueError, Review, 'User', self.comp2, "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4, offer=True, accepted=True, start_date="23-05-2022", intern_desc="desc", work_rat=4, culture_rat=4, location=("San Francisco", "California"), pay=35.25, bonuses="Bonus") - self.assertRaises(ValueError, Review, self.comp2, "Title", 'Software Engineering', + self.assertRaises(ValueError, Review, 'User', self.comp2, "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4, offer=True, accepted=True, start_date="2022-05-23", intern_desc="desc", work_rat=4, culture_rat=4, location=("San Francisco", "California"), pay=35.25, bonuses="Bonus") # accepted an offer that wasn't given (offer = False) - self.assertRaises(ValueError, Review, self.comp2, "Title", 'Software Engineering', + self.assertRaises(ValueError, Review, 'User', self.comp2, "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4, offer=False, accepted=True, start_date="05-22-2023", intern_desc="desc", work_rat=4, culture_rat=4, location=("San Francisco", "California"), pay=35.25, bonuses="Bonus") # invalid artings - self.assertRaises(ValueError, Review, self.comp2, "Title", 'Software Engineering', + self.assertRaises(ValueError, Review, 'User', self.comp2, "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4, offer=True, accepted=True, start_date="05-23-2022", intern_desc="desc", work_rat=-4, culture_rat=4, location=("San Francisco", "California"), pay=35.25, bonuses="Bonus") - self.assertRaises(ValueError, Review, self.comp2, "Title", 'Software Engineering', + self.assertRaises(ValueError, Review, 'User', self.comp2, "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4, offer=True, accepted=True, start_date="05-23-2022", intern_desc="desc", work_rat=10, culture_rat=4, location=("San Francisco", "California"), pay=35.25, bonuses="Bonus") - self.assertRaises(ValueError, Review, self.comp2, "Title", 'Software Engineering', + self.assertRaises(ValueError, Review, 'User', self.comp2, "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4, offer=True, accepted=True, start_date="05-23-2022", intern_desc="desc", work_rat=4, culture_rat=-4, location=("San Francisco", "California"), pay=35.25, bonuses="Bonus") # invalid state - self.assertRaises(ValueError, Review, self.comp2, "Title", 'Software Engineering', + self.assertRaises(ValueError, Review, 'User', self.comp2, "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4, offer=True, accepted=True, start_date="05-23-2022", intern_desc="desc", work_rat=4, culture_rat=4, location=("San Francisco", "Some State"), pay=35.25, bonuses="Bonus") # invalid pay - self.assertRaises(ValueError, Review, self.comp2, "Title", 'Software Engineering', + self.assertRaises(ValueError, Review, 'User', self.comp2, "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4, offer=True, accepted=True, start_date="05-23-2022", intern_desc="desc", work_rat=4, culture_rat=4, location=("San Francisco", "California"), pay=-35.25, bonuses="Bonus") # invalid city (not alphabetical) - self.assertRaises(ValueError, Review, self.comp2, "Title", 'Software Engineering', + self.assertRaises(ValueError, Review, 'User', self.comp2, "Title", 'Software Engineering', "Position", 4, "M.S.", "Interview", 4, offer=True, accepted=True, start_date="05-23-2022", intern_desc="desc", work_rat=4, culture_rat=4, location=("Some c1t5", "California"), diff --git a/tests/test_user.py b/tests/test_user.py index 30d86e3..3ef998e 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -1,5 +1,6 @@ from model import User -import unittest +import unittest +import bcrypt class TestUser(unittest.TestCase): def setUp(self): @@ -13,7 +14,6 @@ def setUp(self): self.valid_email1, self.valid_password, self.valid_pic) - def test_types(self): #username tests @@ -50,23 +50,25 @@ def test_values(self): #password tests self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,"",self.valid_pic) self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,"1",self.valid_pic) - self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,"apple",self.valid_pic) - self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,"test12",self.valid_pic) + self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,"aple",self.valid_pic) #url tests self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,self.valid_password,"test.com") - self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,self.valid_password,"") self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,self.valid_password,"@") self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,self.valid_password,"@gmail.com") self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,self.valid_password,"user@") self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,self.valid_password,"https://") self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,self.valid_password,"http//www.google.com") + def test_set_profile_pic(self): self.assertTrue(self.user.set_profile_pic("https://bit.ly/3KF5IT0")) self.assertFalse(self.user.set_profile_pic("bit.ly/3KF5IT0")) self.assertEqual(self.user.profile_pic,"https://bit.ly/3KF5IT0") self.assertTrue(self.user.set_profile_pic("https://www.google.com")) self.assertEqual(self.user.profile_pic, "https://www.google.com") - # self.assertTrue(self.user.set_profile_pic("https://www.com")) - def test_account_creation_time(self): - self.assertIsNotNone(self.user.creation_time) \ No newline at end of file + def test_pswd(self): + self.assertTrue(bcrypt.checkpw("password1".encode("utf-8"), + self.user.password)) + + self.assertFalse(bcrypt.checkpw("password".encode("utf-8"), + self.user.password)) \ No newline at end of file From 764dc2bdfa3c9bdbdf91911fb61c7940bfe9a9df Mon Sep 17 00:00:00 2001 From: Mercrist Date: Wed, 13 Apr 2022 11:07:20 -0400 Subject: [PATCH 07/13] Prettified code up --- model.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/model.py b/model.py index eaaf1bc..6cbb5a2 100644 --- a/model.py +++ b/model.py @@ -76,11 +76,8 @@ def is_url(url:str)->bool: Returns: bool indicating validity # """ - regex = ("((http|https)://)(www.)?" + - "[a-zA-Z0-9@:%._\\+~#?&//=]" + - "{2,256}\\.[a-z]" + - "{2,6}\\b([-a-zA-Z0-9@:%" + - "._\\+~#?&//=]*)") + regex = ("((http|https)://)(www.)?" + "[a-zA-Z0-9@:%._\\+~#?&//=]" + "{2,256}\\.[a-z]" + + "{2,6}\\b([-a-zA-Z0-9@:%" + "._\\+~#?&//=]*)") # Compile the ReGex p = re.compile(regex) @@ -96,12 +93,6 @@ def is_url(url:str)->bool: return True else: return False - # from urllib.parse import urlparse - # try: - # result = urlparse(url) - # return all([result.scheme, result.netloc]) - # except: - # return False def validate_date(date:str)->None: """Validates a given date. Raises From 3c4fc9e13951cca3dc10ea78777f2589ad2f2a5f Mon Sep 17 00:00:00 2001 From: Mercrist Date: Wed, 13 Apr 2022 15:32:04 -0400 Subject: [PATCH 08/13] Documentation mostly done --- model.py | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 86 insertions(+), 4 deletions(-) diff --git a/model.py b/model.py index 6cbb5a2..bcdd8c9 100644 --- a/model.py +++ b/model.py @@ -73,8 +73,9 @@ def is_url(url:str)->bool: Args: url (str): The url to validate + Returns: - bool indicating validity + bool: Indicates URL validity # """ regex = ("((http|https)://)(www.)?" + "[a-zA-Z0-9@:%._\\+~#?&//=]" + "{2,256}\\.[a-z]" + "{2,6}\\b([-a-zA-Z0-9@:%" + "._\\+~#?&//=]*)") @@ -113,14 +114,55 @@ def validate_date(date:str)->None: except ValueError: raise ValueError("Incorrect date format. Should be MM-DD-YYYY") -def validate_email(email:str): +def validate_email(email:str)->bool: + """Validates an email. + + Args: + email (str): The email string to validate. + + Returns: + bool: A boolean indicating whether the email, passed in as a parameter, is valid. + """ regex = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b' if re.fullmatch(regex,email): return True return False class Company: + """Represents an existing and reviewable company. + Contains internal information on the amount + of reviews submitted to that company as well + as the company's status on Pathway. + + Attributes: + name: A string, representing the company's name. + category: A string, representing the category the company belongs to. + logo_img: + description: A string, representing the company's description. + banner_img: + company_rat: The average of the company's overall rating, as a float. + work_rat: The average of the company's workplace reviews, as a float. + culture_rat: A float denoting the average of the company's culture reviews. + num_company_reviews: The total number of ratings under the overall rating category, as an integer. + num_work_reviews: The total number of workplace reviews, as an integer. + num_culture_reviews: An integer denoting the total number of work culture reviews. + total_reviews: An integer representing the overall, singular number of reviews. + status: A string representing the company's status. Must be a valid CSS color keyword. + """ def __init__(self, name:str, category:str, logo_img:str, description:str="", banner_img:str="")->None: + """Initializes a reviewable Company. + + Args: + name (str): The company's name. + category (str): The category the company falls under. + logo_img (str): + description (str, optional): A description on the current company. + banner_img (str, optional): + + Raises: + TypeError: Raised if any of the arguments aren't of the expected types. + ValueError: Raised if the company's name, categories, and more are of the correct types but not supported. + """ #type checks if type(name) is not str: raise TypeError(f"Error: Company name must a string. Given type '{type(name)}'.") @@ -179,6 +221,14 @@ def __init__(self, name:str, category:str, logo_img:str, description:str="", ban self.status = "grey" def update_status(self)->None: + """Updates the company's status. A company + status is a color indicating whether the + company has had favorable reviews or not. + This manifests around the company logo's + circumference on the front end. Changes + based on what numerical ranges the company's + overall reviews fall under. + """ if self.company_rat is not None: if self.company_rat == 5: self.status = "green" @@ -197,7 +247,30 @@ def update_status(self)->None: self.status = "black" class User: + """Represents a user class. Contains the user's information such as + their username, email, password, and more. + + Attributes: + username: A string, representing the user's username. + email: The user's email, as a string. + password: The user's confirmed password, as plaintext. Hashed once the account is created. + profile_pic: + creation_time: A datetime object, containing the time the user account was created. + """ def __init__(self, username:str, email:str, pswd:str, profile_pic:str="")->None: + """Initializes a user given the information + the user decides to input. + + Args: + username (str): The user's username. + email (str): The user's email. + pswd (str): The user's password, as plaintext. + profile_pic (str, optional): + + Raises: + TypeError: Raised if none of the parameters match the expected types. + ValueError: Raised if the parameters match the expected types but fail any validation checks. + """ #type checks if type(username) is not str: raise TypeError(f"Error: username must a string. Given type '{type(username)}'.") @@ -218,14 +291,23 @@ def __init__(self, username:str, email:str, pswd:str, profile_pic:str="")->None: if profile_pic != "" and not is_url(profile_pic): raise ValueError("Error: Profile picture URL not valid!") - self.username = username self.email = email self.password = self.generate_password(pswd) self.profile_pic = profile_pic self.creation_time = datetime.now() - def generate_password(self,unhashed:str): + def generate_password(self, unhashed:str)->bytes: + """Generates a secure, hashed version of + the user's password, initially passed + in as plaintext, with bcrypt's methods. + + Args: + unhashed (str): The user's password as plaintext. + + Returns: + bytes: A hashed representation of the user's password, in bytes. + """ salt = bcrypt.gensalt() hashed = bcrypt.hashpw(unhashed.encode("utf-8"), salt) return hashed From 33804adf2a814f3eeadd78d8eb2aa51e0f25f33e Mon Sep 17 00:00:00 2001 From: XV1R Date: Wed, 13 Apr 2022 20:43:51 -0400 Subject: [PATCH 09/13] large changes for User signup; database connection set up for debugging; placeholder template for signing up; changed user tests to reflect changes --- app.py | 42 ++++++++++++++++++++++++++++++++++-------- config.py | 4 ++++ model.py | 20 +++++++++++++++++--- templates/index.html | 4 +++- templates/signup.html | 19 +++++++++++++++++++ tests/test_user.py | 31 +++++++++++++------------------ 6 files changed, 90 insertions(+), 30 deletions(-) create mode 100644 templates/signup.html diff --git a/app.py b/app.py index b8b80e5..5e25099 100644 --- a/app.py +++ b/app.py @@ -1,11 +1,37 @@ -import re -from flask import Flask, url_for, render_template +from flask import Flask, redirect, url_for, render_template, request +from flask_pymongo import PyMongo import model app = Flask(__name__) -# app.config.from_object('config') +app.config.from_object('config') +mongo = PyMongo(app) +db = mongo.db +pathway = db.pathway +@app.route('/file/', methods=["GET"]) +def file(filename): + return mongo.send_file(filename) + +@app.route('/create_user', methods=['POST']) +def create_user(): + if 'profile_image' in request.files: + pf = request.files['profile_image'] + filename = model.hash_profile_name(pf.filename) + user = model.User( + request.form['username'], + pswd=request.form['password'], + email=request.form['email'].strip(), + profile_pic=filename + ) + mongo.save_file(filename,pf) + pathway.insert_one(user.to_json()) + + return redirect(url_for('index')) + +@app.route('/signup', methods=["GET"]) +def signup(): + return render_template("signup.html") @app.route("/") @app.route("/index") @@ -34,10 +60,10 @@ def index(): offer=True ) review_2 = model.Review(company2, "Title", 'Software Engineering', - "Position", company_rating=4, education="M.S.", interview_desc="Interview", - interview_rat=4, offer=False, accepted=False, start_date="05-23-2022", - intern_desc="desc", work_rat=None, culture_rat=None, location=("San Francisco", "California"), - pay=35.25, bonuses="Bonus") + "Position", company_rating=4, education="M.S.", interview_desc="Interview", + interview_rat=4, offer=False, accepted=False, start_date="05-23-2022", + intern_desc="desc", work_rat=None, culture_rat=None, location=("San Francisco", "California"), + pay=35.25, bonuses="Bonus") placeholder = [review_1 for _ in range(3)] placeholder.extend([review_2 for _ in range(3)]) - return render_template("index.html", recent_reviews=placeholder) \ No newline at end of file + return render_template("index.html", recent_reviews=placeholder) diff --git a/config.py b/config.py index e69de29..6558dbf 100644 --- a/config.py +++ b/config.py @@ -0,0 +1,4 @@ +import os +SECRET_KEY = os.urandom(16).hex() +#TODO: reset this password bc it's in plaintext +MONGO_URI= f"mongodb+srv://admin:Rqs7wmYA1qkMzh8X@pathway.rgqhh.mongodb.net/pathway?retryWrites=true&w=majority" \ No newline at end of file diff --git a/model.py b/model.py index 83d7b01..a851a2c 100644 --- a/model.py +++ b/model.py @@ -128,6 +128,11 @@ def validate_email(email:str): return True return False +def hash_profile_name(name:str): + salt = bcrypt.gensalt() + hashed = bcrypt.hashpw(name.encode("utf-8"),salt) + return hashed.decode("utf-8") + class Company: def __init__(self, name:str, category:str, logo_img:str, description:str="", banner_img:str="")->None: #type checks @@ -198,11 +203,11 @@ def __init__(self, username:str, email:str, pswd:str, profile_pic:str="")->None: if len(username) <= 3: raise ValueError("Error: usernames must have more than 3 characters.") if not validate_email(email): - raise ValueError("Error: Invalid email") + raise ValueError(f"Error: Invalid email. Given email {email} with type {type(email)} ") if len(pswd) < 8: raise ValueError("Error: Password must be 6 or more characters.") - if not is_url(profile_pic): - raise ValueError("Error: Profile picture URL not valid!") + if len(profile_pic) == 0: + raise ValueError("Error: Invalid Profile Picture name.") self.username = username @@ -231,6 +236,15 @@ def set_profile_pic(self,link:str) -> bool: self.profile_pic = link return True + def to_json(self)->dict: + return { + "username":self.username, + "email":self.email, + "password":self.password, + "profile_pic":self.profile_pic, + "creation_time":self.creation_time + } + class Review: diff --git a/templates/index.html b/templates/index.html index 688b913..dd966ec 100644 --- a/templates/index.html +++ b/templates/index.html @@ -15,7 +15,9 @@

Read Reviews on top companies from fellow students.

- + + sample +
    {% for review in recent_reviews %} diff --git a/templates/signup.html b/templates/signup.html new file mode 100644 index 0000000..f1c712e --- /dev/null +++ b/templates/signup.html @@ -0,0 +1,19 @@ +{% extends 'base.html' %} + +{% block htmlclass %}class="signup"{% endblock %} + +{% block head %} +Pathway - Sign up +{% endblock %} + +{% block bodyclass %}class="signup"{% endblock %} + +{% block main %} +
    + + + + + +
    +{% endblock %} \ No newline at end of file diff --git a/tests/test_user.py b/tests/test_user.py index 30d86e3..881c8bb 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -35,31 +35,26 @@ def test_types(self): def test_values(self): #username tests - self.assertRaises(ValueError,User,"tes",self.valid_email1,self.valid_password,self.valid_email1) - self.assertRaises(ValueError,User,"",self.valid_email1,self.valid_password,self.valid_email1) - self.assertRaises(ValueError,User,"tes",self.valid_email1,self.valid_password,self.valid_email1) - self.assertRaises(ValueError,User,"1",self.valid_email1,self.valid_password,self.valid_email1) - self.assertRaises(ValueError,User,"__",self.valid_email1,self.valid_password,self.valid_email1) + self.assertRaises(ValueError,User,"tes",self.valid_email1,self.valid_password,self.valid_pic) + self.assertRaises(ValueError,User,"",self.valid_email1,self.valid_password,self.valid_pic) + self.assertRaises(ValueError,User,"tes",self.valid_email1,self.valid_password,self.valid_pic) + self.assertRaises(ValueError,User,"1",self.valid_email1,self.valid_password,self.valid_pic) + self.assertRaises(ValueError,User,"__",self.valid_email1,self.valid_password,self.valid_pic) #email tests - self.assertRaises(ValueError,User,self.valid_username,"inv@test",self.valid_password,self.valid_email1) - self.assertRaises(ValueError,User,self.valid_username,"email.com",self.valid_password,self.valid_email1) - self.assertRaises(ValueError,User,self.valid_username,"@test.com",self.valid_password,self.valid_email1) - self.assertRaises(ValueError,User,self.valid_username,"inv@@@",self.valid_password,self.valid_email1) - self.assertRaises(ValueError,User,self.valid_username,"t@",self.valid_password,self.valid_email1) - self.assertRaises(ValueError,User,self.valid_username,"@.com",self.valid_password,self.valid_email1) + self.assertRaises(ValueError,User,self.valid_username,"inv@test",self.valid_password,self.valid_pic) + self.assertRaises(ValueError,User,self.valid_username,"email.com",self.valid_password,self.valid_pic) + self.assertRaises(ValueError,User,self.valid_username,"@test.com",self.valid_password,self.valid_pic) + self.assertRaises(ValueError,User,self.valid_username,"inv@@@",self.valid_password,self.valid_pic) + self.assertRaises(ValueError,User,self.valid_username,"t@",self.valid_password,self.valid_pic) + self.assertRaises(ValueError,User,self.valid_username,"@.com",self.valid_password,self.valid_pic) #password tests self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,"",self.valid_pic) self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,"1",self.valid_pic) self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,"apple",self.valid_pic) self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,"test12",self.valid_pic) - #url tests - self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,self.valid_password,"test.com") + #profile_pic tests self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,self.valid_password,"") - self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,self.valid_password,"@") - self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,self.valid_password,"@gmail.com") - self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,self.valid_password,"user@") - self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,self.valid_password,"https://") - self.assertRaises(ValueError,User,self.valid_username,self.valid_email1,self.valid_password,"http//www.google.com") + def test_set_profile_pic(self): self.assertTrue(self.user.set_profile_pic("https://bit.ly/3KF5IT0")) self.assertFalse(self.user.set_profile_pic("bit.ly/3KF5IT0")) From 5e4d8ac3a60c224ed84cb814701214e896592a47 Mon Sep 17 00:00:00 2001 From: XV1R Date: Wed, 13 Apr 2022 21:08:41 -0400 Subject: [PATCH 10/13] changed profile_pic default to 'default.jpg' --- model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model.py b/model.py index a695ab4..4e42bcf 100644 --- a/model.py +++ b/model.py @@ -262,7 +262,7 @@ class User: profile_pic: creation_time: A datetime object, containing the time the user account was created. """ - def __init__(self, username:str, email:str, pswd:str, profile_pic:str="")->None: + def __init__(self, username:str, email:str, pswd:str, profile_pic:str="default.jpg")->None: """Initializes a user given the information the user decides to input. From 4a18c3a56ce208e082638e09a7e6cf56acfc0ab7 Mon Sep 17 00:00:00 2001 From: XV1R Date: Wed, 13 Apr 2022 21:17:34 -0400 Subject: [PATCH 11/13] fixed placeholder reviews to reflect new constructor --- app.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/app.py b/app.py index 5e25099..a187ea5 100644 --- a/app.py +++ b/app.py @@ -48,18 +48,13 @@ def index(): logo_img="https://bit.ly/3Jvmy5t", banner_img="http://somelink.com" ) - review_1 = model.Review( - company1, - "Placeholder Review", - "Security Engineering", - "Security Engineer Intern", - 4, - "B.S.", - "Had a good time overall. Tasking was tough and hours were long.", - 5, - offer=True - ) - review_2 = model.Review(company2, "Title", 'Software Engineering', + review_1 = model.Review('user',company1,"Placeholder Review","Security Engineering", + "Security Engineer Intern",company_rating=4,education="B.S.", + interview_desc="Had a good time overall. Tasking was tough and hours were long.", + interview_rat=5,offer=False, accepted=False, start_date="05-23-2022", + intern_desc="desc", work_rat=None, culture_rat=None, location=("San Francisco", "California"), + pay=35.25, bonuses="Bonus") + review_2 = model.Review('user',company2, "Title", 'Software Engineering', "Position", company_rating=4, education="M.S.", interview_desc="Interview", interview_rat=4, offer=False, accepted=False, start_date="05-23-2022", intern_desc="desc", work_rat=None, culture_rat=None, location=("San Francisco", "California"), From 82284e20a5fdaf17dfb7bfa832a09dad0d733ba5 Mon Sep 17 00:00:00 2001 From: XV1R Date: Wed, 13 Apr 2022 21:38:03 -0400 Subject: [PATCH 12/13] extra documentation for functions and routes --- app.py | 19 +++++++++++++++++++ model.py | 18 +++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/app.py b/app.py index a187ea5..a1608f0 100644 --- a/app.py +++ b/app.py @@ -11,10 +11,26 @@ @app.route('/file/', methods=["GET"]) def file(filename): + """Helper route for getting files from database. Functions as wrapper for + mongo.send_file function. + + Args: + filename (str): file to return + """ return mongo.send_file(filename) @app.route('/create_user', methods=['POST']) def create_user(): + """Helper route for creating users.Takes in form information and + pushes user to database. Should only be accessed from signup route. + + Args: + Form: + username (str) + password (str) + email (str) + profile_pic (file) + """ if 'profile_image' in request.files: pf = request.files['profile_image'] filename = model.hash_profile_name(pf.filename) @@ -36,6 +52,9 @@ def signup(): @app.route("/") @app.route("/index") def index(): + """Route for index page. Renders 'index.html' file. Currently has + placeholder data for debugging/visualization of work. + """ company1 = model.Company( name="Microsoft", category="Software", diff --git a/model.py b/model.py index 4e42bcf..bf3a669 100644 --- a/model.py +++ b/model.py @@ -3,7 +3,7 @@ import re company_categories = ["Software", "Hardware", "Computing", "Finance", "Government", "Defense", "Aerospace", - "Restaurant", "Automobiles", "Aviation", "Retail", "Other"] + "Restaurant", "Automobiles", "Aviation", "Retail", "Other"] job_categories = ["Software Engineering", "Computer Science", "Information Technology", "System Administrator", "Computer Engineering", "Eletrical Engineering", "Data Science", "Security Engineering", @@ -129,6 +129,16 @@ def validate_email(email:str)->bool: return False def hash_profile_name(name:str): + """Helper method for hashing profile names. Used to avoid collisions in filenames + when uploading profile pictures to the database. Method should generate entirely + unique hashes stored both in the user object and separately by the database. + + Args: + name (str): name to hash + + Returns: + str: String of hashed name encoded in utf-8. + """ salt = bcrypt.gensalt() hashed = bcrypt.hashpw(name.encode("utf-8"),salt) return hashed.decode("utf-8") @@ -327,6 +337,12 @@ def set_profile_pic(self,link:str)->bool: return True def to_json(self)->dict: + """Generates a copy of the User object as a dictionary for use as JSON. + + Returns: + dict: Representing the user object attributes. + + """ return { "username":self.username, "email":self.email, From 0d1188e70e929ef95d60d12e704e8753a5790913 Mon Sep 17 00:00:00 2001 From: XV1R Date: Thu, 14 Apr 2022 01:08:22 -0400 Subject: [PATCH 13/13] mostly done user; basic login done; wrapper pymongo class fixing issue --- app.py | 90 ++++++++++++++++++++++++++++++-------- model.py | 74 ++++++++++++++++++++++++++++++- static/Images/default.jpg | Bin 0 -> 7861 bytes templates/index.html | 7 ++- templates/login.html | 17 +++++++ 5 files changed, 168 insertions(+), 20 deletions(-) create mode 100644 static/Images/default.jpg create mode 100644 templates/login.html diff --git a/app.py b/app.py index a1608f0..7c10f3c 100644 --- a/app.py +++ b/app.py @@ -1,13 +1,18 @@ -from flask import Flask, redirect, url_for, render_template, request -from flask_pymongo import PyMongo +from flask import Flask, redirect, url_for, render_template, request, session +# from flask_pymongo import PyMongo import model +import bcrypt + app = Flask(__name__) app.config.from_object('config') -mongo = PyMongo(app) +mongo = model.PyMongoFixed(app) db = mongo.db -pathway = db.pathway +if "users" not in db.list_collection_names(): + db.create_collection("users") + #TODO: Use save_file to store default.jpg for non-logged in users or default pic. + @app.route('/file/', methods=["GET"]) def file(filename): @@ -19,6 +24,36 @@ def file(filename): """ return mongo.send_file(filename) +@app.route('/login',methods=['GET','POST']) +def login(): + """Login form for users. Sends POST request to itself. If it + validates the user, redirects to index page. Taken mostly from class + slides. + """ + if request.method == "POST": + users = db.users + login_user = users.find_one({'username':request.form['username']}) + if login_user: + db_password = login_user['password'] + password = request.form['password'].encode('utf-8') + if bcrypt.checkpw(password,db_password): + session['username'] = request.form['username'] + return redirect(url_for('index')) + else: + return 'Invalid username/password combination.' + else: + return 'User not found.' + else: + return render_template('login.html') + +@app.route('/logout') +def logout(): + """Logging out endpoint. Clears all local + session information and redirects to index. + """ + session.clear() + return redirect('/') + @app.route('/create_user', methods=['POST']) def create_user(): """Helper route for creating users.Takes in form information and @@ -31,23 +66,35 @@ def create_user(): email (str) profile_pic (file) """ - if 'profile_image' in request.files: - pf = request.files['profile_image'] - filename = model.hash_profile_name(pf.filename) - user = model.User( - request.form['username'], - pswd=request.form['password'], - email=request.form['email'].strip(), - profile_pic=filename - ) - mongo.save_file(filename,pf) - pathway.insert_one(user.to_json()) - + users = db.users + existing_user = users.find_one({'name':request.form['username']}) + if not existing_user: + if 'profile_image' in request.files: + pf = request.files['profile_image'] + filename = model.hash_profile_name(pf.filename) + user = model.User( + request.form['username'], + pswd=request.form['password'], + email=request.form['email'].strip(), + profile_pic=filename + ) + mongo.save_file(filename,pf) + else: + user = model.User( + request.form['username'], + pswd=request.form['password'], + email=request.form['email'].strip() + ) + users.insert_one(user.to_json()) + session['username'] = user.username return redirect(url_for('index')) @app.route('/signup', methods=["GET"]) def signup(): - return render_template("signup.html") + """Endpoint for 'signup.html' + """ + return render_template('signup.html') + @app.route("/") @app.route("/index") @@ -80,4 +127,11 @@ def index(): pay=35.25, bonuses="Bonus") placeholder = [review_1 for _ in range(3)] placeholder.extend([review_2 for _ in range(3)]) - return render_template("index.html", recent_reviews=placeholder) + + if 'username' in session: + current_user = db.users.find_one({"username":session['username']}) + if not current_user: + return render_template("index.html", recent_reviews=placeholder, user=None) + return render_template("index.html", recent_reviews=placeholder, user=current_user) + return render_template("index.html", recent_reviews=placeholder, user=None) + diff --git a/model.py b/model.py index bf3a669..8c535ec 100644 --- a/model.py +++ b/model.py @@ -2,6 +2,8 @@ import bcrypt import re +from flask_pymongo import PyMongo + company_categories = ["Software", "Hardware", "Computing", "Finance", "Government", "Defense", "Aerospace", "Restaurant", "Automobiles", "Aviation", "Retail", "Other"] @@ -143,6 +145,7 @@ def hash_profile_name(name:str): hashed = bcrypt.hashpw(name.encode("utf-8"),salt) return hashed.decode("utf-8") + class Company: """Represents an existing and reviewable company. Contains internal information on the amount @@ -623,4 +626,73 @@ def score_formula(old_score:float, num_reviews:int, new_score:int)->float: self.company.num_culture_reviews += 1 -local_companies = {Company("Microsoft", "Software", "https://bit.ly/3uWfYzK", banner_img="https://bit.ly/3xfolJs")} \ No newline at end of file +local_companies = {Company("Microsoft", "Software", "https://bit.ly/3uWfYzK", banner_img="https://bit.ly/3xfolJs")} + + + +class PyMongoFixed(PyMongo): + """A small magic trick Class that functions as a Wrapper for PyMongo. + Overwrites a broken flask_pymongo 2.3.0 function to fetch image data from + the database. See https://github.com/dcrosta/flask-pymongo/issues/153 + to learn more. + """ + + def __init__(self, app=None, uri=None, *args, **kwargs): + super().__init__(app, uri, *args, **kwargs) + self.text_type = str + self.num_type = int + + + def send_file(self, filename, base="fs", version=-1, cache_for=31536000): + """Respond with a file from GridFS. + + Returns an instance of the :attr:`~flask.Flask.response_class` + containing the named file, and implement conditional GET semantics + (using :meth:`~werkzeug.wrappers.ETagResponseMixin.make_conditional`). + + .. code-block:: python + + @app.route("/uploads/") + def get_upload(filename): + return mongo.send_file(filename) + + :param str filename: the filename of the file to return + :param str base: the base name of the GridFS collections to use + :param bool version: if positive, return the Nth revision of the file + identified by filename; if negative, return the Nth most recent + revision. If no such version exists, return with HTTP status 404. + :param int cache_for: number of seconds that browsers should be + instructed to cache responses + """ + from flask import abort, current_app, request + from gridfs import GridFS, NoFile + from werkzeug.wsgi import wrap_file + + if not isinstance(base, self.text_type): + raise TypeError("'base' must be string or unicode") + if not isinstance(version, self.num_type): + raise TypeError("'version' must be an integer") + if not isinstance(cache_for, self.num_type): + raise TypeError("'cache_for' must be an integer") + + storage = GridFS(self.db, base) + + try: + fileobj = storage.get_version(filename=filename, version=version) + except NoFile: + abort(404) + + # mostly copied from flask/helpers.py, with + # modifications for GridFS + data = wrap_file(request.environ, fileobj, buffer_size=1024 * 255) + response = current_app.response_class( + data, + mimetype=fileobj.content_type, + direct_passthrough=True, + ) + response.content_length = fileobj.length + response.last_modified = fileobj.upload_date + response.cache_control.max_age = cache_for + response.cache_control.public = True + response.make_conditional(request) + return response \ No newline at end of file diff --git a/static/Images/default.jpg b/static/Images/default.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7b940c8ab045b168f4eb2b8a0fa99fd31da6acd5 GIT binary patch literal 7861 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 Read Reviews on top companies from fellow students.

    - sample + {% if user %} + sample + {% else %} + sample + {% endif %} +
      diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..93c71d2 --- /dev/null +++ b/templates/login.html @@ -0,0 +1,17 @@ +{% extends 'base.html' %} + +{% block htmlclass %}class="login{% endblock %} + +{% block head %} +Pathway - Login +{% endblock %} + +{% block bodyclass %}class="login"{% endblock %} + +{% block main %} +
      + + + +
      +{% endblock %} \ No newline at end of file