Example #1
0
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')
Example #2
0
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')}
Example #4
0
    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])
Example #6
0
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()
Example #8
0
    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,
        }
Example #9
0
    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,
        }
Example #10
0
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()
Example #11
0
    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,
        }
Example #12
0
    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,
        }
Example #13
0
    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])
Example #14
0
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
        }
Example #15
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')
Example #17
0
 def emails_by_fk_id(self):
     return groupify(self.emails, 'fk_id')
 def dept_roles_by_name(self):
     return groupify(self.dept_roles, 'name')
Example #19
0
 def available_events_by_day(self):
     return groupify(self.available_events, 'start_day_local')
Example #20
0
 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'])
Example #21
0
 def locations_by_feature_id(self):
     return groupify(self.features, 'id', lambda f: f.locations)
Example #22
0
 def locations_by_feature_id(self):
     return groupify(self.features, 'id', lambda f: f.locations)
Example #23
0
 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'])