def really_send(self, model_instance, raise_errors=False): """ Actually send an email to a particular model instance (i.e. a particular attendee). Doesn't perform any kind of checks at all if we should be sending this, just immediately sends the email no matter what. NOTE: use send_if_should() instead of calling this method unless you 100% know what you're doing. NOTE: send_email() fails if c.SEND_EMAILS is False """ try: subject = self.computed_subject(model_instance) format = 'text' if self.template.endswith('.txt') else 'html' send_email(self.sender, model_instance.email, subject, self.render(model_instance), format, model=model_instance, cc=self.cc, ident=self.ident) except Exception: log.error('error sending {!r} email to {}', self.subject, model_instance.email, exc_info=True) if raise_errors: raise
def process_group_payment(self, session, payment_id, stripeToken): charge = Charge.get(payment_id) [group] = charge.groups message = charge.charge_cc(session, stripeToken) if message: raise HTTPRedirect('group_members?id={}&message={}', group.id, message) else: group.amount_paid += charge.dollar_amount session.merge(group) if group.is_dealer: try: send_email.delay( c.MARKETPLACE_EMAIL, c.MARKETPLACE_EMAIL, 'Dealer Payment Completed', render('emails/dealers/payment_notification.txt', {'group': group}, encoding=None), model=group.to_dict('id')) except Exception: log.error( 'unable to send dealer payment confirmation email', exc_info=True) raise HTTPRedirect('group_members?id={}&message={}', group.id, 'Your payment has been accepted!')
def swallow_exception(*args, **kwargs): try: return func(*args, **kwargs) except Exception: log.error( "Exception raised, but we're going to ignore it and continue.", exc_info=True)
def attendees_by_state(self, out, session): # Result of set(map(lambda x: x.state, SearchEngine(db_file_dir="/srv/reggie/data").ses.query(SimpleZipcode))) -- literally all the states uszipcode knows about states = [ 'SD', 'IL', 'WY', 'NV', 'NJ', 'NM', 'UT', 'OR', 'TX', 'NE', 'MS', 'FL', 'VA', 'HI', 'KY', 'MO', 'NY', 'WV', 'DC', 'AR', 'MT', 'MD', 'SC', 'NC', 'KS', 'OH', 'PR', 'CO', 'IN', 'VT', 'LA', 'ND', 'AZ', 'AK', 'AL', 'CT', 'TN', 'PA', 'IA', 'WA', 'ME', 'NH', 'MA', 'ID', 'OK', 'WI', 'GA', 'CA', 'DE', 'MN', 'MI', 'RI' ] total_count = session.attendees_with_badges().count() out.writerow(['# of Attendees', 'State', '% of Total Attendees']) for state in states: try: zip_codes = list( map( lambda x: x.zipcode, SearchEngine(db_file_dir="/srv/reggie/data").by_state( state, returns=None))) except Exception as e: log.error("Error calling SearchEngine: " + e) else: current_count = session.attendees_with_badges().filter( Attendee.zip_code.in_(zip_codes)).count() if current_count: out.writerow([ current_count, state, "%.2f" % float(current_count / total_count * 100) ])
def check_id_for_model(model, **params): message = None session = params['session'] model_id = params.get('id') if not model_id: message = "No ID provided. Try using a different link or going back." elif model_id == 'None': # Some pages use the string 'None' is indicate that a new model should be created, so this is a valid ID pass else: try: if not isinstance(model_id, uuid.UUID): uuid.UUID(model_id) except ValueError: message = "That ID is not a valid format. Did you enter or edit it manually or paste it incorrectly?" else: if not session.query(model).filter(model.id == model_id).first(): message = "The ID provided was not found in our database." if message: log.error("check_id {} error: {}: id={}", model.__name__, message, model_id) raise HTTPRedirect('../preregistration/not_found?id={}&message={}', model_id, message)
def radial_zip_data(self, out, session, **params): if params.get('radius'): try: res = SearchEngine( db_file_dir="/srv/reggie/data").by_coordinates( self.center.lat, self.center.lng, radius=int(params['radius']), returns=None) except Exception as e: log.error("Error calling SearchEngine: " + e) else: out.writerow([ '# of Attendees', 'City', 'State', 'Zipcode', 'Miles from Event', '% of Total Attendees' ]) if len(res) > 0: keys = self.zips.keys() center_coord = (self.center.lat, self.center.lng) total_count = session.attendees_with_badges().count() for x in res: if x.zipcode in keys: out.writerow([ self.zips_counter[x.zipcode], x.city, x.state, x.zipcode, VincentyDistance((x.lat, x.lng), center_coord).miles, "%.2f" % float(self.zips_counter[x.zipcode] / total_count * 100) ])
def unset_group_member(self, session, id): attendee = session.attendee(id) try: send_email.delay( c.REGDESK_EMAIL, attendee.email, '{} group registration dropped'.format(c.EVENT_NAME), render('emails/reg_workflow/group_member_dropped.txt', {'attendee': attendee}, encoding=None), model=attendee.to_dict('id')) except Exception: log.error('unable to send group unset email', exc_info=True) session.assign_badges( attendee.group, attendee.group.badges + 1, new_badge_type=attendee.badge_type, new_ribbon_type=attendee.ribbon, registered=attendee.registered, paid=attendee.paid) session.delete_from_group(attendee, attendee.group) raise HTTPRedirect( 'group_members?id={}&message={}', attendee.group_id, 'Attendee unset; you may now assign their badge to someone else')
def unset_group_member(self, session, id): attendee = session.attendee(id) try: send_email.delay( c.REGDESK_EMAIL, attendee.email, '{} group registration dropped'.format(c.EVENT_NAME), render('emails/reg_workflow/group_member_dropped.txt', {'attendee': attendee}, encoding=None), model=attendee.to_dict('id')) except Exception: log.error('unable to send group unset email', exc_info=True) session.assign_badges( attendee.group, attendee.group.badges + 1, new_badge_type=attendee.badge_type, new_ribbon_type=attendee.ribbon, registered=attendee.registered, paid=attendee.paid) session.delete_from_group(attendee, attendee.group) raise HTTPRedirect( 'group_members?id={}&message={}', attendee.group_id, 'Attendee unset; you may now assign their badge to someone else')
def link_badge(self, session, applicant_id, attendee_id): ids = [] try: attendee = session.attendee(attendee_id) if attendee.badge_type != c.GUEST_BADGE: attendee.ribbon = add_opt(attendee.ribbon_ints, c.PANELIST_RIBBON) pa = session.panel_applicant(applicant_id) applicants = session.query(PanelApplicant).filter_by( first_name=pa.first_name, last_name=pa.last_name, email=pa.email) for applicant in applicants: ids.append(applicant.id) applicant.attendee_id = attendee_id session.commit() except Exception: log.error('unexpected error linking panelist to a badge', exc_info=True) return { 'error': 'Unexpected error: unable to link applicant to badge.' } else: return {'linked': ids, 'name': pa.full_name}
def create_badge(self, session, applicant_id): ids = [] try: pa = session.panel_applicant(applicant_id) attendee = Attendee(placeholder=True, paid=c.NEED_NOT_PAY, ribbon=c.PANELIST_RIBBON, badge_type=c.ATTENDEE_BADGE, first_name=pa.first_name, last_name=pa.last_name, email=pa.email, cellphone=pa.cellphone) session.add(attendee) applicants = session.query(PanelApplicant).filter_by( first_name=pa.first_name, last_name=pa.last_name, email=pa.email) for applicant in applicants: ids.append(applicant.id) applicant.attendee_id = attendee.id session.commit() except Exception: log.error('unexpected error adding new panelist', exc_info=True) return {'error': 'Unexpected error: unable to add attendee'} else: return {'added': ids}
def charge_cc(self, session, token): try: log.debug( 'PAYMENT: !!! attempting to charge stripeToken {} {} cents for {}', token, self.amount, self.description) self.response = stripe.Charge.create( card=token, currency='usd', amount=self.amount, description=self.description, receipt_email=self.receipt_email) log.info( 'PAYMENT: !!! SUCCESS: charged stripeToken {} {} cents for {}, responseID={}', token, self.amount, self.description, getattr(self.response, 'id', None)) except stripe.CardError as e: msg = 'Your card was declined with the following error from our processor: ' + str( e) log.error('PAYMENT: !!! FAIL: {}', msg) return msg except stripe.StripeError as e: error_txt = 'Got an error while calling charge_cc(self, token={!r})'.format( token) report_critical_exception( msg=error_txt, subject='ERROR: MAGFest Stripe invalid request error') return 'An unexpected problem occurred while processing your card: ' + str( e) else: if self.models: session.add(self.stripe_transaction_from_charge())
def _jsonrpc_handler(self=None): id = None def error(status, code, message): response = {'jsonrpc': '2.0', 'id': id, 'error': {'code': code, 'message': message}} log.debug('Returning error message: {}', repr(response).encode('utf-8')) cherrypy.response.status = status return response def success(result): response = {'jsonrpc': '2.0', 'id': id, 'result': result} log.debug('Returning success message: {}', { 'jsonrpc': '2.0', 'id': id, 'result': len(result) if is_listy(result) else str(result).encode('utf-8')}) cherrypy.response.status = 200 return response request_body = cherrypy.request.json if not isinstance(request_body, dict): return error(400, ERR_INVALID_JSON, 'Invalid json input: {!r}'.format(request_body)) log.debug('jsonrpc request body: {}', repr(request_body).encode('utf-8')) id, params = request_body.get('id'), request_body.get('params', []) if 'method' not in request_body: return error(400, ERR_INVALID_RPC, '"method" field required for jsonrpc request') method = request_body['method'] if method.count('.') != 1: return error(404, ERR_MISSING_FUNC, 'Invalid method ' + method) module, function = method.split('.') if module not in services: return error(404, ERR_MISSING_FUNC, 'No module ' + module) service = services[module] if not hasattr(service, function): return error(404, ERR_MISSING_FUNC, 'No function ' + method) if not isinstance(params, (list, dict)): return error(400, ERR_INVALID_PARAMS, 'Invalid parameter list: {!r}'.format(params)) args, kwargs = (params, {}) if isinstance(params, list) else ([], params) precall(request_body) try: return success(getattr(service, function)(*args, **kwargs)) except HTTPError as http_error: return error(http_error.code, ERR_FUNC_EXCEPTION, http_error._message) except Exception as e: log.error('Unexpected error', exc_info=True) message = 'Unexpected error: {}'.format(e) if debug: message += '\n' + traceback.format_exc() return error(500, ERR_FUNC_EXCEPTION, message) finally: trigger_delayed_notifications()
def _jsonrpc_handler(self=None): id = None def error(status, code, message): response = {'jsonrpc': '2.0', 'id': id, 'error': {'code': code, 'message': message}} log.debug('Returning error message: {}', repr(response).encode('utf-8')) cherrypy.response.status = status return response def success(result): response = {'jsonrpc': '2.0', 'id': id, 'result': result} log.debug('Returning success message: {}', { 'jsonrpc': '2.0', 'id': id, 'result': len(result) if is_listy(result) else str(result).encode('utf-8')}) cherrypy.response.status = 200 return response request_body = cherrypy.request.json if not isinstance(request_body, dict): return error(400, ERR_INVALID_JSON, 'Invalid json input: {!r}'.format(request_body)) log.debug('jsonrpc request body: {}', repr(request_body).encode('utf-8')) id, params = request_body.get('id'), request_body.get('params', []) if 'method' not in request_body: return error(400, ERR_INVALID_RPC, '"method" field required for jsonrpc request') method = request_body['method'] if method.count('.') != 1: return error(404, ERR_MISSING_FUNC, 'Invalid method ' + method) module, function = method.split('.') if module not in services: return error(404, ERR_MISSING_FUNC, 'No module ' + module) service = services[module] if not hasattr(service, function): return error(404, ERR_MISSING_FUNC, 'No function ' + method) if not isinstance(params, (list, dict)): return error(400, ERR_INVALID_PARAMS, 'Invalid parameter list: {!r}'.format(params)) args, kwargs = (params, {}) if isinstance(params, list) else ([], params) precall(request_body) try: return success(getattr(service, function)(*args, **kwargs)) except HTTPError as http_error: return error(http_error.code, ERR_FUNC_EXCEPTION, http_error._message) except Exception as e: log.error('Unexpected error', exc_info=True) message = 'Unexpected error: {}'.format(e) if debug: message += '\n' + traceback.format_exc() return error(500, ERR_FUNC_EXCEPTION, message) finally: trigger_delayed_notifications()
def wrapper(*args, **kwargs): try: a = [str(x)[:255] for x in args] kw = dict([(k[:255], str(v)[:255]) for k, v in kwargs.items()]) log.trace('Calling %s.%s %r %r', fn.__module__, fn.__name__, a, kw) return fn(*args, **kwargs) except Exception as e: log.error('Error calling function %s: %s' % (fn.__name__, e)) log.exception(e) raise
def transfer_badge(self, session, message='', **params): old = session.attendee(params['id']) assert old.is_transferable, 'This badge is not transferrable.' session.expunge(old) attendee = session.attendee(params, restricted=True) if 'first_name' in params: message = check(attendee, prereg=True) if (old.first_name == attendee.first_name and old.last_name == attendee.last_name) \ or (old.legal_name and old.legal_name == attendee.legal_name): message = 'You cannot transfer your badge to yourself.' elif not message and (not params['first_name'] and not params['last_name']): message = check(attendee, prereg=True) if not message and (not params['first_name'] and not params['last_name']): message = 'First and Last names are required.' if not message: subject = c.EVENT_NAME + ' Registration Transferred' body = render('emails/reg_workflow/badge_transfer.txt', { 'new': attendee, 'old': old }, encoding=None) try: send_email.delay( c.REGDESK_EMAIL, [old.email, attendee.email, c.REGDESK_EMAIL], subject, body, model=attendee.to_dict('id')) except Exception: log.error('unable to send badge change email', exc_info=True) if attendee.amount_unpaid: raise HTTPRedirect('attendee_donation_form?id={}', attendee.id) else: raise HTTPRedirect( 'badge_updated?id={}&message={}', attendee.id, 'Your registration has been transferred') else: for attr in c.UNTRANSFERABLE_ATTRS: setattr(attendee, attr, getattr(Attendee(), attr)) return { 'old': old, 'attendee': attendee, 'message': message, 'affiliates': session.affiliates() }
def drop(self, session, attendee_id, tournament_id): try: session.delete(session.tabletop_entrant(attendee_id=attendee_id, tournament_id=tournament_id)) session.commit() except Exception: log.error('unable to drop tournament entrant', exc_info=True) return { 'message': 'Entrant dropped; if re-added they will be re-texted', 'state': _state(session) }
def wrapped(*args, **kwargs): try: return fn(*args, **kwargs) except Exception: a = [x for x in (args or [])] kw = {k: v for k, v in (kwargs or {}).items()} log.error('Error calling {}.{} {!r} {!r}'.format( fn.__module__, fn.__name__, a, kw), exc_info=True) exc_class, exc, tb = sys.exc_info() raise six.reraise(CrudException, CrudException(str(exc)), tb)
def get_twilio_client(twilio_sid, twilio_token): if c.SEND_SMS: try: if twilio_sid and twilio_token: return TwilioRestClient(twilio_sid, twilio_token) else: log.info('Twilio: could not create twilio client. Missing twilio {}.'.format( readable_join(['' if twilio_sid else 'SID', '' if twilio_token else 'TOKEN']))) except Exception: log.error('Twilio: could not create twilio client', exc_info=True) return None
def invalid_notification(self): if self.staffing and self.badge_status == c.INVALID_STATUS \ and self.badge_status != self.orig_value_of('badge_status'): try: send_email.delay( c.STAFF_EMAIL, c.STAFF_EMAIL, 'Volunteer invalidated', render('emails/invalidated_volunteer.txt', {'attendee': self}, encoding=None), model=self.to_dict('id')) except Exception: log.error('unable to send invalid email', exc_info=True)
def send_sms(to, body, from_=c.TABLETOP_TWILIO_NUMBER): to = normalize_phone(to, c.TABLETOP_PHONE_COUNTRY or 'US') if not twilio_client: log.error('no twilio client configured') elif c.DEV_BOX and to not in c.TESTING_PHONE_NUMBERS: log.info('We are in dev box mode, so we are not sending {!r} to {!r}', body, to) else: return twilio_client.messages.create( to=to, body=body, from_=normalize_phone(from_, c.TABLETOP_PHONE_COUNTRY or 'US'))
def set_center(self, session, **params): if params.get("zip"): try: self.center = SearchEngine( db_file_dir="/srv/reggie/data").by_zipcode( int(params["zip"])) except Exception as e: log.error("Error calling SearchEngine: " + e) else: return "Set to %s, %s - %s" % ( self.center.city, self.center.state, self.center.zipcode) return False
def invalid_notification(self): if self.staffing and self.badge_status == c.INVALID_STATUS \ and self.badge_status != self.orig_value_of('badge_status'): try: send_email.delay(c.STAFF_EMAIL, c.STAFF_EMAIL, 'Volunteer invalidated', render('emails/invalidated_volunteer.txt', {'attendee': self}, encoding=None), model=self.to_dict('id')) except Exception: log.error('unable to send invalid email', exc_info=True)
def drop(self, session, attendee_id, tournament_id): try: session.delete( session.tabletop_entrant(attendee_id=attendee_id, tournament_id=tournament_id)) session.commit() except Exception: log.error('unable to drop tournament entrant', exc_info=True) return { 'message': 'Entrant dropped; if re-added they will be re-texted', 'state': _state(session) }
def send_sms(to, body, from_=c.PANELS_TWILIO_NUMBER): message = None sid = 'Unable to send sms' try: to = normalize_phone(to) if not twilio_client: log.error('no twilio client configured') elif c.DEV_BOX and to not in c.TESTING_PHONE_NUMBERS: log.info( 'We are in dev box mode, so we are not sending {!r} to {!r}', body, to) else: message = twilio_client.messages.create( to=to, from_=normalize_phone(from_), body=body) # Avoid hitting rate limit. # NOTE: the send_email() implementation already does this. sleep(0.1) if message: sid = message.sid if not message.error_code else message.error_text except TwilioRestException as e: if e.code == 21211: # https://www.twilio.com/docs/api/errors/21211 log.error('Invalid cellphone number', exc_info=True) else: log.error('Unable to send SMS notification', exc_info=True) raise except Exception: log.error('Unexpected error sending SMS', exc_info=True) raise return sid
def link_badge(self, session, applicant_id, attendee_id): attendee = session.attendee(attendee_id) try: applicant = session.mits_applicant(applicant_id) applicant.attendee = attendee add_opt(attendee.ribbon_ints, c.MIVS) session.commit() except Exception: log.error('unexpected error linking applicant to a badge', exc_info=True) return {'error': 'Unexpected error: unable to link applicant to badge.'} else: return { 'name': applicant.full_name, 'comp_count': applicant.team.comped_badge_count }
def mark_paid_from_stripe_id(stripe_id): from uber.tasks.email import send_email from uber.decorators import render with uber.models.Session() as session: matching_stripe_txns = session.query(uber.models.StripeTransaction).filter_by(stripe_id=stripe_id) dealers_paid = [] for txn in matching_stripe_txns: txn.type = c.PAYMENT session.add(txn) for item in txn.receipt_items: item.txn_type = c.PAYMENT session.add(item) for group_log in txn.groups: group = group_log.group if not group.amount_pending: group.paid = c.HAS_PAID session.add(group) if group.is_dealer: dealers_paid.append(group) for attendee_log in txn.attendees: attendee = attendee_log.attendee if not attendee.amount_pending: if attendee.badge_status == c.PENDING_STATUS: attendee.badge_status = c.NEW_STATUS if attendee.paid in [c.NOT_PAID, c.PENDING]: attendee.paid = c.HAS_PAID session.add(attendee) session.commit() for group in dealers_paid: try: send_email.delay( c.MARKETPLACE_EMAIL, c.MARKETPLACE_EMAIL, '{} Payment Completed'.format(c.DEALER_TERM.title()), render('emails/dealers/payment_notification.txt', {'group': group}, encoding=None), model=group.to_dict('id')) except Exception: log.error('unable to send {} payment confirmation email'.format(c.DEALER_TERM), exc_info=True) return matching_stripe_txns
def create_badge(self, session, applicant_id): try: applicant = session.mits_applicant(applicant_id) applicant.attendee = Attendee(placeholder=True, paid=c.NEED_NOT_PAY, badge_type=c.ATTENDEE_BADGE, first_name=applicant.first_name, last_name=applicant.last_name, email=applicant.email, cellphone=applicant.cellphone) session.add(applicant.attendee) session.commit() except Exception: log.error('unexpected error adding new applicant', exc_info=True) return {'error': 'Unexpected error: unable to add attendee'} else: return {'comp_count': applicant.team.comped_badge_count}
def transfer_badge(self, session, message='', **params): old = session.attendee(params['id']) assert old.is_transferable, 'This badge is not transferrable.' session.expunge(old) attendee = session.attendee(params, restricted=True) if 'first_name' in params: message = check(attendee, prereg=True) if (old.first_name == attendee.first_name and old.last_name == attendee.last_name) \ or (old.legal_name and old.legal_name == attendee.legal_name): message = 'You cannot transfer your badge to yourself.' elif not message and (not params['first_name'] and not params['last_name']): message = check(attendee, prereg=True) if not message and (not params['first_name'] and not params['last_name']): message = 'First and Last names are required.' if not message: subject = c.EVENT_NAME + ' Registration Transferred' body = render('emails/reg_workflow/badge_transfer.txt', {'new': attendee, 'old': old}, encoding=None) try: send_email.delay( c.REGDESK_EMAIL, [old.email, attendee.email, c.REGDESK_EMAIL], subject, body, model=attendee.to_dict('id')) except Exception: log.error('unable to send badge change email', exc_info=True) if attendee.amount_unpaid: raise HTTPRedirect('attendee_donation_form?id={}', attendee.id) else: raise HTTPRedirect( 'badge_updated?id={}&message={}', attendee.id, 'Your registration has been transferred') else: for attr in c.UNTRANSFERABLE_ATTRS: setattr(attendee, attr, getattr(Attendee(), attr)) return { 'old': old, 'attendee': attendee, 'message': message, 'affiliates': session.affiliates() }
def differences(cls, instance): diff = {} for attr, column in instance.__table__.columns.items(): new_val = getattr(instance, attr) old_val = instance.orig_value_of(attr) if old_val != new_val: """ Important note: here we try and show the old vs new value for something that has been changed so that we can report it in the tracking page. Sometimes, however, if we changed the type of the value in the database (via a database migration) the old value might not be able to be shown as the new type (i.e. it used to be a string, now it's int). In that case, we won't be able to show a representation of the old value and instead we'll log it as '<ERROR>'. In theory the database migration SHOULD be the thing handling this, but if it doesn't, it becomes our problem to deal with. We are overly paranoid with exception handling here because the tracking code should be made to never, ever, ever crash, even if it encounters insane/old data that really shouldn't be our problem. """ try: old_val_repr = cls.repr(column, old_val) except Exception: log.error( 'Tracking repr({}) failed on old value'.format(attr), exc_info=True) old_val_repr = '<ERROR>' try: new_val_repr = cls.repr(column, new_val) except Exception: log.error( 'Tracking repr({}) failed on new value'.format(attr), exc_info=True) new_val_repr = '<ERROR>' diff[attr] = "'{} -> {}'".format(old_val_repr, new_val_repr) return diff
def sign_up(self, session, tournament_id, attendee_id, cellphone): from uber import model_checks as umc if umc._invalid_phone_number(cellphone): return {'error': 'That is not a valid phone number'} try: attendee = session.attendee(attendee_id) attendee.cellphone = cellphone session.add(TabletopEntrant(attendee_id=attendee_id, tournament_id=tournament_id)) session.commit() except Exception: session.rollback() log.error( 'unable to add tournament entrant tournament={} attendee={}', tournament_id, attendee_id, exc_info=True) return {'error': 'That attendee is already signed up for that tournament'} else: return { 'message': 'Attendee signed up', 'state': _state(session) }
def send_email(source, dest, subject, body, format='text', cc=(), bcc=(), model=None, ident=None): subject = subject.format(EVENT_NAME=c.EVENT_NAME) to, cc, bcc = map(listify, [dest, cc, bcc]) ident = ident or subject if c.DEV_BOX: for xs in [to, cc, bcc]: xs[:] = [email for email in xs if _is_dev_email(email)] if c.SEND_EMAILS and to: msg_kwargs = {'bodyText' if format == 'text' else 'bodyHtml': body} message = EmailMessage(subject=subject, **msg_kwargs) AmazonSES(c.AWS_ACCESS_KEY, c.AWS_SECRET_KEY).sendEmail(source=source, toAddresses=to, ccAddresses=cc, bccAddresses=bcc, message=message) sleep(0.1) # Avoid hitting rate limit else: log.error('email sending turned off, so unable to send {}', locals()) if model and dest: body = body.decode('utf-8') if isinstance(body, bytes) else body if model == 'n/a': fk_kwargs = {'model': 'n/a'} else: fk_kwargs = {'fk_id': model.id, 'model': model.__class__.__name__} _record_email_sent( uber.models.email.Email(subject=subject, dest=','.join(listify(dest)), body=body, ident=ident, **fk_kwargs))
def _should_send(self, model_inst, raise_errors=False): """ If True, we should generate an actual email created from our email category and send it to a particular model instance. This is determined based on a few things like: 1) whether we have sent this exact email out yet or not 2) whether the email category has been approved 3) whether the model instance passed in is the same type as what we want to process 4) do any date-based filters exist on this email category? (i.e. send 7 days before magfest) 5) do any other filters exist on this email category? (i.e. only if attendee.staffing == true) Example #1 of a model instance to check: self.ident: "You {attendee.name} have registered for our event!" model_inst: class Attendee: id #4532, name: "John smith" Example #2 of a model instance to check: self.ident: "Your group {group.name} owes money" model_inst: class Group: id #1251, name: "The Fighting Mongooses" :param model_inst: The model we've been requested to use (i.e. Attendee, Group, etc) :return: True if we should send this email to this model instance, False if not. """ try: return all(condition() for condition in [ lambda: not c.AT_THE_CON or self.allow_during_con, lambda: isinstance(model_inst, self.model), lambda: getattr(model_inst, 'email', None), lambda: not self._already_sent(model_inst), lambda: self.filters_run(model_inst), lambda: self.approved, ]) except Exception: log.error('error determining whether to send {!r} email to {}', self.subject, model_inst.email, exc_info=True) if raise_errors: raise return False
def refresh(self, session, **params): zips = {} self.zips_counter = Counter() attendees = session.query(Attendee).all() for person in attendees: if person.zip_code: self.zips_counter[person.zip_code] += 1 for z in self.zips_counter.keys(): try: found = SearchEngine( db_file_dir="/srv/reggie/data").by_zipcode(int(z)) except Exception as e: log.error("Error calling SearchEngine: " + e) else: if found.zipcode: zips[z] = found self.zips = zips return True
def process_group_payment(self, session, payment_id, stripeToken): charge = Charge.get(payment_id) [group] = charge.groups message = charge.charge_cc(session, stripeToken) if message: raise HTTPRedirect('group_members?id={}&message={}', group.id, message) else: group.amount_paid += charge.dollar_amount session.merge(group) if group.is_dealer: try: send_email.delay( c.MARKETPLACE_EMAIL, c.MARKETPLACE_EMAIL, 'Dealer Payment Completed', render('emails/dealers/payment_notification.txt', {'group': group}, encoding=None), model=group.to_dict('id')) except Exception: log.error('unable to send dealer payment confirmation email', exc_info=True) raise HTTPRedirect('group_members?id={}&message={}', group.id, 'Your payment has been accepted!')
def differences(cls, instance): diff = {} for attr, column in instance.__table__.columns.items(): new_val = getattr(instance, attr) old_val = instance.orig_value_of(attr) if old_val != new_val: """ Important note: here we try and show the old vs new value for something that has been changed so that we can report it in the tracking page. Sometimes, however, if we changed the type of the value in the database (via a database migration) the old value might not be able to be shown as the new type (i.e. it used to be a string, now it's int). In that case, we won't be able to show a representation of the old value and instead we'll log it as '<ERROR>'. In theory the database migration SHOULD be the thing handling this, but if it doesn't, it becomes our problem to deal with. We are overly paranoid with exception handling here because the tracking code should be made to never, ever, ever crash, even if it encounters insane/old data that really shouldn't be our problem. """ try: old_val_repr = cls.repr(column, old_val) except Exception: log.error('Tracking repr({}) failed on old value'.format(attr), exc_info=True) old_val_repr = '<ERROR>' try: new_val_repr = cls.repr(column, new_val) except Exception: log.error('Tracking repr({}) failed on new value'.format(attr), exc_info=True) new_val_repr = '<ERROR>' diff[attr] = "'{} -> {}'".format(old_val_repr, new_val_repr) return diff
def check_id_for_model(model, **params): message = None session = params['session'] model_id = params.get('id') if not model_id: message = "No ID provided. Try using a different link or going back." elif model_id == 'None': # Some pages use the string 'None' is indicate that a new model should be created, so this is a valid ID pass else: try: if not isinstance(model_id, uuid.UUID): uuid.UUID(model_id) except ValueError: message = "That ID is not a valid format. Did you enter or edit it manually or paste it incorrectly?" else: if not session.query(model).filter(model.id == model_id).first(): message = "The ID provided was not found in our database." if message: log.error("check_id {} error: {}: id={}", model.__name__, message, model_id) raise HTTPRedirect('../preregistration/not_found?id={}&message={}', model_id, message)
def test_eager_formatting_adapter(log_stream): log = AutoLogger(EagerFormattingAdapter) log.log(0, 'suppressed') log.debug('a %(a)d b %(b)s', {'a': 1, 'b': 2}) log.trace('TEST NO INTERPOLATION') log.trace('TEST %s', 'MSG') log.debug('TEST %s', 'MSG') log.info('TEST %s%s%s', 'M', 'S', 'G') log.warn('TEST %s', 'MSG') log.warning('TEST %s', 'MSG') log.error('TEST %s', 'MSG') try: assert False except Exception: log.exception('TEST %s', 'MSG') log.critical('TEST %s', 'MSG') log.fatal('TEST %s', 'MSG') result = log_stream.getvalue() assert result.startswith("""\ [DEBUG] tests.test_logging: a 1 b 2 [TRACE] tests.test_logging: TEST NO INTERPOLATION [TRACE] tests.test_logging: TEST MSG [DEBUG] tests.test_logging: TEST MSG [INFO] tests.test_logging: TEST MSG [WARNING] tests.test_logging: TEST MSG [WARNING] tests.test_logging: TEST MSG [ERROR] tests.test_logging: TEST MSG [ERROR] tests.test_logging: TEST MSG Traceback (most recent call last): """) assert result.endswith("""\ AssertionError: assert False [CRITICAL] tests.test_logging: TEST MSG [CRITICAL] tests.test_logging: TEST MSG """)
def send_to(self, model_instance, delay=True, raise_errors=False): try: from uber.tasks.email import send_email data = self.renderable_data(model_instance) send_func = send_email.delay if delay else send_email send_func(self.sender, model_instance.email_to_address, self.render_template(self.subject, data), self.render_template(self.body, data), self.format, model=model_instance.to_dict('id'), cc=self.cc, bcc=self.bcc, ident=self.ident, automated_email=self.to_dict('id')) return True except Exception: log.error('Error sending {!r} email to {}', self.subject, model_instance.email_to_address, exc_info=True) if raise_errors: raise return False
def sign_up(self, session, tournament_id, attendee_id, cellphone): from uber import model_checks as umc if umc._invalid_phone_number(cellphone): return {'error': 'That is not a valid phone number'} try: attendee = session.attendee(attendee_id) attendee.cellphone = cellphone session.add( TabletopEntrant(attendee_id=attendee_id, tournament_id=tournament_id)) session.commit() except Exception: session.rollback() log.error( 'unable to add tournament entrant tournament={} attendee={}', tournament_id, attendee_id, exc_info=True) return { 'error': 'That attendee is already signed up for that tournament' } else: return {'message': 'Attendee signed up', 'state': _state(session)}
def send_sms_with_client(twilio_client, to, body, from_): message = None sid = 'Unable to send SMS' try: to = normalize_phone(to) if not twilio_client: log.error('No twilio client configured') elif c.DEV_BOX and to not in c.TESTING_PHONE_NUMBERS: log.info('We are in DEV BOX mode, so we are not sending {!r} to {!r}', body, to) else: message = twilio_client.messages.create(to=to, body=body, from_=normalize_phone(from_)) sleep(0.1) # Avoid hitting rate limit. if message: sid = message.sid if not message.error_code else message.error_text except TwilioRestException as e: if e.code == 21211: # https://www.twilio.com/docs/api/errors/21211 log.error('Invalid cellphone number', exc_info=True) else: log.error('Unable to send SMS notification', exc_info=True) raise except Exception: log.error('Unexpected error sending SMS', exc_info=True) raise return sid
c.STORE_ITEM_NAMES = [desc for val, desc in c.STORE_PRICE_OPTS] c.FEE_ITEM_NAMES = [desc for val, desc in c.FEE_PRICE_OPTS] c.WRISTBAND_COLORS = defaultdict(lambda: c.WRISTBAND_COLORS[c.DEFAULT_WRISTBAND], c.WRISTBAND_COLORS) c.SAME_NUMBER_REPEATED = r'^(\d)\1+$' # Allows 0-9, a-z, A-Z, and a handful of punctuation characters c.INVALID_BADGE_PRINTED_CHARS = r'[^a-zA-Z0-9!"#$%&\'()*+,\-\./:;<=>?@\[\\\]^_`\{|\}~ "]' c.EVENT_QR_ID = c.EVENT_QR_ID or c.EVENT_NAME_AND_YEAR.replace(' ', '_').lower() try: _items = sorted([int(step), url] for step, url in _config['volunteer_checklist'].items() if url) except ValueError: log.error('[volunteer_checklist] config options must have integer option names') raise else: c.VOLUNTEER_CHECKLIST = [url for step, url in _items] stripe.api_key = c.STRIPE_SECRET_KEY # plugins can use this to append paths which will be included as <script> tags, e.g. if a plugin # appends '../static/foo.js' to this list, that adds <script src="../static/foo.js"></script> to # all of the pages on the site except for preregistration pages (for performance) c.JAVASCRIPT_INCLUDES = [] # A list of models that have properties defined for exporting for Guidebook c.GUIDEBOOK_MODELS = [
def log_with_verbose_context(msg, exc_info=False): full_msg = '\n'.join([msg, get_verbose_request_context()]) log.error(full_msg, exc_info=exc_info)
def attractions_send_notifications(): twilio_client = get_twilio_client(c.PANELS_TWILIO_SID, c.PANELS_TWILIO_TOKEN) with Session() as session: for attraction in session.query(Attraction): now = datetime.now(pytz.UTC) from_time = now - timedelta(seconds=300) to_time = now + timedelta(seconds=300) signups = attraction.signups_requiring_notification(session, from_time, to_time, [ subqueryload( AttractionSignup.attendee).subqueryload( Attendee.attraction_notifications), subqueryload( AttractionSignup.event).subqueryload( AttractionEvent.feature)]) for signup, advance_notices in signups.items(): attendee = signup.attendee if not attendee.first_name or not attendee.email: try: log.error( 'ERROR: Unassigned attendee signed up for an attraction, deleting signup:\n' '\tAttendee.id: {}\n' '\tAttraction.id: {}\n' '\tAttractionEvent.id: {}\n' '\tAttractionSignup.id: {}'.format( attendee.id, signup.attraction_id, signup.attraction_event_id, signup.id)) session.delete(signup) session.commit() except Exception: log.error('ERROR: Failed to delete signup with unassigned attendee', exc_info=True) continue # The first time someone signs up for an attractions, they always # receive the welcome email (even if they've chosen SMS or None # for their notification prefs). If they've chosen to receive SMS # notifications, they'll also get a text message. is_first_signup = not(attendee.attraction_notifications) if not is_first_signup and attendee.notification_pref == Attendee._NOTIFICATION_NONE: continue use_text = twilio_client \ and c.PANELS_TWILIO_NUMBER \ and attendee.cellphone \ and attendee.notification_pref == Attendee._NOTIFICATION_TEXT event = signup.event # If we overlap multiple notices, we only want to send a single # notification. So if we have both "5 minutes before checkin" and # "when checkin starts", we only want to send the notification # for "when checkin starts". advance_notice = min(advance_notices) if advance_notice == -1 or advance_notice > 1800: checkin = 'is at {}'.format(event.checkin_start_time_label) else: checkin = humanize_timedelta( event.time_remaining_to_checkin, granularity='minutes', separator=' ', prefix='is in ', now='is right now', past_prefix='was ', past_suffix=' ago') ident = AttractionEvent.get_ident(event.id, advance_notice) try: if use_text: type_ = Attendee._NOTIFICATION_TEXT type_str = 'TEXT' from_ = c.PANELS_TWILIO_NUMBER to_ = attendee.cellphone body = TEXT_TEMPLATE.format(signup=signup, checkin=checkin) subject = '' sid = send_sms_with_client(twilio_client, to_, body, from_) if not use_text or is_first_signup: type_ = Attendee._NOTIFICATION_EMAIL type_str = 'EMAIL' from_ = c.ATTRACTIONS_EMAIL to_ = attendee.email if is_first_signup: template = 'emails/panels/attractions_welcome.html' subject = 'Welcome to {} Attractions'.format(c.EVENT_NAME) else: template = 'emails/panels/attractions_notification.html' subject = 'Checkin for {} is at {}'.format(event.name, event.checkin_start_time_label) body = render(template, { 'signup': signup, 'checkin': checkin, 'c': c}).decode('utf-8') sid = ident send_email(from_, to_, subject=subject, body=body, format='html', model=attendee, ident=ident) except Exception: log.error( 'Error sending notification\n' '\tfrom: {}\n' '\tto: {}\n' '\tsubject: {}\n' '\tbody: {}\n' '\ttype: {}\n' '\tattendee: {}\n' '\tident: {}\n'.format( from_, to_, subject, body, type_str, attendee.id, ident), exc_info=True) else: session.add(AttractionNotification( attraction_event_id=event.id, attraction_id=event.attraction_id, attendee_id=attendee.id, notification_type=type_, ident=ident, sid=sid, sent_time=datetime.now(pytz.UTC), subject=subject, body=body)) session.commit()
def swallow_exception(*args, **kwargs): try: return func(*args, **kwargs) except Exception: log.error('Unexpected error', exc_info=True)
def form(self, session, message='', edit_id=None, **params): """ Our production NGINX config caches the page at /preregistration/form. Since it's cached, we CAN'T return a session cookie with the page. We must POST to a different URL in order to bypass the cache and get a valid session cookie. Thus, this page is also exposed as "post_form". """ params['id'] = 'None' # security! group = Group() if edit_id is not None: attendee = self._get_unsaved( edit_id, if_not_found=HTTPRedirect('form?message={}', 'That preregistration has already been finalized')) attendee.apply(params, restricted=True) params.setdefault('pii_consent', True) else: attendee = session.attendee(params, ignore_csrf=True, restricted=True) if attendee.badge_type == c.PSEUDO_DEALER_BADGE: if not c.DEALER_REG_OPEN: return render('static_views/dealer_reg_closed.html') if c.AFTER_DEALER_REG_START \ else render('static_views/dealer_reg_not_open.html') # Both the Attendee class and Group class have identically named # address fields. In order to distinguish the two sets of address # fields in the params, the Group fields are prefixed with "group_" # when the form is submitted. To prevent instantiating the Group object # with the Attendee's address fields, we must clone the params and # rename all the "group_" fields. group_params = dict(params) for field_name in ['country', 'region', 'zip_code', 'address1', 'address2', 'city']: group_params[field_name] = params.get('group_{}'.format(field_name), '') if params.get('copy_address'): params[field_name] = group_params[field_name] group = session.group(group_params, ignore_csrf=True, restricted=True) if c.PAGE == 'post_dealer': attendee.badge_type = c.PSEUDO_DEALER_BADGE elif not attendee.badge_type: attendee.badge_type = c.ATTENDEE_BADGE if cherrypy.request.method == 'POST' or edit_id is not None: message = check_pii_consent(params, attendee) or message if not message and attendee.badge_type not in c.PREREG_BADGE_TYPES: message = 'Invalid badge type!' if not message and c.BADGE_PROMO_CODES_ENABLED and params.get('promo_code'): message = session.add_promo_code_to_attendee(attendee, params.get('promo_code')) if message: return { 'message': message, 'attendee': attendee, 'group': group, 'edit_id': edit_id, 'affiliates': session.affiliates(), 'cart_not_empty': Charge.unpaid_preregs, 'copy_address': params.get('copy_address'), 'promo_code': params.get('promo_code', ''), 'pii_consent': params.get('pii_consent'), } if 'first_name' in params: message = check(attendee, prereg=True) if not message and attendee.badge_type == c.PSEUDO_DEALER_BADGE: message = check(group, prereg=True) if attendee.badge_type in [c.PSEUDO_GROUP_BADGE, c.PSEUDO_DEALER_BADGE]: message = "Please enter a group name" if not params.get('name') else '' else: params['badges'] = 0 params['name'] = '' if not message: if attendee.badge_type == c.PSEUDO_DEALER_BADGE: attendee.paid = c.PAID_BY_GROUP group.attendees = [attendee] session.assign_badges(group, params['badges']) group.status = c.WAITLISTED if c.DEALER_REG_SOFT_CLOSED else c.UNAPPROVED attendee.ribbon = add_opt(attendee.ribbon_ints, c.DEALER_RIBBON) attendee.badge_type = c.ATTENDEE_BADGE session.add_all([attendee, group]) session.commit() try: send_email.delay( c.MARKETPLACE_EMAIL, c.MARKETPLACE_EMAIL, 'Dealer Application Received', render('emails/dealers/reg_notification.txt', {'group': group}, encoding=None), model=group.to_dict('id')) send_email.delay( c.MARKETPLACE_EMAIL, attendee.email, 'Dealer Application Received', render('emails/dealers/application.html', {'group': group}, encoding=None), 'html', model=group.to_dict('id')) except Exception: log.error('unable to send marketplace application confirmation email', exc_info=True) raise HTTPRedirect('dealer_confirmation?id={}', group.id) else: track_type = c.UNPAID_PREREG if attendee.id in Charge.unpaid_preregs: track_type = c.EDITED_PREREG # Clear out any previously cached targets, in case the unpaid badge # has been edited and changed from a single to a group or vice versa. del Charge.unpaid_preregs[attendee.id] Charge.unpaid_preregs[attendee.id] = Charge.to_sessionized(attendee, params.get('name'), params.get('badges')) Tracking.track(track_type, attendee) if session.attendees_with_badges().filter_by( first_name=attendee.first_name, last_name=attendee.last_name, email=attendee.email).count(): raise HTTPRedirect('duplicate?id={}', group.id if attendee.paid == c.PAID_BY_GROUP else attendee.id) if attendee.banned: raise HTTPRedirect('banned?id={}', group.id if attendee.paid == c.PAID_BY_GROUP else attendee.id) if c.PREREG_REQUEST_HOTEL_INFO_OPEN: hotel_page = 'hotel?edit_id={}' if edit_id else 'hotel?id={}' raise HTTPRedirect(hotel_page, group.id if attendee.paid == c.PAID_BY_GROUP else attendee.id) else: raise HTTPRedirect('index') else: if edit_id is None: if attendee.badge_type == c.PSEUDO_DEALER_BADGE: # All new dealer signups should default to receiving the # hotel info email, even if the deadline has passed. # There's a good chance some dealers will apply for a table # AFTER the hotel booking deadline, but BEFORE the hotel # booking is sent out. This ensures they'll still receive # the email, as requested by the Marketplace Department. attendee.requested_hotel_info = True if attendee.badge_type == c.PSEUDO_DEALER_BADGE and c.DEALER_REG_SOFT_CLOSED: message = 'Dealer registration is closed, but you can ' \ 'fill out this form to add yourself to our waitlist' promo_code_group = None if attendee.promo_code: promo_code_group = session.query(PromoCode).filter_by(code=attendee.promo_code.code).first().group return { 'message': message, 'attendee': attendee, 'group': group, 'promo_code_group': promo_code_group, 'edit_id': edit_id, 'affiliates': session.affiliates(), 'cart_not_empty': Charge.unpaid_preregs, 'copy_address': params.get('copy_address'), 'promo_code': params.get('promo_code', ''), 'pii_consent': params.get('pii_consent'), }
def send_email( sender, to, subject, body, format='text', cc=(), bcc=(), model=None, ident=None, automated_email=None, session=None): to, cc, bcc = map(lambda x: listify(x if x else []), [to, cc, bcc]) original_to, original_cc, original_bcc = to, cc, bcc ident = ident or subject if c.DEV_BOX: to, cc, bcc = map(lambda xs: list(filter(_is_dev_email, xs)), [to, cc, bcc]) if c.SEND_EMAILS and to: msg_kwargs = {'bodyText' if format == 'text' else 'bodyHtml': body} message = EmailMessage(subject=subject, **msg_kwargs) AmazonSES(c.AWS_ACCESS_KEY, c.AWS_SECRET_KEY).sendEmail( source=sender, toAddresses=to, ccAddresses=cc, bccAddresses=bcc, message=message) sleep(0.1) # Avoid hitting rate limit else: log.error('Email sending turned off, so unable to send {}', locals()) if original_to: body = body.decode('utf-8') if isinstance(body, bytes) else body if isinstance(model, MagModel): fk_kwargs = {'fk_id': model.id, 'model': model.__class__.__name__} elif isinstance(model, Mapping): fk_kwargs = {'fk_id': model.get('id', None), 'model': model.get('_model', model.get('__type__', 'n/a'))} else: fk_kwargs = {'model': 'n/a'} if automated_email: if isinstance(automated_email, MagModel): fk_kwargs['automated_email_id'] = automated_email.id elif isinstance(model, Mapping): fk_kwargs['automated_email_id'] = automated_email.get('id', None) email = Email( subject=subject, body=body, sender=sender, to=','.join(original_to), cc=','.join(original_cc), bcc=','.join(original_bcc), ident=ident, **fk_kwargs) session = session or getattr(model, 'session', getattr(automated_email, 'session', None)) if session: session.add(email) else: with Session() as session: session.add(email)
def import_model(self, session, model_import, selected_model='', date_format="%Y-%m-%d"): model = Session.resolve_model(selected_model) message = '' cols = {col.name: getattr(model, col.name) for col in model.__table__.columns} result = csv.DictReader(model_import.file.read().decode('utf-8').split('\n')) id_list = [] for row in result: if 'id' in row: id = row.pop('id') # id needs special treatment try: # get the instance if it already exists model_instance = getattr(session, selected_model)(id, allow_invalid=True) except Exception: session.rollback() # otherwise, make a new one and add it to the session for when we commit model_instance = model() session.add(model_instance) for colname, val in row.items(): col = cols[colname] if not val: # in a lot of cases we'll just have the empty string, so we'll just # do nothing for those cases continue if isinstance(col.type, Boolean): if isinstance(val, six.string_types): val = val.strip().lower() not in ('f', 'false', 'n', 'no', '0') else: val = bool(val) elif isinstance(col.type, Choice): # the export has labels, and we want to convert those back into their # integer values, so let's look that up (note: we could theoretically # modify the Choice class to do this automatically in the future) label_lookup = {val: key for key, val in col.type.choices.items()} val = label_lookup[val] elif isinstance(col.type, MultiChoice): # the export has labels separated by ' / ' and we want to convert that # back into a comma-separate list of integers label_lookup = {val: key for key, val in col.type.choices} vals = [label_lookup[label] for label in val.split(' / ')] val = ','.join(map(str, vals)) elif isinstance(col.type, UTCDateTime): # we'll need to make sure we use whatever format string we used to # export this date in the first place try: val = UTC.localize(datetime.strptime(val, date_format + ' %H:%M:%S')) except Exception: val = UTC.localize(datetime.strptime(val, date_format)) elif isinstance(col.type, Date): val = datetime.strptime(val, date_format).date() elif isinstance(col.type, Integer): val = int(val) # now that we've converted val to whatever it actually needs to be, we # can just set it on the model setattr(model_instance, colname, val) try: session.commit() except Exception: log.error('ImportError', exc_info=True) session.rollback() message = 'Import unsuccessful' id_list.append(model_instance.id) all_instances = session.query(model).filter(model.id.in_(id_list)).all() if id_list else None return self.index(message, all_instances)