|
@@ -16,19 +16,12 @@ import logging
|
|
|
|
|
|
|
|
### TO DO ###
|
|
### TO DO ###
|
|
|
#
|
|
#
|
|
|
-# Print useful reports (land only, house and land, etc)
|
|
|
|
|
# Check if db entries no longer appear online (mark expired)
|
|
# Check if db entries no longer appear online (mark expired)
|
|
|
# When checking online from various sites, check if address already exists in db
|
|
# When checking online from various sites, check if address already exists in db
|
|
|
# - if so, warn user and do not add
|
|
# - if so, warn user and do not add
|
|
|
-# Add date_added to initial entries
|
|
|
|
|
# Check results against database for changes
|
|
# Check results against database for changes
|
|
|
# - update and add/change date_modified
|
|
# - update and add/change date_modified
|
|
|
-# Add argument to run update query when results.py is calles
|
|
|
|
|
-# Add database column to hold parcel number. Make links to GIS servers
|
|
|
|
|
-#
|
|
|
|
|
-# IDENTIFY NEW PROPERTIES!!
|
|
|
|
|
-#
|
|
|
|
|
-# Automate db opening and closing when calling dbinsert()
|
|
|
|
|
|
|
+# Add database column to hold parcel number. Make links to GIS servers
|
|
|
#
|
|
#
|
|
|
#############
|
|
#############
|
|
|
|
|
|
|
@@ -54,6 +47,7 @@ class Property:
|
|
|
self.description = description
|
|
self.description = description
|
|
|
self.link = link
|
|
self.link = link
|
|
|
|
|
|
|
|
|
|
+
|
|
|
class Parameters:
|
|
class Parameters:
|
|
|
'''Parameters taken from config file'''
|
|
'''Parameters taken from config file'''
|
|
|
|
|
|
|
@@ -69,15 +63,18 @@ class Parameters:
|
|
|
except Exception as err:
|
|
except Exception as err:
|
|
|
print(err, "Using default search Parameters")
|
|
print(err, "Using default search Parameters")
|
|
|
|
|
|
|
|
|
|
+
|
|
|
class Mylogger:
|
|
class Mylogger:
|
|
|
''' Logging tool for this session'''
|
|
''' Logging tool for this session'''
|
|
|
|
|
+
|
|
|
def __init__(self):
|
|
def __init__(self):
|
|
|
log_params = Parameters().log_params
|
|
log_params = Parameters().log_params
|
|
|
|
|
|
|
|
- filename=log_params.get('log_file')
|
|
|
|
|
- level=int(log_params.get('logging_level', str('30')))
|
|
|
|
|
- format='%(asctime)s %(levelname)-8s %(message)s'
|
|
|
|
|
- datefmt='%Y-%m-%d %H:%M:%S'
|
|
|
|
|
|
|
+ filename = log_params.get('log_file')
|
|
|
|
|
+ level = int(log_params.get('logging_level', str('30')))
|
|
|
|
|
+ format = '%(asctime)s %(levelname)-8s %(message)s'
|
|
|
|
|
+ datefmt = '%Y-%m-%d %H:%M:%S'
|
|
|
|
|
+
|
|
|
|
|
|
|
|
class Search:
|
|
class Search:
|
|
|
'''Universal Search Criteria'''
|
|
'''Universal Search Criteria'''
|
|
@@ -140,18 +137,20 @@ class Search:
|
|
|
## FOR TESTING, PRINT ALL ATTRIBUTES OF SEARCH ##
|
|
## FOR TESTING, PRINT ALL ATTRIBUTES OF SEARCH ##
|
|
|
logging.debug(vars(self))
|
|
logging.debug(vars(self))
|
|
|
|
|
|
|
|
|
|
+
|
|
|
class ImproperSearchError(Exception):
|
|
class ImproperSearchError(Exception):
|
|
|
def __init__(self, search, message="Improper Search. Must use instance of Search class"):
|
|
def __init__(self, search, message="Improper Search. Must use instance of Search class"):
|
|
|
self.search = search
|
|
self.search = search
|
|
|
self.message = message
|
|
self.message = message
|
|
|
super().__init__(self.message)
|
|
super().__init__(self.message)
|
|
|
|
|
|
|
|
|
|
+
|
|
|
class MLSDATA:
|
|
class MLSDATA:
|
|
|
"""Fetches and stores MLS Data
|
|
"""Fetches and stores MLS Data
|
|
|
Currently only supports GeorgiaMLS.com (GMLS)"""
|
|
Currently only supports GeorgiaMLS.com (GMLS)"""
|
|
|
counties = ['Gwinnett', 'Barrow', 'Hall', 'Jackson', 'Walton']
|
|
counties = ['Gwinnett', 'Barrow', 'Hall', 'Jackson', 'Walton']
|
|
|
GoogleAPIKey = 'AIzaSyAXAnpBtjv760W8YIPqKZ0dFXpwAaZN7Es'
|
|
GoogleAPIKey = 'AIzaSyAXAnpBtjv760W8YIPqKZ0dFXpwAaZN7Es'
|
|
|
- live_google = True
|
|
|
|
|
|
|
+ # live_google = False
|
|
|
|
|
|
|
|
def __init__(self, mlstype):
|
|
def __init__(self, mlstype):
|
|
|
self.parameters = Parameters()
|
|
self.parameters = Parameters()
|
|
@@ -161,6 +160,7 @@ class MLSDATA:
|
|
|
self.cnx = ''
|
|
self.cnx = ''
|
|
|
self.new_listings = []
|
|
self.new_listings = []
|
|
|
self.email = self.parameters.search_params.getboolean('email')
|
|
self.email = self.parameters.search_params.getboolean('email')
|
|
|
|
|
+ self.live_google = self.parameters.search_params.getboolean('live_google')
|
|
|
print('Email ' + str(self.email))
|
|
print('Email ' + str(self.email))
|
|
|
|
|
|
|
|
def stringbuilder(self, search: Search, county):
|
|
def stringbuilder(self, search: Search, county):
|
|
@@ -297,17 +297,25 @@ class MLSDATA:
|
|
|
print("Scanning for results in " + county + " using the " + self.mlstype.upper() + " database.")
|
|
print("Scanning for results in " + county + " using the " + self.mlstype.upper() + " database.")
|
|
|
if self.mlstype == 'gmls':
|
|
if self.mlstype == 'gmls':
|
|
|
list = self.gmlsparser(self.stringbuilder(search, county), county)
|
|
list = self.gmlsparser(self.stringbuilder(search, county), county)
|
|
|
- logging.info("Completed search in " + county + " county. " + str(len(list)) + " total properties scanned.")
|
|
|
|
|
|
|
+ logging.info(
|
|
|
|
|
+ "Completed search in " + county + " county. " + str(len(list)) + " total properties scanned.")
|
|
|
return list
|
|
return list
|
|
|
else:
|
|
else:
|
|
|
raise ImproperSearchError(search)
|
|
raise ImproperSearchError(search)
|
|
|
|
|
|
|
|
- def checkdb(self, criteria_dict):
|
|
|
|
|
|
|
+ def check_db(self, criteria_dict):
|
|
|
"""Check dictionary of critera against database.
|
|
"""Check dictionary of critera against database.
|
|
|
Currently accepts keys: MLS, title, address (street number/name, not city/state/zip).
|
|
Currently accepts keys: MLS, title, address (street number/name, not city/state/zip).
|
|
|
Returns True if records exists."""
|
|
Returns True if records exists."""
|
|
|
- if self.cursor: ## Check if DB is connected
|
|
|
|
|
- for criteria in criteria_dict:
|
|
|
|
|
|
|
+ if not self.cursor: ## Check if DB is connected
|
|
|
|
|
+ try:
|
|
|
|
|
+ self.connect_db()
|
|
|
|
|
+ logging.debug("No Database Connection. Connecting to DB in check_db function.")
|
|
|
|
|
+ except Exception as err:
|
|
|
|
|
+ print("Could not connect to Database. " + str(err))
|
|
|
|
|
+ logging.warning("Could not connect to Database. " + str(err))
|
|
|
|
|
+ return 0
|
|
|
|
|
+ for criteria in criteria_dict:
|
|
|
## Determine criteria passed, and execute queries for each
|
|
## Determine criteria passed, and execute queries for each
|
|
|
if criteria == 'MLS':
|
|
if criteria == 'MLS':
|
|
|
self.cursor.execute("SELECT COUNT(*) FROM properties WHERE MLS = %(MLS)s GROUP BY id",
|
|
self.cursor.execute("SELECT COUNT(*) FROM properties WHERE MLS = %(MLS)s GROUP BY id",
|
|
@@ -323,11 +331,9 @@ class MLSDATA:
|
|
|
if self.cursor.rowcount > 0: return self.cursor.rowcount # stop for loop if match already found.
|
|
if self.cursor.rowcount > 0: return self.cursor.rowcount # stop for loop if match already found.
|
|
|
else:
|
|
else:
|
|
|
print("Cannot search on parameter: " + criteria)
|
|
print("Cannot search on parameter: " + criteria)
|
|
|
- return self.cursor.rowcount
|
|
|
|
|
- else:
|
|
|
|
|
- print("Database is not connected or cursor not filled. Use function 'connectdb()' to establish")
|
|
|
|
|
|
|
+ return self.cursor.rowcount
|
|
|
|
|
|
|
|
- def getGoogle(self, property):
|
|
|
|
|
|
|
+ def get_google(self, property):
|
|
|
"""Supplies date from Google Distance Matrix API to populate
|
|
"""Supplies date from Google Distance Matrix API to populate
|
|
|
distance_to_work
|
|
distance_to_work
|
|
|
time_to_work
|
|
time_to_work
|
|
@@ -362,6 +368,16 @@ class MLSDATA:
|
|
|
|
|
|
|
|
def insertrecord(self, property, work_address=None, school_address=None):
|
|
def insertrecord(self, property, work_address=None, school_address=None):
|
|
|
"""Inserts record into database. Takes argument Property class object."""
|
|
"""Inserts record into database. Takes argument Property class object."""
|
|
|
|
|
+ if not self.cursor:
|
|
|
|
|
+ print("not self.cursor")
|
|
|
|
|
+ logging.debug("MYSQL connection not established. Trying to connect...")
|
|
|
|
|
+ try:
|
|
|
|
|
+ self.connect_db()
|
|
|
|
|
+ logging.debug("Connecting to DB in insertrecord fucntion.")
|
|
|
|
|
+ except Exception as err:
|
|
|
|
|
+ print("Could not connect to Database. " + str(err))
|
|
|
|
|
+ logging.warning("Could not connect to Database. " + str(err))
|
|
|
|
|
+ return
|
|
|
if self.cursor:
|
|
if self.cursor:
|
|
|
criteria_dict = property.__dict__
|
|
criteria_dict = property.__dict__
|
|
|
criteria_dict['Date_Added'] = str(datetime.date.today())
|
|
criteria_dict['Date_Added'] = str(datetime.date.today())
|
|
@@ -373,7 +389,7 @@ class MLSDATA:
|
|
|
self.cursor.execute(qry)
|
|
self.cursor.execute(qry)
|
|
|
self.cnx.commit()
|
|
self.cnx.commit()
|
|
|
print("Inserted " + criteria_dict['MLS'] + " | " + criteria_dict['address'] + " into database.")
|
|
print("Inserted " + criteria_dict['MLS'] + " | " + criteria_dict['address'] + " into database.")
|
|
|
- logging.debug("Inserted " + criteria_dict['MLS'] + " | " + criteria_dict['address'] + " into database.")
|
|
|
|
|
|
|
+ logging.info("Inserted " + criteria_dict['MLS'] + " | " + criteria_dict['address'] + " into database.")
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
print("Could not insert " + criteria_dict['address'] + " into database. Database connection error.")
|
|
print("Could not insert " + criteria_dict['address'] + " into database. Database connection error.")
|
|
|
logging.warning("Could not insert " + criteria_dict['address'] + "into database. Database connection "
|
|
logging.warning("Could not insert " + criteria_dict['address'] + "into database. Database connection "
|
|
@@ -381,27 +397,33 @@ class MLSDATA:
|
|
|
logging.warning(str(e))
|
|
logging.warning(str(e))
|
|
|
else:
|
|
else:
|
|
|
print("Database is not connected or cursor not filled. Use function 'connectdb()' to establish")
|
|
print("Database is not connected or cursor not filled. Use function 'connectdb()' to establish")
|
|
|
|
|
+ print(str(self.cursor))
|
|
|
|
|
|
|
|
- def connectdb(self, host='192.168.100.26', user='landsearchuser', password='1234', database='landsearch'):
|
|
|
|
|
|
|
+ def connect_db(self, host='192.168.100.26', user='landsearchuser', password='1234', database='landsearch'):
|
|
|
"""Connects to database and returns a cursor object"""
|
|
"""Connects to database and returns a cursor object"""
|
|
|
self.cnx = mysql.connector.connect(host=host, user=user, password=password, database=database, buffered=True)
|
|
self.cnx = mysql.connector.connect(host=host, user=user, password=password, database=database, buffered=True)
|
|
|
self.cursor = self.cnx.cursor()
|
|
self.cursor = self.cnx.cursor()
|
|
|
return self.cursor
|
|
return self.cursor
|
|
|
|
|
|
|
|
- def closedb(self):
|
|
|
|
|
|
|
+ def close_db(self):
|
|
|
"""Cleanly close the db."""
|
|
"""Cleanly close the db."""
|
|
|
self.cursor.close()
|
|
self.cursor.close()
|
|
|
self.cnx.close()
|
|
self.cnx.close()
|
|
|
|
|
|
|
|
- def dbinsert(self, properties: list):
|
|
|
|
|
|
|
+ def db_insert(self, properties: list):
|
|
|
"""Inserts records into database. Takes list of Property class objects"""
|
|
"""Inserts records into database. Takes list of Property class objects"""
|
|
|
if not properties == None:
|
|
if not properties == None:
|
|
|
if not isinstance(properties, list):
|
|
if not isinstance(properties, list):
|
|
|
raise TypeError('type list required')
|
|
raise TypeError('type list required')
|
|
|
for property in properties:
|
|
for property in properties:
|
|
|
- if not self.checkdb({'MLS': property.MLS, 'address': property.address}):
|
|
|
|
|
- if self.live_google: self.getGoogle(
|
|
|
|
|
|
|
+ if not self.check_db({'MLS': property.MLS, 'address': property.address}):
|
|
|
|
|
+ if self.live_google: self.get_google(
|
|
|
property) ## <- This will populate distance and time fields if set TRUE
|
|
property) ## <- This will populate distance and time fields if set TRUE
|
|
|
|
|
+ else:
|
|
|
|
|
+ print("NOT fetching google data. Suppressed by settings in landsearch.conf")
|
|
|
|
|
+ logging.warning("NOT fetching google data for " + property.address + ". Suppressed by "
|
|
|
|
|
+ "settings in "
|
|
|
|
|
+ "landsearch.conf")
|
|
|
self.insertrecord(property)
|
|
self.insertrecord(property)
|
|
|
self.new_listings.append(property)
|
|
self.new_listings.append(property)
|
|
|
else:
|
|
else:
|
|
@@ -414,9 +436,6 @@ class MLSDATA:
|
|
|
logging.info("Database Update Complete.")
|
|
logging.info("Database Update Complete.")
|
|
|
logging.info(str(len(self.new_listings)) + " new listings found.")
|
|
logging.info(str(len(self.new_listings)) + " new listings found.")
|
|
|
|
|
|
|
|
- def alerts(self):
|
|
|
|
|
- pass
|
|
|
|
|
-
|
|
|
|
|
def email_results(self):
|
|
def email_results(self):
|
|
|
global mymail
|
|
global mymail
|
|
|
sendto = ['stagl.mike@gmail.com', 'M_Stagl@hotmail.com']
|
|
sendto = ['stagl.mike@gmail.com', 'M_Stagl@hotmail.com']
|
|
@@ -424,10 +443,9 @@ class MLSDATA:
|
|
|
''' Send some kind of email! '''
|
|
''' Send some kind of email! '''
|
|
|
# If there are new listings, populate email ##
|
|
# If there are new listings, populate email ##
|
|
|
if len(self.new_listings) > 0:
|
|
if len(self.new_listings) > 0:
|
|
|
- logging.debug("email_results" + str(self.email))
|
|
|
|
|
body = ''
|
|
body = ''
|
|
|
data = []
|
|
data = []
|
|
|
- subj = "New Real Estate Listings for " + str(datetime.date.today())
|
|
|
|
|
|
|
+ subj = str(len(self.new_listings)) + " New Real Estate Listings for " + str(datetime.date.today())
|
|
|
for listing in self.new_listings:
|
|
for listing in self.new_listings:
|
|
|
row = []
|
|
row = []
|
|
|
body += listing.MLS + " | " + listing.address + " | " + listing.acres + " | " + listing.price + " | " + listing.link + "\n"
|
|
body += listing.MLS + " | " + listing.address + " | " + listing.acres + " | " + listing.price + " | " + listing.link + "\n"
|
|
@@ -464,6 +482,7 @@ class MLSDATA:
|
|
|
print("Suppressing email based on landsearch.conf preferences.")
|
|
print("Suppressing email based on landsearch.conf preferences.")
|
|
|
logging.warning("Suppressing email based on landsearch.conf preferences.")
|
|
logging.warning("Suppressing email based on landsearch.conf preferences.")
|
|
|
|
|
|
|
|
|
|
+
|
|
|
if __name__ == '__main__':
|
|
if __name__ == '__main__':
|
|
|
|
|
|
|
|
gmls = MLSDATA('GMLS') # Create MLSDATA object
|
|
gmls = MLSDATA('GMLS') # Create MLSDATA object
|
|
@@ -487,10 +506,8 @@ if __name__ == '__main__':
|
|
|
for listing in mydata:
|
|
for listing in mydata:
|
|
|
myresults.append(listing)
|
|
myresults.append(listing)
|
|
|
|
|
|
|
|
- # print(len(myresults))
|
|
|
|
|
- # print(myresults[0].address)
|
|
|
|
|
- gmls.connectdb()
|
|
|
|
|
- gmls.dbinsert(myresults)
|
|
|
|
|
- gmls.closedb()
|
|
|
|
|
|
|
+ #gmls.connectdb()
|
|
|
|
|
+ gmls.db_insert(myresults)
|
|
|
|
|
+ #gmls.closedb()
|
|
|
|
|
|
|
|
gmls.email_results()
|
|
gmls.email_results()
|