def notify_admins_of_pending_emails(): """ Generate and email a report which alerts admins that there are automated emails which are ready to send, but can't be sent until they are approved by an admin. This is important so we don't forget to let certain automated emails send. """ if not c.ENABLE_PENDING_EMAILS_REPORT or not c.PRE_CON or not (c.DEV_BOX or c.SEND_EMAILS): return None with Session() as session: pending_emails = session.query(AutomatedEmail).filter(*AutomatedEmail.filters_for_pending).all() pending_emails_by_sender = groupify(pending_emails, ['sender', 'ident']) for sender, emails_by_ident in pending_emails_by_sender.items(): if sender == c.STAFF_EMAIL: # STOPS receives a report on ALL the pending emails. emails_by_sender = pending_emails_by_sender else: emails_by_sender = {sender: emails_by_ident} subject = '{} Pending Emails Report for {}'.format(c.EVENT_NAME, utils.localized_now().strftime('%Y-%m-%d')) body = render('emails/daily_checks/pending_emails.html', { 'pending_emails_by_sender': emails_by_sender, 'primary_sender': sender, }) send_email(c.STAFF_EMAIL, sender, subject, body, format='html', model='n/a', session=session) return groupify(pending_emails, 'sender', 'ident')
def send_automated_emails(): """ Send any automated emails that are currently active, and have been approved or do not need approval. For each unapproved email that needs approval from an admin, the unapproved_count will be updated to indicate the number of recepients that _would have_ received the email if it had been approved. """ if not (c.DEV_BOX or c.SEND_EMAILS): return None with Session() as session: active_automated_emails = session.query(AutomatedEmail) \ .filter(*AutomatedEmail.filters_for_active) \ .options(joinedload(AutomatedEmail.emails)).all() for automated_email in active_automated_emails: automated_email.unapproved_count = 0 automated_emails_by_model = groupify(active_automated_emails, 'model') for model, query_func in AutomatedEmailFixture.queries.items(): model_instances = query_func(session) for model_instance in model_instances: automated_emails = automated_emails_by_model.get(model.__name__, []) for automated_email in automated_emails: if model_instance.id not in automated_email.emails_by_fk_id: if automated_email.would_send_if_approved(model_instance): if automated_email.approved or not automated_email.needs_approval: automated_email.send_to(model_instance, delay=False) else: automated_email.unapproved_count += 1 return {e.ident: e.unapproved_count for e in active_automated_emails if e.unapproved_count > 0}
def email_statuses(self, session): emails = session.query(AutomatedEmail).filter( AutomatedEmail.ident.in_([ 'panel_accepted', 'panel_declined', 'panel_waitlisted', 'panel_scheduled' ])) return {'emails': groupify(emails, 'ident')}
def attendees(self, session, target_server='', api_token='', query='', message=''): target_url, target_host, remote_api_token = _format_import_params(target_server, api_token) results = {} if cherrypy.request.method == 'POST': if not remote_api_token: message = 'No API token given and could not find a token for: {}'.format(target_host) elif not target_url: message = 'Unrecognized hostname: {}'.format(target_server) if not message: try: uri = '{}/jsonrpc/'.format(target_url) service = ServerProxy(uri=uri, extra_headers={'X-Auth-Token': remote_api_token}) results = service.attendee.export(query=query) except Exception as ex: message = str(ex) attendees = results.get('attendees', []) for attendee in attendees: attendee['href'] = '{}/registration/form?id={}'.format(target_url, attendee['id']) if attendees: attendees_by_name_email = groupify(attendees, lambda a: ( a['first_name'].lower(), a['last_name'].lower(), Attendee.normalize_email(a['email']), )) filters = [ and_( func.lower(Attendee.first_name) == first, func.lower(Attendee.last_name) == last, Attendee.normalized_email == email, ) for first, last, email in attendees_by_name_email.keys() ] existing_attendees = session.query(Attendee).filter(or_(*filters)).all() for attendee in existing_attendees: existing_key = (attendee.first_name.lower(), attendee.last_name.lower(), attendee.normalized_email) attendees_by_name_email.pop(existing_key, {}) attendees = list(chain(*attendees_by_name_email.values())) else: existing_attendees = [] return { 'target_server': target_server, 'api_token': api_token, 'query': query, 'message': message, 'unknown_ids': results.get('unknown_ids', []), 'unknown_emails': results.get('unknown_emails', []), 'unknown_names': results.get('unknown_names', []), 'unknown_names_and_emails': results.get('unknown_names_and_emails', []), 'attendees': attendees, 'existing_attendees': existing_attendees, }
def signups_requiring_notification(self, session, from_time, to_time, options=None): """ Returns a dict of AttractionSignups that require notification. The keys of the returned dict are the amount of advanced notice, given in seconds. A key of -1 indicates confirmation notices after a signup. The query generated by this method looks horrific, but is surprisingly efficient. """ advance_checkin = max(0, self.advance_checkin) subqueries = [] for advance_notice in sorted(set([-1] + self.advance_notices)): event_filters = [AttractionEvent.attraction_id == self.id] if advance_notice == -1: notice_ident = cast(AttractionSignup.attraction_event_id, UnicodeText) notice_param = bindparam( 'confirm_notice', advance_notice).label('advance_notice') else: advance_notice = max(0, advance_notice) + advance_checkin notice_delta = timedelta(seconds=advance_notice) event_filters += [ AttractionEvent.start_time >= from_time + notice_delta, AttractionEvent.start_time < to_time + notice_delta ] notice_ident = func.concat( AttractionSignup.attraction_event_id, '_{}'.format(advance_notice)) notice_param = bindparam( 'advance_notice_{}'.format(advance_notice), advance_notice).label('advance_notice') subquery = session.query(AttractionSignup, notice_param).filter( AttractionSignup.is_unchecked_in, AttractionSignup.attraction_event_id.in_( session.query(AttractionEvent.id).filter(*event_filters)), not_(exists().where( and_( AttractionNotification.ident == notice_ident, AttractionNotification.attraction_event_id == AttractionSignup.attraction_event_id, AttractionNotification.attendee_id == AttractionSignup.attendee_id)))).with_labels() subqueries.append(subquery) query = subqueries[0].union(*subqueries[1:]) if options: query = query.options(*listify(options)) query.order_by(AttractionSignup.id) return groupify(query, lambda x: x[0], lambda x: x[1])
def notify_admins_of_pending_emails(): """ Generate and email a report which alerts admins that there are automated emails which are ready to send, but can't be sent until they are approved by an admin. This is important so we don't forget to let certain automated emails send. """ if not c.ENABLE_PENDING_EMAILS_REPORT or not c.PRE_CON or not ( c.DEV_BOX or c.SEND_EMAILS): return None with Session() as session: pending_emails = session.query(AutomatedEmail).filter( *AutomatedEmail.filters_for_pending).all() pending_emails_by_sender = groupify(pending_emails, ['sender', 'ident']) for sender, emails_by_ident in pending_emails_by_sender.items(): if sender == c.STAFF_EMAIL: # STOPS receives a report on ALL the pending emails. emails_by_sender = pending_emails_by_sender else: emails_by_sender = {sender: emails_by_ident} subject = '{} Pending Emails Report for {}'.format( c.EVENT_NAME, utils.localized_now().strftime('%Y-%m-%d')) body = render( 'emails/daily_checks/pending_emails.html', { 'pending_emails_by_sender': emails_by_sender, 'primary_sender': sender, }) send_email(c.STAFF_EMAIL, sender, subject, body, format='html', model='n/a', session=session) return groupify(pending_emails, 'sender', 'ident')
def attractions_check_notification_replies(): twilio_client = get_twilio_client(c.PANELS_TWILIO_SID, c.PANELS_TWILIO_TOKEN) if not twilio_client or not c.PANELS_TWILIO_NUMBER: log.warn('SMS notification replies disabled for attractions') return with Session() as session: messages = twilio_client.messages.list(to=c.PANELS_TWILIO_NUMBER) sids = set(m.sid for m in messages) existing_sids = set( sid for [sid] in session.query(AttractionNotificationReply.sid).filter( AttractionNotificationReply.sid.in_(sids))) attendees = session.query(Attendee).filter( Attendee.cellphone != '', Attendee.attraction_notifications.any()) attendees_by_phone = groupify(attendees, lambda a: normalize_phone(a.cellphone)) for message in filter(lambda m: m.sid not in existing_sids, messages): attraction_event_id = None attraction_id = None attendee_id = None attendees = attendees_by_phone.get(normalize_phone(message.from_), []) for attendee in attendees: notifications = sorted(filter( lambda s: s.notification_type == Attendee. _NOTIFICATION_TEXT, attendee.attraction_notifications), key=lambda s: s.sent_time) if notifications: notification = notifications[-1] attraction_event_id = notification.attraction_event_id attraction_id = notification.attraction_id attendee_id = notification.attendee_id if 'N' in message.body.upper() and notification.signup: session.delete(notification.signup) break session.add( AttractionNotificationReply( attraction_event_id=attraction_event_id, attraction_id=attraction_id, attendee_id=attendee_id, notification_type=Attendee._NOTIFICATION_TEXT, from_phonenumber=message.from_, to_phonenumber=message.to, sid=message.sid, received_time=datetime.now(pytz.UTC), sent_time=message.date_sent.replace(tzinfo=pytz.UTC), body=message.body)) session.commit()
def import_attendees(self, session, target_server='', api_token='', query='', message=''): service, service_message, target_url = get_api_service_from_server(target_server, api_token) message = message or service_message attendees, existing_attendees, results = {}, {}, {} if service: try: results = service.attendee.export(query=query) except Exception as ex: message = str(ex) if cherrypy.request.method == 'POST' and not message: attendees = results.get('attendees', []) for attendee in attendees: attendee['href'] = '{}/registration/form?id={}'.format(target_url, attendee['id']) if attendees: attendees_by_name_email = groupify(attendees, lambda a: ( a['first_name'].lower(), a['last_name'].lower(), normalize_email(a['email']), )) filters = [ and_( func.lower(Attendee.first_name) == first, func.lower(Attendee.last_name) == last, Attendee.normalized_email == email, ) for first, last, email in attendees_by_name_email.keys() ] existing_attendees = session.query(Attendee).filter(or_(*filters)).all() for attendee in existing_attendees: existing_key = (attendee.first_name.lower(), attendee.last_name.lower(), attendee.normalized_email) attendees_by_name_email.pop(existing_key, {}) attendees = list(chain(*attendees_by_name_email.values())) return { 'target_server': target_server, 'api_token': api_token, 'query': query, 'message': message, 'unknown_ids': results.get('unknown_ids', []), 'unknown_emails': results.get('unknown_emails', []), 'unknown_names': results.get('unknown_names', []), 'unknown_names_and_emails': results.get('unknown_names_and_emails', []), 'attendees': attendees, 'existing_attendees': existing_attendees, }
def pending(self, session, message=''): emails_with_count = session.query(AutomatedEmail, AutomatedEmail.email_count).filter( AutomatedEmail.subject != '', AutomatedEmail.sender != '',).all() emails = [] for email, email_count in sorted(emails_with_count, key=lambda e: e[0].ordinal): email.sent_email_count = email_count emails.append(email) emails_by_sender = groupify(emails, 'sender') return { 'message': message, 'automated_emails': emails_by_sender, }
def attractions_check_notification_replies(): twilio_client = get_twilio_client(c.PANELS_TWILIO_SID, c.PANELS_TWILIO_TOKEN) if not twilio_client or not c.PANELS_TWILIO_NUMBER: log.warn('SMS notification replies disabled for attractions') return with Session() as session: messages = twilio_client.messages.list(to=c.PANELS_TWILIO_NUMBER) sids = set(m.sid for m in messages) existing_sids = set( sid for [sid] in session.query(AttractionNotificationReply.sid).filter(AttractionNotificationReply.sid.in_(sids))) attendees = session.query(Attendee).filter(Attendee.cellphone != '', Attendee.attraction_notifications.any()) attendees_by_phone = groupify(attendees, lambda a: normalize_phone(a.cellphone)) for message in filter(lambda m: m.sid not in existing_sids, messages): attraction_event_id = None attraction_id = None attendee_id = None attendees = attendees_by_phone.get(normalize_phone(message.from_), []) for attendee in attendees: notifications = sorted(filter( lambda s: s.notification_type == Attendee._NOTIFICATION_TEXT, attendee.attraction_notifications), key=lambda s: s.sent_time) if notifications: notification = notifications[-1] attraction_event_id = notification.attraction_event_id attraction_id = notification.attraction_id attendee_id = notification.attendee_id if 'N' in message.body.upper() and notification.signup: session.delete(notification.signup) break session.add(AttractionNotificationReply( attraction_event_id=attraction_event_id, attraction_id=attraction_id, attendee_id=attendee_id, notification_type=Attendee._NOTIFICATION_TEXT, from_phonenumber=message.from_, to_phonenumber=message.to, sid=message.sid, received_time=datetime.now(pytz.UTC), sent_time=message.date_sent.replace(tzinfo=pytz.UTC), body=message.body)) session.commit()
def staff(self, session, target_server='', api_token='', query='', message=''): target_url = _server_to_url(target_server) if cherrypy.request.method == 'POST': try: uri = '{}/jsonrpc/'.format(target_url) service = ServerProxy( uri=uri, extra_headers={'X-Auth-Token': api_token.strip()}) results = service.attendee.export(query=query) except Exception as ex: message = str(ex) results = {} else: results = {} attendees = results.get('attendees', []) for attendee in attendees: attendee['href'] = '{}/registration/form?id={}'.format( target_url, attendee['id']) if attendees: attendees_by_email = groupify( attendees, lambda a: Attendee.normalize_email(a['email'])) emails = list(attendees_by_email.keys()) existing_attendees = session.query(Attendee).filter( Attendee.normalized_email.in_(emails)).all() for attendee in existing_attendees: attendees_by_email.pop(attendee.normalized_email, {}) attendees = list(chain(*attendees_by_email.values())) else: existing_attendees = [] return { 'target_server': target_server, 'api_token': api_token, 'query': query, 'message': message, 'unknown_emails': results.get('unknown_emails', []), 'unknown_names': results.get('unknown_names', []), 'attendees': attendees, 'existing_attendees': existing_attendees, }
def pending(self, session, message=''): emails_with_count = session.query(AutomatedEmail, AutomatedEmail.email_count).filter( AutomatedEmail.subject != '', AutomatedEmail.sender != '', ).all() emails = [] for email, email_count in sorted(emails_with_count, key=lambda e: e[0].ordinal): email.sent_email_count = email_count emails.append(email) emails_by_sender = groupify(emails, 'sender') return { 'message': message, 'automated_emails': emails_by_sender, }
def signups_requiring_notification(self, session, from_time, to_time, options=None): """ Returns a dict of AttractionSignups that require notification. The keys of the returned dict are the amount of advanced notice, given in seconds. A key of -1 indicates confirmation notices after a signup. The query generated by this method looks horrific, but is surprisingly efficient. """ advance_checkin = max(0, self.advance_checkin) subqueries = [] for advance_notice in sorted(set([-1] + self.advance_notices)): event_filters = [AttractionEvent.attraction_id == self.id] if advance_notice == -1: notice_ident = cast(AttractionSignup.attraction_event_id, UnicodeText) notice_param = bindparam('confirm_notice', advance_notice).label('advance_notice') else: advance_notice = max(0, advance_notice) + advance_checkin notice_delta = timedelta(seconds=advance_notice) event_filters += [ AttractionEvent.start_time >= from_time + notice_delta, AttractionEvent.start_time < to_time + notice_delta] notice_ident = func.concat(AttractionSignup.attraction_event_id, '_{}'.format(advance_notice)) notice_param = bindparam( 'advance_notice_{}'.format(advance_notice), advance_notice).label('advance_notice') subquery = session.query(AttractionSignup, notice_param).filter( AttractionSignup.is_unchecked_in, AttractionSignup.attraction_event_id.in_( session.query(AttractionEvent.id).filter(*event_filters)), not_(exists().where(and_( AttractionNotification.ident == notice_ident, AttractionNotification.attraction_event_id == AttractionSignup.attraction_event_id, AttractionNotification.attendee_id == AttractionSignup.attendee_id)))).with_labels() subqueries.append(subquery) query = subqueries[0].union(*subqueries[1:]) if options: query = query.options(*listify(options)) query.order_by(AttractionSignup.id) return groupify(query, lambda x: x[0], lambda x: x[1])
def send_automated_emails(): """ Send any automated emails that are currently active, and have been approved or do not need approval. For each unapproved email that needs approval from an admin, the unapproved_count will be updated to indicate the number of recepients that _would have_ received the email if it had been approved. """ if not (c.DEV_BOX or c.SEND_EMAILS): return None with Session() as session: active_automated_emails = session.query(AutomatedEmail) \ .filter(*AutomatedEmail.filters_for_active) \ .options(joinedload(AutomatedEmail.emails)).all() for automated_email in active_automated_emails: automated_email.unapproved_count = 0 automated_emails_by_model = groupify(active_automated_emails, 'model') for model, query_func in AutomatedEmailFixture.queries.items(): model_instances = query_func(session) for model_instance in model_instances: automated_emails = automated_emails_by_model.get( model.__name__, []) for automated_email in automated_emails: if model_instance.id not in automated_email.emails_by_fk_id: if automated_email.would_send_if_approved( model_instance): if automated_email.approved or not automated_email.needs_approval: automated_email.send_to(model_instance, delay=False) else: automated_email.unapproved_count += 1 return { e.ident: e.unapproved_count for e in active_automated_emails if e.unapproved_count > 0 }
def available_events_by_day(self): return groupify(self.available_events, 'start_day_local')
def dept_roles_by_id(self): return groupify(self.dept_roles, 'id')
def emails_by_fk_id(self): return groupify(self.emails, 'fk_id')
def dept_roles_by_name(self): return groupify(self.dept_roles, 'name')
def available_events_by_day(self): return groupify(self.available_events, 'start_day_local')
def events_by_location_by_day(self): events = sorted(self.events, key=lambda e: (c.EVENT_LOCATIONS[e.location], e.start_time)) return groupify(events, ['location', 'start_day_local'])
def locations_by_feature_id(self): return groupify(self.features, 'id', lambda f: f.locations)
def locations_by_feature_id(self): return groupify(self.features, 'id', lambda f: f.locations)
def events_by_location_by_day(self): events = sorted(self.events, key=lambda e: (c.EVENT_LOCATIONS[e.location], e.start_time)) return groupify(events, ['location', 'start_day_local'])