def _clean_address(self): from common.geocode import geocode if "address" in self.initial and self.cleaned_data["address"] == self.initial["address"]: return self.initial["address"] result = None geocode_str = u"%s %s" % (self.cleaned_data["city"], self.cleaned_data["address"]) geocode_results = geocode(geocode_str) if len(geocode_results) < 1: raise ValidationError("Could not resolve address") elif len(geocode_results) > 1: address_options = [] for res in geocode_results: address = "%s %s" % (res["street_address"], res["house_number"]) address_options.append(address) if address == self.cleaned_data["address"]: result = res break if not result: raise ValidationError("Please choose one: %s" % ", ".join(address_options)) else: result = geocode_results[0] self.instance.lon = result["lon"] self.instance.lat = result["lat"] self.instance.geohash = result["geohash"] # self.instance.save() self.cleaned_data["address"] = "%s %s" % (result["street_address"], result["house_number"]) return self.cleaned_data["address"]
def transliterate_english_order_to_hebrew(order, address_type): """ Translate English order to Hebrew by transliterating the street name and geocoding the result, looking for a match. If no match is found, the transliterated result is returned. """ street_address = getattr(order, "%s_street_address" % address_type) house_number = getattr( order, "%s_house_number" % address_type ) # or int(re.search("\d+", getattr(order, "%s_raw" % address_type)).group(0)) city = getattr(order, "%s_city" % address_type) lat = getattr(order, "%s_lat" % address_type) lon = getattr(order, "%s_lon" % address_type) # transliterate English street name to Hebrew logging.info("queying google transliteration for %s" % street_address) hebrew_transliterator = Transliteration('iw') hebrew_street_address = hebrew_transliterator.getTransliteration( street_address.replace("'", "")) logging.info("transliteration returned %s" % hebrew_street_address) hebrew_address = hebrew_street_address if house_number: hebrew_address = u"%s %d" % (hebrew_street_address, house_number) # geocode transliterated address, and look for one with matching lon, lat results = geocode(hebrew_address, constrain_to_city=city) for result in results: if float(result["lat"]) == lat and float(result["lon"]) == lon: logging.info("telmap found a match") return u"%s %s, %s" % (result["street_address"], result["house_number"], result["city"]) # try again without constrain to city hebrew_address = u"%s, %s" % (hebrew_address, city.name if city else "") results = geocode(hebrew_address) for result in results: if float(result["lat"]) == lat and float(result["lon"]) == lon: logging.info("telmap found a match") return u"%s %s, %s" % (result["street_address"], result["house_number"], result["city"]) logging.info("telmap DID NOT find a match") return order.from_raw
def resolve_structured_address(request): city = get_object_or_404(City, id=request.GET.get("city_id")) street = request.GET.get("street", "") house_number = request.GET.get("house_number", "") address = "%s %s, %s" % (street.strip(), house_number.strip(), city.name) geocoding_results = geocode(address, resolve_to_ids=True) errors = None if not geocoding_results: # try replacing house number address = "%s %s, %s" % (street.strip(), 1, city.name) geocoding_results = geocode(address, resolve_to_ids=True) if geocoding_results: errors = {"house_number": _("Not found")} else: errors = {"street": _("Not found"), "house_number": _("Not found")} return JSONResponse({'geocoding_results': geocoding_results, 'errors': errors})
def test_well_formed_request(requests_mock): settings.GEOCODIO_KEY = "foobar" APIClient() geocodio_call = requests_mock.register_uri( "GET", API_ENDPOINT, json={ "input": { "address_components": {"zip": "90024", "country": "US"}, "formatted_address": "90024", }, "results": [ { "address_components": { "city": "Los Angeles", "county": "Los Angeles County", "state": "CA", "zip": "90024", "country": "US", }, "formatted_address": "Los Angeles, CA 90024", "location": {"lat": 34.065729, "lng": -118.434999}, "accuracy": 1, "accuracy_type": "place", "source": "TIGER\/Line\u00ae dataset from the US Census Bureau", } ], }, ) r = geocode(q="90024") assert geocodio_call.called assert geocodio_call.last_request.qs == { "api_key": [settings.GEOCODIO_KEY], "q": ["90024"], } assert r == [ { "address_components": { "city": "Los Angeles", "county": "Los Angeles County", "state": "CA", "zip": "90024", "country": "US", }, "formatted_address": "Los Angeles, CA 90024", "location": {"lat": 34.065729, "lng": -118.434999}, "accuracy": 1, "accuracy_type": "place", "source": "TIGER\/Line\u00ae dataset from the US Census Bureau", } ]
def resolve_address(request, station): try: city = City.objects.filter(id=request.GET.get('city_id', '-1')).get() except City.DoesNotExist: return HttpResponseBadRequest(_("Invalid city")) address = request.GET.get('address', None) if not address: return JSONResponse('') return JSONResponse(geocode(address, constrain_to_city=city))
def resolve_address(request): # get parameters if not ADDRESS_PARAMETER in request.GET: return HttpResponseBadRequest("Missing address") address = request.GET[ADDRESS_PARAMETER] lon = request.GET.get("lon", None) lat = request.GET.get("lat", None) include_order_history = request.GET.get("include_order_history", True) fixed_address = fix_address(address, lon, lat) size = request.GET.get(MAX_SIZE_PARAMETER) or DEFAULT_RESULT_MAX_SIZE try: size = int(size) except: return HttpResponseBadRequest("Invalid value for max_size") geocoding_results = geocode(fixed_address, max_size=size, resolve_to_ids=True) history_results = [] if include_order_history: passenger = Passenger.from_request(request) if passenger: history_results.extend([ get_results_from_order(o, "from") for o in passenger.orders.filter(from_raw__icontains=address) ]) history_results.extend([ get_results_from_order(o, "to") for o in passenger.orders.filter(to_raw__icontains=address) ]) history_results_by_name = {} for result in history_results: history_results_by_name[result["name"]] = result history_results = history_results_by_name.values() # remove duplicate results history_results_names = [ result_name for result_name in history_results_by_name ] for result in geocoding_results: if result['name'] in history_results_names: geocoding_results.remove(result) return JSONResponse({ "geocode": geocoding_results[:size], "history": history_results[:size], "geocode_label": "map_suggestion", "history_label": "history_suggestion" })
def resolve_structured_address(request): city = get_object_or_404(City, id=request.GET.get("city_id")) street = request.GET.get("street", "") house_number = request.GET.get("house_number", "") address = "%s %s, %s" % (street.strip(), house_number.strip(), city.name) geocoding_results = geocode(address, resolve_to_ids=True) errors = None if not geocoding_results: # try replacing house number address = "%s %s, %s" % (street.strip(), 1, city.name) geocoding_results = geocode(address, resolve_to_ids=True) if geocoding_results: errors = {"house_number": _("Not found")} else: errors = {"street": _("Not found"), "house_number": _("Not found")} return JSONResponse({ 'geocoding_results': geocoding_results, 'errors': errors })
def resolve_address(request): # get parameters if not ADDRESS_PARAMETER in request.GET: return HttpResponseBadRequest("Missing address") address = request.GET[ADDRESS_PARAMETER] lon = request.GET.get("lon", None) lat = request.GET.get("lat", None) include_order_history = request.GET.get("include_order_history", True) fixed_address = fix_address(address, lon, lat) size = request.GET.get(MAX_SIZE_PARAMETER) or DEFAULT_RESULT_MAX_SIZE try: size = int(size) except: return HttpResponseBadRequest("Invalid value for max_size") geocoding_results = geocode(fixed_address, max_size=size, resolve_to_ids=True) history_results = [] if include_order_history: passenger = Passenger.from_request(request) if passenger: history_results.extend( [get_results_from_order(o, "from") for o in passenger.orders.filter(from_raw__icontains=address)] ) history_results.extend( [get_results_from_order(o, "to") for o in passenger.orders.filter(to_raw__icontains=address)] ) history_results_by_name = {} for result in history_results: history_results_by_name[result["name"]] = result history_results = history_results_by_name.values() # remove duplicate results history_results_names = [result_name for result_name in history_results_by_name] for result in geocoding_results: if result["name"] in history_results_names: geocoding_results.remove(result) return JSONResponse( { "geocode": geocoding_results[:size], "history": history_results[:size], "geocode_label": "map_suggestion", "history_label": "history_suggestion", } )
def resolve_lat_lon_or_default(city, street_address, house_number): lat = -1 lon = -1 street_address = street_address.strip() house_number = house_number.strip() address = "%s %s, %s" % (street_address, house_number, city.name) geocoding_results = geocode(address, resolve_to_ids=True) for result in geocoding_results: # make sure it is a match and not a suggestion if result["street_address"] == street_address and result["house_number"] == house_number: lat = result["lat"] lon = result["lon"] break return lat, lon
def resolve_lat_lon_or_default(city, street_address, house_number): lat = -1 lon = -1 street_address = street_address.strip() house_number = house_number.strip() address = "%s %s, %s" % (street_address, house_number, city.name) geocoding_results = geocode(address, resolve_to_ids=True) for result in geocoding_results: # make sure it is a match and not a suggestion if result["street_address"] == street_address and result[ "house_number"] == house_number: lat = result["lat"] lon = result["lon"] break return lat, lon
def lookup_ga( item: Union[BallotRequest, Registration, Lookup, ReminderRequest] ) -> Tuple[str, Dict[str, str]]: from ovrlib import ga from common.geocode import geocode # geocode to a county addrs = geocode( street=item.address1, city=item.city, state="GA", zipcode=item.zipcode, ) if not addrs: logger.warning( f"Unable to geocode {item} ({item.address1}, {item.city}, GA {item.zipcode})" ) return None, None county = None for addr in addrs: if addr.get("address_components", {}).get("state") != "GA": continue county = addr.get("address_components", {}).get("county", "").upper() if county.endswith(" COUNTY"): county = county[0:-7] if county: break if not county: logger.warning(f"Unable to geocode county for {item}: addrs {addrs}") return None, None proxy, proxy_str = get_random_proxy_str_pair() logger.debug(f"lookup up GA {item} with proxy_str {proxy_str}") voter = ga.lookup_voter( first_name=item.first_name, last_name=item.last_name, date_of_birth=item.date_of_birth, county=county, proxies={"https": proxy_str}, ) if not voter: return None, None logger.info(voter) return voter.voter_reg_number, voter
def geocode_to_regions(street, city, state, zipcode): addrs = geocode( street=street, city=city, state=state, zipcode=zipcode, fields="stateleg", ) if not addrs: logger.warning( f"address: Unable to geocode ({street}, {city}, {state} {zipcode})" ) statsd.increment("turnout.official.address.failed_geocode") return None # use the first/best match addr = addrs[0] county = addr["address_components"].get("county") if not county: return None city = addr["address_components"].get("city") location = Point(addr["location"]["lng"], addr["location"]["lat"]) state_code = addr["address_components"].get("state") if state_code != state: # if the address match gets the state wrong, return *no* result logger.warning( f"User-provided state {state} does not match geocoded state in {addr['address_components']}" ) return None return match_region( city, county, state, location=location, districts=addr.get("fields", {}).get("state_legislative_districts", {}), )
def _clean_address(self): from common.geocode import geocode if "address" in self.initial and self.cleaned_data[ "address"] == self.initial["address"]: return self.initial["address"] result = None geocode_str = u"%s %s" % (self.cleaned_data["city"], self.cleaned_data["address"]) geocode_results = geocode(geocode_str) if len(geocode_results) < 1: raise ValidationError("Could not resolve address") elif len(geocode_results) > 1: address_options = [] for res in geocode_results: address = "%s %s" % (res["street_address"], res["house_number"]) address_options.append(address) if address == self.cleaned_data["address"]: result = res break if not result: raise ValidationError("Please choose one: %s" % ", ".join(address_options)) else: result = geocode_results[0] self.instance.lon = result["lon"] self.instance.lat = result["lat"] self.instance.geohash = result["geohash"] # self.instance.save() self.cleaned_data["address"] = "%s %s" % (result["street_address"], result["house_number"]) return self.cleaned_data["address"]
def test_geo_coding(self): """ Check that geocoding returns at least one location with correct geocode, i.e., country, city, street, house number, lon. and lat. are matching those of the query. """ test_data = ( { "address": u"דיזנגוף 99 תל אביב", "country": u"IL", "city": u"תל אביב יפו", "street_address": u"דיזנגוף", "house_number": u"99", "lon": '34.77388', "lat": '32.07933', }, { "address": u"מרג 1 תל אביב יפו", "country": u"IL", "city": u"תל אביב יפו", "street_address": u"מרגולין", "house_number": u"1", "lon": '34.787368', "lat": '32.05856', }, { "address": u"בן יהודה 35 ירושלים", "country": u"IL", "city": u"ירושלים", "street_address": u"אליעזר וחמדה בן יהודה", "house_number": u"35", "lon": '35.214161', "lat": '31.780725', }, ) logging.info("\nTesting geo coding") for test_case in test_data: test_case_success = False address = test_case["address"] logging.info("Testing geo coding for %s" % address) geo_code = geocode(address) self.assertTrue(geo_code, msg="no geo code received for %s" % address) # geo_code may contain more than one location. Check that at least one is correct. for location in geo_code: location_success = True logging.info("Processing location %s" % location["description"]) # textual properties, compare lowercase for property in [ "country", "city", "street_address", "house_number" ]: result = "OK" if not test_case[property].lower( ) == location[property].lower(): result = "failed" location_success = False #uncomment for debug since all Django tests run with DEBUG=False #logging.info("comparing %s: %s" % (property, result)) # numerical properties, allowed to differ a bit. precision = 0.001 result = "OK" for property in ["lon", "lat"]: if not abs( float(test_case[property]) - float(location[property])) < precision: result = "failed" location_success = False #logging.info("comparing %s with precision %f: %s" % (property, precision, result)) if location_success: logging.info("Found correct location at %s" % location["description"]) test_case_success = True break self.assertTrue(test_case_success, msg="correct geo code was not found for %s" % address)
def scrape_offices(session: requests.Session, regions: Sequence[Region]) -> None: existing_region_ids = [region.external_id for region in regions] existing_offices = Office.objects.values_list("external_id", flat=True) offices_dict: Dict[(int, Tuple[Action, Office])] = {} existing_addresses = {a.external_id: a for a in Address.objects.all()} addresses_dict: Dict[(int, Tuple[Action, Address])] = {} # The USVF API pagination is buggy; make multiple passes with # different page sizes. for limit in [100, 73, 67]: next_url = f"{API_ENDPOINT}/offices?limit={limit}" while next_url: with statsd.timed("turnout.official.usvfcall.offices", sample_rate=0.2): result = acquire_data(session, next_url) for office in result["objects"]: # Check that the region is valid (we don't support US territories) region_id = int(office["region"].rsplit("/", 1)[1]) if region_id not in existing_region_ids: continue office_id = office["id"] if office_id in offices_dict: continue # Process each office in the response if office_id in existing_offices: office_action = Action.UPDATE else: office_action = Action.INSERT offices_dict[office_id] = ( office_action, Office( external_id=office_id, region_id=int(office["region"].split("/")[-1]), hours=office.get("hours"), notes=office.get("notes"), ), ) for address in office.get("addresses", []): # Process each address in the office existing = existing_addresses.get(address["id"], None) if existing: address_action = Action.UPDATE location = existing.location else: address_action = Action.INSERT location = None if not location and settings.USVF_GEOCODE: addrs = geocode( street=address.get("street1"), city=address.get("city"), state=address.get("state"), zipcode=address.get("zip"), ) if addrs: location = Point( addrs[0]["location"]["lng"], addrs[0]["location"]["lat"] ) addresses_dict[address["id"]] = ( address_action, Address( external_id=address["id"], office_id=office["id"], address=address.get("address_to"), address2=address.get("street1"), address3=address.get("street2"), city=address.get("city"), state_id=address.get("state"), zipcode=address.get("zip"), website=address.get("website"), email=address.get("main_email"), phone=address.get("main_phone_number"), fax=address.get("main_fax_number"), location=location, is_physical=address.get("is_physical"), is_regular_mail=address.get("is_regular_mail"), process_domestic_registrations="DOM_VR" in address["functions"], process_absentee_requests="DOM_REQ" in address["functions"], process_absentee_ballots="DOM_RET" in address["functions"], process_overseas_requests="OVS_REQ" in address["functions"], process_overseas_ballots="OVS_RET" in address["functions"], ), ) next_url = result["meta"].get("next") logger.info( "Found %(number)s offices after this pass", {"number": len(offices_dict)} ) statsd.gauge("turnout.official.scraper.offices", len(offices_dict)) logger.info("Found %(number)s Offices", {"number": len(offices_dict)}) statsd.gauge("turnout.official.scraper.addresses", len(addresses_dict)) logger.info("Found %(number)s Addresses", {"number": len(addresses_dict)}) # Remove any records in our database but not in the result Office.objects.exclude(external_id__in=offices_dict.keys()).delete() Address.objects.exclude(external_id__in=addresses_dict.keys()).delete() # Create any records that are not already in our database Office.objects.bulk_create( [x[1] for x in offices_dict.values() if x[0] == Action.INSERT] ) Address.objects.bulk_create( [x[1] for x in addresses_dict.values() if x[0] == Action.INSERT] ) # Update any records that are already in our database def slow_bulk_update(cls, pk, records, keys): # this is slower than django's bulk_update(), but it (1) # respects modified_at and (2) works on aurora for r in records: obj = cls.objects.get(pk=r.pk) changed = False for k in keys: if getattr(obj, k) != getattr(r, k): setattr(obj, k, getattr(r, k)) changed = True if changed: logger.info(f"Updated {obj}") obj.save() slow_bulk_update( Office, "external_id", [x[1] for x in offices_dict.values() if x[0] == Action.UPDATE], ["hours", "notes"], ) slow_bulk_update( Address, "external_id", [x[1] for x in addresses_dict.values() if x[0] == Action.UPDATE], [ "address", "address2", "address3", "city", "state", "zipcode", "website", "email", "phone", "fax", "is_physical", "is_regular_mail", "location", "process_domestic_registrations", "process_absentee_requests", "process_absentee_ballots", "process_overseas_requests", "process_overseas_ballots", ], )
def test_geo_coding(self): """ Check that geocoding returns at least one location with correct geocode, i.e., country, city, street, house number, lon. and lat. are matching those of the query. """ test_data = ( {"address" : u"דיזנגוף 99 תל אביב", "country" : u"IL", "city" : u"תל אביב יפו", "street_address" : u"דיזנגוף", "house_number" : u"99", "lon" : '34.77388', "lat" : '32.07933', }, {"address" : u"מרג 1 תל אביב יפו", "country" : u"IL", "city" : u"תל אביב יפו", "street_address" : u"מרגולין", "house_number" : u"1", "lon" : '34.787368', "lat" : '32.05856', }, {"address" : u"בן יהודה 35 ירושלים", "country" : u"IL", "city" : u"ירושלים", "street_address" : u"אליעזר וחמדה בן יהודה", "house_number" : u"35", "lon" : '35.214161', "lat" : '31.780725', }, ) logging.info("\nTesting geo coding") for test_case in test_data: test_case_success = False address = test_case["address"] logging.info("Testing geo coding for %s" % address) geo_code = geocode(address) self.assertTrue(geo_code, msg="no geo code received for %s" % address) # geo_code may contain more than one location. Check that at least one is correct. for location in geo_code: location_success = True logging.info("Processing location %s" % location["description"]) # textual properties, compare lowercase for property in ["country", "city", "street_address", "house_number"]: result = "OK" if not test_case[property].lower() == location[property].lower(): result = "failed" location_success = False #uncomment for debug since all Django tests run with DEBUG=False #logging.info("comparing %s: %s" % (property, result)) # numerical properties, allowed to differ a bit. precision = 0.001 result = "OK" for property in ["lon", "lat"]: if not abs(float(test_case[property]) - float(location[property])) < precision: result = "failed" location_success = False #logging.info("comparing %s with precision %f: %s" % (property, precision, result)) if location_success: logging.info("Found correct location at %s" % location["description"]) test_case_success = True break self.assertTrue(test_case_success, msg="correct geo code was not found for %s" % address)
def send_map_mms( number: Number, map_type: str, # 'pp' or 'ev' address_full: str, content: str = None, blast: Blast = None, ) -> Optional[str]: formdata: Dict[str, str] = {} # geocode home home = geocode(q=address_full) home_address = address_full if not home: logger.info(f"{number}: Failed to geocode {address_full}") return f"Failed to geocode {address_full}" formdata["home_address_short"] = address_full.split(",")[0].upper() home_loc = f"{home[0]['location']['lng']},{home[0]['location']['lat']}" # geocode destination if get_feature_bool("locate_use_dnc_data", home[0]["address_components"]["state"]): # pollproxy with tracer.trace("pollproxy.lookup", service="pollproxy"): response = requests.get(DNC_API_ENDPOINT, {"address": home_address}) if response.status_code != 200: logger.warning( f"Got {response.status_code} from dnc query on {home_address}") return f"Failure querying data source" if map_type == "pp": dest = response.json().get("data", {}).get("election_day_locations", []) if not dest: logger.info( f"{number}: No election day location for {address_full}: {response.json()}" ) return f"No election day location for {address_full}" dest = dest[0] elif map_type == "ev": dest = response.json().get("data", {}).get("early_vote_locations", []) if not dest: logger.info( f"{number}: No early_vote location for {address_full}") return f"No early vote location for {address_full}" dest = dest[0] else: return f"Unrecognized address type {map_type}" dest_name = dest["location_name"] dest_address = ( f"{dest['address_line_1']}, {dest['city']}, {dest['state']} {dest['zip']}" ) dest_hours = dest["dates_hours"] if "lon" not in dest or "lat" not in dest: logger.warning( f"Lon/lat missing from {dest_address} (pollproxy): {dest}") return f"Lon/lat missing from {dest_address} (pollproxy)" dest_lon = dest["lon"] dest_lat = dest["lat"] else: # civic with tracer.trace("civicapi.voterlookup", service="google"): response = requests.get( CIVIC_API_ENDPOINT, { "address": home_address, "electionId": 7000, "key": settings.CIVIC_KEY, }, ) if response.status_code != 200: logger.warning( f"Got {response.status_code} from civic query on {home_address}" ) return f"Failure querying data source" if map_type == "pp": dest = response.json().get("pollingLocations", []) if not dest: logger.info( f"{number}: No election day location for {address_full}: {dest}" ) return f"No election day location for {address_full}" dest = dest[0] elif map_type == "ev": dest = response.json().get("earlyVoteSites", []) if not dest: logger.info( f"{number}: No early_vote location for {address_full}") return f"No early vote location for {address_full}" dest = dest[0] else: return f"Unrecognized address type {map_type}" dest_name = dest["address"]["locationName"] dest_address = f"{dest['address']['line1']}, {dest['address']['city']}, {dest['address']['state']} {dest['address']['zip']}" dest_hours = dest["pollingHours"] if "longitude" not in dest or "latitude" not in dest: logger.warning( f"Lon/lat missing from {dest_address} (pollproxy): {dest}") return f"Lon/lat missing from {dest_address} (civic)" dest_lon = dest["longitude"] dest_lat = dest["latitude"] formdata["dest_name"] = dest_name.upper() formdata["dest_address"] = dest_address.upper() formdata["dest_hours"] = dest_hours # Pick a reasonable zoom level for the map, since mapbox pushes # the markers to the very edge of the map. # # mapbox zoom levels are 1-20, and go by power of 2: +1 zoom means # 1/4 of the map area. dx = abs(float(dest_lon) - float(home[0]["location"]["lng"])) dy = abs(float(dest_lat) - float(home[0]["location"]["lat"])) if dx > dy: d = dx else: d = dy logd = math.log2(1.0 / d) zoom = logd + 7.5 if HIRES: size = "512x512" else: size = "256x256" zoom -= 1.0 centerx = (float(dest_lon) + float(home[0]["location"]["lng"])) / 2 centery = (float(dest_lat) + float(home[0]["location"]["lat"])) / 2 map_loc = f"{centerx},{centery},{zoom}" # fetch map map_url = f"https://api.mapbox.com/styles/v1/mapbox/streets-v11/static/pin-s-home+00f({home_loc}),pin-s-p+f00({dest_lon},{dest_lat})/{map_loc}/{size}?access_token={settings.MAPBOX_KEY}" response = requests.get(map_url) if response.status_code != 200: logger.warning( f"{number}: Failed to fetch map, got status code {response.status_code}" ) return "Failed to fetch map" map_image = response.content map_image_type = response.headers["content-type"] # store map in s3 filename = str(uuid.uuid4()) + "." + map_image_type.split("/")[-1] upload = s3_client.put_object( Bucket=settings.MMS_ATTACHMENT_BUCKET, Key=filename, ContentType=map_image_type, ACL="public-read", Body=map_image, ) if upload.get("ResponseMetadata", {}).get("HTTPStatusCode") != 200: logger.warning( f"{number}: Unable to push {filename} to {settings.MMS_ATTACHMENT_BUCKET}" ) return "Unable to upload map" stored_map_url = ( f"https://{settings.MMS_ATTACHMENT_BUCKET}.s3.amazonaws.com/{filename}" ) # locator link locator_url = f"https://www.voteamerica.com/where-to-vote/?{urlencode({'address':home_address})}" if blast: locator_url += f"&utm_medium=mms&utm_source=turnout&utm_campaign={blast.campaign}&source=va_mms_turnout_{blast.campaign}" formdata["locator_url"] = shorten_url(locator_url) # send number.send_sms( content.format(**formdata), media_url=[stored_map_url], blast=blast, ) logger.info( f"Sent {map_type} map for {address_full} (blast {blast}) to {number}") return None