diff --git a/address_finder/api.py b/address_finder/api.py index b69a4c4..57015ff 100644 --- a/address_finder/api.py +++ b/address_finder/api.py @@ -28,28 +28,38 @@ class AddressFinder: self.dbPath = db_addresses_sqlite_path print("DB addresses Path : " + self.dbPath) - def getCommunesFromNameOrZip(self, communeNameOrZip: str) -> list[Commune]: + def getCommunesFromNameOrZip(self, communeNameOrZip: str, limit: int = None) -> list[Commune]: con = sqlite3.connect(self.dbPath) con.row_factory = sqlite3.Row cur = con.cursor() - communes: list[Commune] = [] + + # Check if a search limit is specified, make sure it is an integer + select_limit = "" + if limit is not None: + try: + select_limit = f"LIMIT {int(limit)}" + except ValueError: + print("Error, limit arg not a valid int: ", limit) try: + # If no search parameter, select all if communeNameOrZip is None: - cur.execute(f"SELECT * from \"{DB_TABLE_INSEE_NAME}\"") + cur.execute( + f"SELECT * from \"{DB_TABLE_INSEE_NAME}\" {select_limit}") else: communeSearch = communeNameOrZip zipSearch = communeNameOrZip searchOpertor = "OR" - regexCommuneAndZip = r"[0-9]{5} .+" # For example: '33000 BO' + # Allow search zip and commune at the same time, in the format "29530 PLO" + regexCommuneAndZip = r"[0-9]{5} .+" if re.match(regexCommuneAndZip, communeNameOrZip): splitSearch = communeNameOrZip.split(' ') zipSearch = splitSearch[0] communeSearch = ' '.join(splitSearch[1:]) searchOpertor = "AND" cur.execute( - f"SELECT * from \"{DB_TABLE_INSEE_NAME}\" WHERE {DB_COL_COMMUNE_NAME} LIKE \"%{communeSearch}%\" COLLATE nocase {searchOpertor} {DB_COL_COMMUNE_POSTE} LIKE \"{zipSearch}%\"") + f"SELECT * from \"{DB_TABLE_INSEE_NAME}\" WHERE {DB_COL_COMMUNE_NAME} LIKE \"%{communeSearch}%\" COLLATE nocase {searchOpertor} {DB_COL_COMMUNE_POSTE} LIKE \"{zipSearch}%\" {select_limit}") except sqlite3.OperationalError as err: print("Error querying DB : {0}".format(err), file=sys.stderr) return [] @@ -61,13 +71,13 @@ class AddressFinder: codeInsee=row_obj[DB_COL_COMMUNE_INSEE], nom=row_obj[DB_COL_COMMUNE_NAME], codeZip=row_obj[DB_COL_COMMUNE_POSTE]) - # This way we avoid duplicates + # This way we avoid duplicates in DB communesMap[commune["codeInsee"]] = commune con.close() return list(communesMap.values()) - def getCommuneFantoirVoies(self, communeInseeCode: str, voieSearch: str = None) -> list[FantoirVoie]: + def getCommuneFantoirVoies(self, communeInseeCode: str, voieSearch: str = None, limit: int = None) -> list[FantoirVoie]: # Extract data from DB con = sqlite3.connect(self.dbPath) @@ -84,6 +94,13 @@ class AddressFinder: # Get JSON payload + # Check if a search limit is specified, make sure it is an integer + if limit is not None: + try: + limit = int(limit) + except ValueError: + print("Error, limit arg not a valid int: ", limit) + fantoir_dict = [] # Check if data where found if data_raw is not None: @@ -92,19 +109,28 @@ class AddressFinder: data_dict = json.loads(data.get("value")) # In extracted JSON data, the interesting payload is behind "value" key fantoir_dict = data_dict.get("value") + + # Apply search filter if any if voieSearch is not None: + # Can match multiple words, for example "avenue noe" matches "avenue de noes" regexSearch = r".*" for expr in voieSearch.split(' '): regexSearch += r"(?=" + expr + r").*" regexSearch += r".*" fantoir_voies_filtered = [] + nb_match = 0 for voie in fantoir_dict: for libelle in voie['libelle']: if re.search(regexSearch, libelle, re.IGNORECASE): fantoir_voies_filtered.append(voie) + nb_match += 1 break - fantoir_dict = fantoir_voies_filtered - + if limit is not None and nb_match >= limit: + break + fantoir_dict = fantoir_voies_filtered + else: + # Apply search limit if any + fantoir_dict = fantoir_dict[:limit] else: print("Did not found any data matching Insee code " + str(communeInseeCode)) diff --git a/templates/landing_form.html b/templates/landing_form.html index 04699ea..c69641d 100644 --- a/templates/landing_form.html +++ b/templates/landing_form.html @@ -85,9 +85,9 @@ var voies=[] var voie="" function fillCommunes(search='') { - var api="addresses/communes"; + var api="addresses/communes?limit=15"; if (search != '') { - api+="?s="+search; + api+="&s="+search; } $.ajax({ type: 'GET', @@ -97,13 +97,10 @@ if (JSON.stringify(data) !== JSON.stringify(communes)){ $("#communes").empty(); communes=data - for (let i = 0; i < 20; i++) { - if (! communes[i]) { - break; - } - $("#communes").append(""); - } + communes.forEach(commune => { + $("#communes").append(""); + }); } if (communes.length == 1) { codeInsee=communes[0].codeInsee; @@ -119,9 +116,9 @@ var voies=[] function fillVoies(codeInsee,search='') { - var api="addresses/fantoirvoies/" + codeInsee; + var api="addresses/fantoirvoies/" + codeInsee + '?limit=15'; if (search != '') { - api+="?s="+search; + api+="&s="+search; } $.ajax({ type: 'GET', @@ -131,13 +128,10 @@ if (JSON.stringify(data) !== JSON.stringify(voies)){ $("#voies").empty(); voies=data - for (let i = 0; i < 20; i++) { - if (! voies[i]) { - break; - } - $("#voies").append(""); - } + voies.forEach(voie => { + $("#voies").append(""); + }); if (voies.length == 1) { $('#btnTestAdresse').collapse('show'); voie=voies[0].libelle[0] diff --git a/webapp.py b/webapp.py index 288be63..3a5ecdf 100644 --- a/webapp.py +++ b/webapp.py @@ -33,7 +33,8 @@ def get_form(): @app.route("/addresses/communes", methods=['GET']) def get_communes(): to_search=request.args.get('s') - communes=addressFinder.getCommunesFromNameOrZip(to_search) + limit=request.args.get('limit') + communes=addressFinder.getCommunesFromNameOrZip(to_search,limit) response = app.response_class( response=json.dumps(communes), mimetype='application/json' @@ -43,7 +44,8 @@ def get_communes(): @app.route("/addresses/fantoirvoies/", methods=['GET']) def get_fantoir_voies(codeInsee): to_search=request.args.get('s') - fantoirVoies=addressFinder.getCommuneFantoirVoies(codeInsee,to_search) + limit=request.args.get('limit') + fantoirVoies=addressFinder.getCommuneFantoirVoies(codeInsee,to_search,limit) response = app.response_class( response=json.dumps(fantoirVoies), mimetype='application/json'