|
@@ -28,7 +28,9 @@ from os import path
|
|
|
# Add database column to hold parcel number. Make links to GIS servers
|
|
# Add database column to hold parcel number. Make links to GIS servers
|
|
|
#
|
|
#
|
|
|
# IDENTIFY NEW PROPERTIES!!
|
|
# IDENTIFY NEW PROPERTIES!!
|
|
|
-
|
|
|
|
|
|
|
+#
|
|
|
|
|
+# Automate db opening and closing when calling dbinsert()
|
|
|
|
|
+#
|
|
|
#############
|
|
#############
|
|
|
|
|
|
|
|
class Property:
|
|
class Property:
|
|
@@ -62,26 +64,33 @@ class Search:
|
|
|
# def __init__(self, county: list, lower_price=0, upper_price=500000, \
|
|
# def __init__(self, county: list, lower_price=0, upper_price=500000, \
|
|
|
# lower_acres=5, upper_acres=15, type=['farm','land','home'], lower_sqft='', upper_sqft='', \
|
|
# lower_acres=5, upper_acres=15, type=['farm','land','home'], lower_sqft='', upper_sqft='', \
|
|
|
# lower_bedrooms='', upper_bedrooms=''):
|
|
# lower_bedrooms='', upper_bedrooms=''):
|
|
|
- def __init__(self):
|
|
|
|
|
|
|
+ def __init__(self, file = '../landsearch.conf'):
|
|
|
|
|
+ self.file = file
|
|
|
|
|
+ if not path.exists(self.file):
|
|
|
|
|
+ raise FileNotFoundError("The config file cannot be opened", self.file)
|
|
|
try:
|
|
try:
|
|
|
config = ConfigParser()
|
|
config = ConfigParser()
|
|
|
- if path.exists('../landsearch.conf'):
|
|
|
|
|
- config.read('../landsearch.conf')
|
|
|
|
|
- search_params = config['Search']
|
|
|
|
|
- else:
|
|
|
|
|
- raise FileNotFoundError("Config file $HOME/TopGunSoftware/landsearch.conf not found.")
|
|
|
|
|
|
|
+ config.read(self.file)
|
|
|
|
|
+ search_params = config['Search']
|
|
|
except FileNotFoundError as err:
|
|
except FileNotFoundError as err:
|
|
|
print(err, "Using default search parameters.")
|
|
print(err, "Using default search parameters.")
|
|
|
except Exception as err:
|
|
except Exception as err:
|
|
|
print(err, "Using default search parameters.")
|
|
print(err, "Using default search parameters.")
|
|
|
|
|
|
|
|
|
|
+ county = search_params.get('county', ['Gwinnett', 'Hall', 'Jackson', 'Walton', 'Barrow'])
|
|
|
|
|
+ if isinstance(county, str):
|
|
|
|
|
+ county = county.split(", ")
|
|
|
|
|
+ type = search_params.get('type', ['farm', 'house', 'land'])
|
|
|
|
|
+ if isinstance(type, str):
|
|
|
|
|
+ type = type.split(", ")
|
|
|
|
|
+
|
|
|
self.types=['land', 'farm', 'home', 'house']
|
|
self.types=['land', 'farm', 'home', 'house']
|
|
|
- self.county = search_params.get('county', ['Gwinnett', 'Hall', 'Jackson', 'Walton', 'Barrow'])
|
|
|
|
|
|
|
+ self.county = county
|
|
|
self.lower_price = search_params.get('lower_price', 0)
|
|
self.lower_price = search_params.get('lower_price', 0)
|
|
|
self.upper_price = search_params.get('upper_price', 525000)
|
|
self.upper_price = search_params.get('upper_price', 525000)
|
|
|
self.lower_acres = search_params.get('lower_acres', 5)
|
|
self.lower_acres = search_params.get('lower_acres', 5)
|
|
|
self.upper_acres = search_params.get('upper_acres', 15)
|
|
self.upper_acres = search_params.get('upper_acres', 15)
|
|
|
- self.type = search_params.get('type', ['farm', 'house', 'land']) ##accept list!
|
|
|
|
|
|
|
+ self.type = type ##accept list!
|
|
|
self.lower_sqft = search_params.get('lower_sqft', '')
|
|
self.lower_sqft = search_params.get('lower_sqft', '')
|
|
|
self.upper_sqft = search_params.get('upper_sqft', '')
|
|
self.upper_sqft = search_params.get('upper_sqft', '')
|
|
|
self.lower_bedrooms = search_params.get('lower_bedrooms', '')
|
|
self.lower_bedrooms = search_params.get('lower_bedrooms', '')
|
|
@@ -91,7 +100,7 @@ class Search:
|
|
|
assert property_type in self.types, ("Unknown type '" + property_type + "'. Property Type must be of type: " + str(self.types))
|
|
assert property_type in self.types, ("Unknown type '" + property_type + "'. Property Type must be of type: " + str(self.types))
|
|
|
|
|
|
|
|
## FOR TESTING, PRINT ALL ATTRIBUTES OF SEARCH ##
|
|
## FOR TESTING, PRINT ALL ATTRIBUTES OF SEARCH ##
|
|
|
- print(vars(self))
|
|
|
|
|
|
|
+# print(vars(self))
|
|
|
|
|
|
|
|
|
|
|
|
|
class ImproperSearchError(Exception):
|
|
class ImproperSearchError(Exception):
|
|
@@ -114,12 +123,12 @@ class MLSDATA:
|
|
|
self.cnx = ''
|
|
self.cnx = ''
|
|
|
self.new_listings = []
|
|
self.new_listings = []
|
|
|
|
|
|
|
|
- def stringbuilder(self, search: Search):
|
|
|
|
|
|
|
+ def stringbuilder(self, search: Search, county):
|
|
|
""" Takes Search class and build appropriate URL query based on mlstype. Currently only supports gmls."""
|
|
""" Takes Search class and build appropriate URL query based on mlstype. Currently only supports gmls."""
|
|
|
|
|
|
|
|
if self.mlstype == 'gmls':
|
|
if self.mlstype == 'gmls':
|
|
|
base_addr = 'https://www.georgiamls.com/real-estate/search-action.cfm?'
|
|
base_addr = 'https://www.georgiamls.com/real-estate/search-action.cfm?'
|
|
|
- params = [('cnty', search.county), \
|
|
|
|
|
|
|
+ params = [('cnty', county), \
|
|
|
('lpl', search.lower_price), ('lph', search.upper_price), \
|
|
('lpl', search.lower_price), ('lph', search.upper_price), \
|
|
|
('acresL', search.lower_acres), ('acresH', search.upper_acres), \
|
|
('acresL', search.lower_acres), ('acresH', search.upper_acres), \
|
|
|
('orderBy', 'b'), \
|
|
('orderBy', 'b'), \
|
|
@@ -226,25 +235,24 @@ class MLSDATA:
|
|
|
|
|
|
|
|
return properties_list
|
|
return properties_list
|
|
|
|
|
|
|
|
- def getmlsdata(self, search: Search):
|
|
|
|
|
|
|
+ def getmlsdata(self, search: Search, county):
|
|
|
"""This is the main entrypoint. Takes arguments to pass to stringbuilder to create the URL.
|
|
"""This is the main entrypoint. Takes arguments to pass to stringbuilder to create the URL.
|
|
|
Selects appropriate parser based on self.mlstype from class intance.
|
|
Selects appropriate parser based on self.mlstype from class intance.
|
|
|
Needs any modifications from the standard search ($0 to $500,000, 5 to 15 acres, etc)
|
|
Needs any modifications from the standard search ($0 to $500,000, 5 to 15 acres, etc)
|
|
|
See class search for more information.
|
|
See class search for more information.
|
|
|
--> 9/1/20 - takes Search class as argument. All properties are handled by the class <--"""
|
|
--> 9/1/20 - takes Search class as argument. All properties are handled by the class <--"""
|
|
|
if isinstance(search, Search):
|
|
if isinstance(search, Search):
|
|
|
- print(search.county)
|
|
|
|
|
|
|
|
|
|
##
|
|
##
|
|
|
-# PROGRAM BREAKS HERE
|
|
|
|
|
|
|
+# PROGRAM BREAKS HERE - Used to loop for each county, not Search class contains list of counties. Need to automate looping.
|
|
|
##
|
|
##
|
|
|
|
|
|
|
|
- if not search.county in self.counties: ### FIX for lower()
|
|
|
|
|
- print("County " + search.county + " not regognized. Exiting")
|
|
|
|
|
|
|
+ if not county in self.counties: ### FIX for lower()
|
|
|
|
|
+ print("County " + county + " not regognized. Exiting")
|
|
|
else:
|
|
else:
|
|
|
- print("Scanning for results in " + search.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), search.county)
|
|
|
|
|
|
|
+ list = self.gmlsparser(self.stringbuilder(search, county), county)
|
|
|
return list
|
|
return list
|
|
|
else:
|
|
else:
|
|
|
raise ImproperSearchError(search)
|
|
raise ImproperSearchError(search)
|
|
@@ -398,20 +406,34 @@ The following properties have been found which may be of interest.\n
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
if __name__ == '__main__':
|
|
|
-# gmls = MLSDATA('GMLS')
|
|
|
|
|
-# Search()
|
|
|
|
|
-
|
|
|
|
|
- gmls = MLSDATA('GMLS')
|
|
|
|
|
-
|
|
|
|
|
- #new_properties = []
|
|
|
|
|
-
|
|
|
|
|
- for county in ['Walton']: ### FIX
|
|
|
|
|
- mysearch = Search() ### FIX
|
|
|
|
|
- mydata = gmls.getmlsdata(mysearch)
|
|
|
|
|
|
|
|
|
|
- gmls.connectdb()
|
|
|
|
|
- gmls.dbinsert(mydata)
|
|
|
|
|
- gmls.closedb()
|
|
|
|
|
|
|
+ gmls = MLSDATA('GMLS') # Create MLSDATA object
|
|
|
|
|
+
|
|
|
|
|
+ mysearch = Search() # Create a custom search object
|
|
|
|
|
+# print(len(mysearch.county))
|
|
|
|
|
+# print(mysearch.county[0])
|
|
|
|
|
+ myresults = []
|
|
|
|
|
+
|
|
|
|
|
+## Create function in MLSDATA module:
|
|
|
|
|
+# - takes counties from configparser and calls getmlsdata for each county.
|
|
|
|
|
+# - Compiles results into single list and returns that list
|
|
|
|
|
+# - User code would look something like this:
|
|
|
|
|
+# _ mysearch = Search()
|
|
|
|
|
+# _ mydata = gmls.findalllistings(mysearch) # This would control the looping of counties and return a list like normal
|
|
|
|
|
+# _ gmls.dbinsert(myresults) # This would automate db opening and closing
|
|
|
|
|
+ for county in mysearch.county:
|
|
|
|
|
+ print("local search: ", county)
|
|
|
|
|
+ mysearch = Search() ## Search used to take county as parameter, so this loop would work. Now Search class contains list. loop must occur in getmlsdata module
|
|
|
|
|
+ mydata = gmls.getmlsdata(mysearch, county)
|
|
|
|
|
+
|
|
|
|
|
+ for listing in mydata:
|
|
|
|
|
+ myresults.append(listing)
|
|
|
|
|
+
|
|
|
|
|
+# print(len(myresults))
|
|
|
|
|
+# print(myresults[0].address)
|
|
|
|
|
+ gmls.connectdb()
|
|
|
|
|
+ gmls.dbinsert(myresults)
|
|
|
|
|
+ gmls.closedb()
|
|
|
#
|
|
#
|
|
|
# gmls.email()
|
|
# gmls.email()
|
|
|
#
|
|
#
|