def _get_bounds(self): bounds = None if self.location.data: geocode = gmaps_api.lookup_address(self.location.data, language=self.locale.data) bounds = math.expand_bounds(geocode.latlng_bounds(), self.distance_in_km()) return bounds
def create_from_location(cls, location): if location: geocode = gmaps_api.get_geocode(address=location) if not geocode: raise search_base.SearchException("Did not understand location: %s" % location) bounds = math.expand_bounds(geocode.latlng_bounds(), 200) # HACK? else: bounds = None self = cls(bounds=bounds) return self
def _get_bounds_for_fb_event(fb_event, check_places=False): # We don't need google-maps latlong accuracy. Let's cheat and use the fb_event for convenience if possible... location = fb_event['info'].get('place', {}).get('location', {}) if location and location.get('latitude') is not None: latlong = (location['latitude'], location['longitude']) # TODO: Someday we'd like to use locations of Taiwan or Germany to grab a bunch of stuff in those bounds # but for now, FB doesn't send us proper lat-long-boxes for them, and I don't want to look up everything # just in case there are bigger bounds...so we accept the latlong as-is. bounds = math.expand_bounds((latlong, latlong), cities.NEARBY_DISTANCE_KM) else: logging.info('Looking up event %s LocationInfo', fb_event['info']['id']) # Places textsearch lookups turn out to be 10x-expensive against our quota # So we disable them here, and instead just rely on the 99% good address searches # It should fallback to places on un-geocodable addresses too... # But at least it won't try Places *in addition* to geocode lookups. location_info = event_locations.LocationInfo(fb_event, check_places=check_places) if location_info.geocode: bounds = math.expand_bounds(location_info.geocode.latlng_bounds(), cities.NEARBY_DISTANCE_KM) else: bounds = None return bounds
def get_nearby_cities(latlng, country=None, distance=None): # We shrink it by two: # An event in Palo Alto could be thrown into a San Jose bucket # But an event in San Francisco, looking for "people who would go to SF event", # would want to include Palo Alto in its search radius....so would need to go 2x to San Jose # So instead of searching 200km in popular people for cities...let's try to be more specific about which person goes to which city distance = distance or NEARBY_DISTANCE_KM / 2 southwest, northeast = math.expand_bounds((latlng, latlng), distance) nearby_cities = _get_contained_cities((southwest, northeast), country=country) nearby_cities = [ x for x in nearby_cities if x.closer_than(latlng, distance) ] return nearby_cities
def _generate_results_for(city, week_start): start_time = week_start end_time = start_time + datetime.timedelta(days=8) latlng_bounds = ((city.latitude, city.longitude), (city.latitude, city.longitude)) city_bounds = math.expand_bounds(latlng_bounds, cities.NEARBY_DISTANCE_KM) search_query = search_base.SearchQuery( time_period=search_base.TIME_ALL_FUTURE, start_date=start_time, end_date=end_time, bounds=city_bounds) searcher = search.Search(search_query) search_results = searcher.get_search_results(full_event=True) return search_results
def promote_events_to_user(user): # TODO: Adjust when we have iphone notifications if not android.can_notify(user): return logging.info("Promoting new events to user %s", user.fb_uid) # Only send notifications for Mike for now user = users.User.get_by_id(user.fb_uid) if not user: logging.error("No user found: %s", user.fb_uid) return if user.expired_oauth_token: logging.info("User has expired token, aborting: %s", user.fb_uid) return user_location = user.location if not user_location: return distance_in_km = user.distance_in_km() min_attendees = user.min_attendees # search for relevant events geocode = gmaps_api.get_geocode(address=user_location) if not geocode: return None bounds = math.expand_bounds(geocode.latlng_bounds(), distance_in_km) query = search_base.SearchQuery(time_period=search_base.TIME_UPCOMING, bounds=bounds, min_attendees=min_attendees) one_day_ago = time.mktime( (datetime.datetime.now() - datetime.timedelta(hours=24)).timetuple()) search_query = search.Search(query) search_query.extra_fields = ['creation_time'] search_results = search_query._get_candidate_doc_events() # TODO: can we move this filter into the search query itself?? recent_events = [ x.doc_id for x in search_results if x.field('creation_time').value > one_day_ago ] logging.info("Found %s search_results, %s new events", len(search_results), len(recent_events)) for event_id in recent_events: if android.add_notify(user, event_id): logging.info("Sent notification!")
def promote_events_to_user(user): # TODO: Adjust when we have iphone notifications if not android.can_notify(user): return logging.info("Promoting new events to user %s", user.fb_uid) # Only send notifications for Mike for now user = users.User.get_by_id(user.fb_uid) if not user: logging.error("No user found: %s", user.fb_uid) return if user.expired_oauth_token: logging.info("User has expired token, aborting: %s", user.fb_uid) return user_location = user.location if not user_location: return distance_in_km = user.distance_in_km() min_attendees = user.min_attendees # search for relevant events geocode = gmaps_api.lookup_address(user_location) if not geocode: return None bounds = math.expand_bounds(geocode.latlng_bounds(), distance_in_km) query = search_base.SearchQuery(time_period=search_base.TIME_UPCOMING, bounds=bounds, min_attendees=min_attendees) one_day_ago = time.mktime((datetime.datetime.now() - datetime.timedelta(hours=24)).timetuple()) search_query = search.Search(query) search_query.extra_fields = ['creation_time'] search_results = search_query._get_candidate_doc_events() # TODO: can we move this filter into the search query itself?? recent_events = [x.doc_id for x in search_results if x.field('creation_time').value > one_day_ago] logging.info("Found %s search_results, %s new events", len(search_results), len(recent_events)) for event_id in recent_events: if android.add_notify(user, event_id): logging.info("Sent notification!")
def get(self): data = { 'location': self.request.get('location'), 'keywords': self.request.get('keywords'), 'locale': self.request.get('locale'), } # If it's 1.0 clients, or web clients, then grab all data if self.version == (1, 0): time_period = search_base.TIME_UPCOMING else: time_period = self.request.get('time_period') data['time_period'] = time_period form = search_base.SearchForm(data=data) if not form.validate(): for field, errors in form.errors.items(): for error in errors: self.add_error(u"%s error: %s" % ( getattr(form, field).label.text, error )) if not form.location.data: city_name = None southwest = None northeast = None if not form.keywords.data: if self.version == (1, 0): self.write_json_success({'results': []}) return else: self.add_error('Please enter a location or keywords') else: place = gmaps_api.fetch_place_as_json(query=form.location.data, language=form.locale.data) if place['status'] == 'OK' and place['results']: geocode = gmaps_api.GMapsGeocode(place['results'][0]) southwest, northeast = math.expand_bounds(geocode.latlng_bounds(), form.distance_in_km()) city_name = place['results'][0]['formatted_address'] # This will fail on a bad location, so let's verify the location is geocodable above first. else: if self.version == (1, 0): self.write_json_success({'results': []}) return else: self.add_error('Could not geocode location') self.errors_are_fatal() search_results = [] distances = [50, 100, 170, 300] distance_index = 0 while not search_results: form.distance.data = distances[distance_index] form.distance_units.data = 'miles' search_query = form.build_query() searcher = search.Search(search_query) # TODO(lambert): Increase the size limit when our clients can handle it. And improve our result sorting to return the 'best' results. searcher.limit = 500 search_results = searcher.get_search_results(full_event=True) # Increase our search distance in the hopes of finding something distance_index += 1 if distance_index == len(distances): # If we searched the world, then break break logging.info("Found %r events within %s %s of %s", form.keywords.data, form.distance.data, form.distance_units.data, form.location.data) onebox_links = onebox.get_links_for_query(search_query) json_results = [] for result in search_results: try: json_result = canonicalize_event_data(result.db_event, result.event_keywords, self.version) json_results.append(json_result) except Exception as e: logging.exception("Error processing event %s: %s" % (result.event_id, e)) title = self._get_title(city_name, form.keywords.data) json_response = { 'results': json_results, 'onebox_links': onebox_links, 'title': title, 'location': city_name, 'query': data, } if southwest and northeast: json_response['location_box'] = { 'southwest': { 'latitude': southwest[0], 'longitude': southwest[1], }, 'northeast': { 'latitude': northeast[0], 'longitude': northeast[1], }, } self.write_json_success(json_response)
def get_center_and_bounds(geocode, distance): southwest, northeast = math.expand_bounds(geocode.latlng_bounds(), distance) return geocode.latlng(), southwest, northeast
def email_for_user(user, fbl, should_send=True): if not user.send_email or not user.email: return if user.weekly_email_send_date and user.weekly_email_send_date > datetime.datetime.now() - datetime.timedelta(days=3): logging.warning("Skipping user %s (%s) because last weekly email was sent on %s", user.fb_uid, user.full_name, user.weekly_email_send_date) # Sent the weekly email too recently, let's abort return fb_user = fbl.fetched_data(fb_api.LookupUser, fbl.fb_uid) if not 'profile' in fb_user: return user_location = user.location if not user_location: return distance_in_km = user.distance_in_km() min_attendees = user.min_attendees # search for relevant events geocode = gmaps_api.lookup_address(user_location) if not geocode: return None bounds = math.expand_bounds(geocode.latlng_bounds(), distance_in_km) query = search_base.SearchQuery(time_period=search_base.TIME_UPCOMING, bounds=bounds, min_attendees=min_attendees) fb_user = fbl.fetched_data(fb_api.LookupUser, fbl.fb_uid) search_results = search.Search(query).get_search_results() # Don't send email... if not search_results: return friends.decorate_with_friends(fbl, search_results) rsvp.decorate_with_rsvps(fbl, search_results) past_results, present_results, grouped_results = search.group_results(search_results, include_all=True) display = {} display['user'] = user display['fb_user'] = fb_user week_events = grouped_results[0] # Only send emails if we have upcoming events if not week_events.results: return None display['results'] = week_events.results jinja_env = jinja2.Environment(loader=jinja2.FileSystemLoader("templates")) jinja_env.filters['date_human_format'] = user.date_human_format jinja_env.globals['dd_event_url'] = urls.dd_event_url jinja_env.globals['raw_fb_event_url'] = urls.raw_fb_event_url jinja_env.globals['CHOOSE_RSVPS'] = rsvp.CHOOSE_RSVPS rendered = jinja_env.get_template('html_mail_summary.html').render(display) d = datetime.date.today() d = d - datetime.timedelta(days=d.weekday()) # round down to last monday logging.info("Rendered HTML:\n%s", rendered) message = mail.EmailMessage( sender="DanceDeets Events <*****@*****.**>", subject="Dance events for %s" % d.strftime('%b %d, %Y'), to=user.email, html=rendered ) if should_send: # Update the last-sent-time here, so we any retryable errors don't cause emails to be multi-sent user = users.User.get_by_id(user.fb_uid) user.weekly_email_send_date = datetime.datetime.now() user.put() # And send the message now. message.send() return message
def email_for_user(user, fbl, should_send=True): if not user.send_email or not user.email: return if user.weekly_email_send_date and user.weekly_email_send_date > datetime.datetime.now( ) - datetime.timedelta(days=3): logging.warning( "Skipping user %s (%s) because last weekly email was sent on %s", user.fb_uid, user.full_name, user.weekly_email_send_date) # Sent the weekly email too recently, let's abort return fb_user = fbl.fetched_data(fb_api.LookupUser, fbl.fb_uid) if not 'profile' in fb_user: return user_location = user.location if not user_location: return distance_in_km = user.distance_in_km() min_attendees = user.min_attendees # search for relevant events geocode = gmaps_api.get_geocode(address=user_location) if not geocode: return None bounds = math.expand_bounds(geocode.latlng_bounds(), distance_in_km) query = search_base.SearchQuery(time_period=search_base.TIME_UPCOMING, bounds=bounds, min_attendees=min_attendees) fb_user = fbl.fetched_data(fb_api.LookupUser, fbl.fb_uid) search_results = search.Search(query).get_search_results() # Don't send email... if not search_results: return friends.decorate_with_friends(fbl, search_results) rsvp.decorate_with_rsvps(fbl, search_results) past_results, present_results, grouped_results = search.group_results( search_results, include_all=True) display = {} display['user'] = user display['fb_user'] = fb_user week_events = grouped_results[0] # Only send emails if we have upcoming events if not week_events.results: return None display['results'] = week_events.results jinja_env = jinja2.Environment(loader=jinja2.FileSystemLoader("templates")) jinja_env.filters['date_human_format'] = user.date_human_format jinja_env.globals['dd_event_url'] = urls.dd_event_url jinja_env.globals['raw_fb_event_url'] = urls.raw_fb_event_url jinja_env.globals['CHOOSE_RSVPS'] = rsvp.CHOOSE_RSVPS rendered = jinja_env.get_template('html_mail_summary.html').render(display) d = datetime.date.today() d = d - datetime.timedelta(days=d.weekday()) # round down to last monday logging.info("Rendered HTML:\n%s", rendered) message = mail.EmailMessage( sender="DanceDeets Events <*****@*****.**>", subject="Dance events for %s" % d.strftime('%b %d, %Y'), to=user.email, html=rendered) if should_send: # Update the last-sent-time here, so we any retryable errors don't cause emails to be multi-sent user = users.User.get_by_id(user.fb_uid) user.weekly_email_send_date = datetime.datetime.now() user.put() # And send the message now. message.send() return message
def get_bounds(self): bounds = None if self.location.data: geocode = gmaps_api.lookup_address(self.location.data, language=self.locale.data) bounds = math.expand_bounds(geocode.latlng_bounds(), self.distance_in_km()) return bounds
def get(self): data = { 'location': self.request.get('location'), 'keywords': self.request.get('keywords'), 'locale': self.request.get('locale'), } # If it's 1.0 clients, or web clients, then grab all data if self.version == (1, 0): time_period = search_base.TIME_UPCOMING else: time_period = self.request.get('time_period') data['time_period'] = time_period form = search_base.SearchForm(data=data) if not form.validate(): for field, errors in form.errors.items(): for error in errors: self.add_error(u"%s error: %s" % (getattr(form, field).label.text, error)) if not form.location.data: city_name = None southwest = None northeast = None if not form.keywords.data: if self.version == (1, 0): self.write_json_success({'results': []}) return else: self.add_error('Please enter a location or keywords') else: place = gmaps_api.fetch_place_as_json(query=form.location.data, language=form.locale.data) if place['status'] == 'OK' and place['results']: geocode = gmaps_api.GMapsGeocode(place['results'][0]) southwest, northeast = math.expand_bounds( geocode.latlng_bounds(), form.distance_in_km()) city_name = place['results'][0]['formatted_address'] # This will fail on a bad location, so let's verify the location is geocodable above first. else: if self.version == (1, 0): self.write_json_success({'results': []}) return else: self.add_error('Could not geocode location') self.errors_are_fatal() search_results = [] distances = [50, 100, 170, 300] distance_index = 0 while not search_results: form.distance.data = distances[distance_index] form.distance_units.data = 'miles' search_query = form.build_query() searcher = search.Search(search_query) # TODO(lambert): Increase the size limit when our clients can handle it. And improve our result sorting to return the 'best' results. searcher.limit = 500 search_results = searcher.get_search_results(full_event=True) # Increase our search distance in the hopes of finding something distance_index += 1 if distance_index == len(distances): # If we searched the world, then break break logging.info("Found %r events within %s %s of %s", form.keywords.data, form.distance.data, form.distance_units.data, form.location.data) onebox_links = onebox.get_links_for_query(search_query) json_results = [] for result in search_results: try: json_result = canonicalize_event_data(result.db_event, result.event_keywords) json_results.append(json_result) except Exception as e: logging.exception("Error processing event %s: %s" % (result.event_id, e)) title = self._get_title(city_name, form.keywords.data) json_response = { 'results': json_results, 'onebox_links': onebox_links, 'title': title, 'location': city_name, 'query': data, } if southwest and northeast: json_response['location_box'] = { 'southwest': { 'latitude': southwest[0], 'longitude': southwest[1], }, 'northeast': { 'latitude': northeast[0], 'longitude': northeast[1], }, } self.write_json_success(json_response)