def test_cleanup_expiry_date(self): config = SiteConfiguration.get_solo() config.sms_expiration_date = None config.save() logs.check_incoming_log() logs.check_outgoing_log() num_sms = models.SmsInbound.objects.count() + models.SmsOutbound.objects.count() logs.cleanup_expired_sms() assert num_sms > 0 assert models.SmsInbound.objects.count() + models.SmsOutbound.objects.count() == num_sms config = SiteConfiguration.get_solo() config.sms_expiration_date = today + timedelta(days=1) config.save() logs.cleanup_expired_sms() assert models.SmsInbound.objects.count() + models.SmsOutbound.objects.count() == 0
def test_cleanup_expired_sms(self, smsin): config = SiteConfiguration.get_solo() config.sms_expiration_date = None config.save() for sms in SmsInbound.objects.all(): # move back in time so they can be deleted sms.time_received = sms.time_received - timedelta(days=5) sms.save() cleanup_expired_sms() assert SmsInbound.objects.count() == len(smsin) config = SiteConfiguration.get_solo() config.sms_expiration_date = timezone.localdate() config.save() cleanup_expired_sms() assert SmsInbound.objects.count() == 0
def get_person_or_ask_for_name(from_, sms_body, keyword_obj): """ Return the Recipient object for the sender of the message. Perform a look up on the sender of the message. If they exist in the system, they are returned. Otherwise a message is queued to ask them for their name. """ try: person_from = Recipient.objects.get(number=from_) except Recipient.DoesNotExist: person_from = Recipient.objects.create( number=from_, first_name='Unknown', last_name='Person' ) person_from.save() if keyword_obj == "name": pass else: from site_config.models import SiteConfiguration config = SiteConfiguration.get_solo() if not config.disable_all_replies: person_from.send_message( content=fetch_default_reply('auto_name_request'), sent_by="auto name request" ) notify_office_mail.delay( '[Apostello] Unknown Contact!', 'SMS: {0}\nFrom: {1}\n\n\nThis person is unknown and has been asked for their name.'.format( sms_body, from_ ), ) return person_from
def test_no_name_raises(self): """Test shorter limit imposed with %name% present.""" s = SiteConfiguration.get_solo() with pytest.raises(ValidationError): less_than_sms_char_limit( 't %name%' * (s.sms_char_limit - settings.MAX_NAME_LENGTH + len('%name%')))
def test_default_prefix(self, live_server, browser_in, users, driver_wait_time): """Test prefix shows on form.""" from site_config.models import SiteConfiguration config = SiteConfiguration.get_solo() config.default_number_prefix = "+45" config.save() b = load_page(browser_in, driver_wait_time, live_server + URI) # test is there def _test(): num_input = b.find_element_by_id("id_number") assert num_input.get_attribute("value") == "+45" assert_with_timeout(_test, 10 * driver_wait_time) config.default_number_prefix = "" config.save() b = load_page(browser_in, driver_wait_time, live_server + URI) # test is not there def _test(): num_input = b.find_element_by_id("id_number") assert num_input.get_attribute("value") != "+45"
def elm_settings(user): try: profile = user.profile except AttributeError: profile = UserProfile.nullProfile() config = SiteConfiguration.get_solo() elm = { 'userPerms': UserProfileSerializer(profile).data, 'twilioSendingCost': settings.TWILIO_SENDING_COST, 'twilioFromNumber': settings.TWILIO_FROM_NUM, 'smsCharLimit': config.sms_char_limit, 'defaultNumberPrefix': config.default_number_prefix, 'noAccessMessage': config.not_approved_msg, 'blockedKeywords': [ x.keyword for x in Keyword.objects.all().prefetch_related('owners') if x.is_locked and not x.can_user_access(user) ], } return mark_safe(json.dumps(elm))
def get_person_or_ask_for_name(from_, sms_body, keyword_obj): """ Return the Recipient object for the sender of the message. Perform a look up on the sender of the message. If they exist in the system, they are returned. Otherwise a message is queued to ask them for their name. """ try: person_from = Recipient.objects.get(number=from_) except Recipient.DoesNotExist: person_from = Recipient.objects.create(number=from_, first_name='Unknown', last_name='Person') person_from.save() if keyword_obj == "name": pass else: from site_config.models import SiteConfiguration config = SiteConfiguration.get_solo() if not config.disable_all_replies: person_from.send_message( content=fetch_default_reply('auto_name_request'), sent_by="auto name request") notify_office_mail.delay( '[Apostello] Unknown Contact!', 'SMS: {0}\nFrom: {1}\n\n\nThis person is unknown and has been asked for their name.' .format(sms_body, from_), ) return person_from
def elm_settings(user): try: profile = user.profile except AttributeError: profile = UserProfile.nullProfile() config = SiteConfiguration.get_solo() try: twilio_settings = config.get_twilio_settings() # remove sensitive settings: del twilio_settings['auth_token'] del twilio_settings['sid'] except ConfigurationError: twilio_settings = None bk_key = f'blocked_keywords_user_{user.pk}' blocked_keywords = cache.get(bk_key) if blocked_keywords is None: blocked_keywords = [ x.keyword for x in Keyword.objects.all().prefetch_related('owners') if not x.can_user_access(user) ] cache.set(bk_key, blocked_keywords, 120) elm = { 'userPerms': UserProfileSerializer(profile).data, 'twilio': twilio_settings, 'isEmailSetup': config.is_email_setup(), 'smsCharLimit': config.sms_char_limit, 'defaultNumberPrefix': config.default_number_prefix, 'noAccessMessage': config.not_approved_msg, 'blockedKeywords': blocked_keywords, } return mark_safe(json.dumps(elm))
def calculate_cost(self): """Calculate the cost of sending to this group.""" try: cost = SiteConfiguration.get_twilio_settings()['sending_cost'] except ConfigurationError: cost = 0 return cost * self.all_recipients.count()
def send_async_mail(subject, body, to): """Send email.""" # read email settings from DB from site_config.models import SiteConfiguration s = SiteConfiguration.get_solo() from_ = s.email_from send_mail(subject, body, from_, to)
def get_expiry_date(): config = SiteConfiguration.get_solo() exp_date = config.sms_expiration_date try: roll_date = datetime.today() - timedelta( days=config.sms_rolling_expiration_days) roll_date = roll_date.date() except TypeError: # no rolling expiration roll_date = None if roll_date is None and exp_date is None: # no expiration set return None elif roll_date is None: # no roll date, use expiration date delete_date = exp_date elif exp_date is None: # no expiration date, use roll date delete_date = roll_date else: # both set, use the most recent date of the tow delete_date = max([exp_date, roll_date]) return delete_date
def fetch_elvanto_groups(force=False): """Fetch all Elvanto groups.""" from site_config.models import SiteConfiguration config = SiteConfiguration.get_solo() if force or config.sync_elvanto: from elvanto.models import ElvantoGroup ElvantoGroup.fetch_all_groups()
def global_settings(request): """Expose TWILIO_FROM_NUM, DEBUG and site config in templates.""" return { 'CONFIG': SiteConfiguration.get_solo(), 'DISPLAY_GOOGLE_LOGIN': SocialApp.objects.filter(provider='google').count(), 'ROLLBAR_ACCESS_TOKEN_CLIENT': settings.ROLLBAR_ACCESS_TOKEN_CLIENT, }
def test_import_outgoing_rolling(self): config = SiteConfiguration.get_solo() config.sms_expiration_date = None config.sms_rolling_expiration_days = 0 config.save() logs.check_outgoing_log() assert models.SmsOutbound.objects.filter(time_sent__lt=today).count() == 0
def recipient_send_message_task(recipient_pk, body, group, sent_by): """Send a message asynchronously.""" from apostello.models import Recipient from site_config.models import SiteConfiguration recipient = Recipient.objects.get(pk=recipient_pk) if recipient.is_archived: # if recipient is not active, fail silently return from apostello.models import SmsOutbound, RecipientGroup # if %name% is present, replace: body = recipient.personalise(body) # send twilio message try: message = get_twilio_client().messages.create( body=body, to=str(recipient.number), from_=str(SiteConfiguration.get_solo().twilio_from_num) ) # add to sms out table sms = SmsOutbound(sid=message.sid, content=body, time_sent=timezone.now(), recipient=recipient, sent_by=sent_by) if group is not None: sms.recipient_group = RecipientGroup.objects.filter(name=group)[0] sms.save() except TwilioRestException as e: if e.code == 21610: recipient.is_blocking = True recipient.save() async('apostello.tasks.blacklist_notify', recipient.pk, '', 'stop') else: raise e
def pull_elvanto_groups(force=False): """Pull all the Elvanto groups that are set to sync.""" from site_config.models import SiteConfiguration config = SiteConfiguration.get_solo() if force or config.sync_elvanto: from elvanto.models import ElvantoGroup ElvantoGroup.pull_all_groups()
def elm_settings(user): try: profile = user.profile except AttributeError: profile = UserProfile.nullProfile() config = SiteConfiguration.get_solo() try: twilio_settings = config.get_twilio_settings() # remove sensitive settings: del twilio_settings["auth_token"] del twilio_settings["sid"] except ConfigurationError: twilio_settings = None bk_key = f"blocked_keywords_user_{user.pk}" blocked_keywords = cache.get(bk_key) if blocked_keywords is None: blocked_keywords = [ x.keyword for x in Keyword.objects.all().prefetch_related("owners") if not x.can_user_access(user) ] cache.set(bk_key, blocked_keywords, 120) elm = { "userPerms": UserProfileSerializer(profile).data, "twilio": twilio_settings, "isEmailSetup": config.is_email_setup(), "smsCharLimit": config.sms_char_limit, "defaultNumberPrefix": config.default_number_prefix, "noAccessMessage": config.not_approved_msg, "blockedKeywords": blocked_keywords, } return mark_safe(json.dumps(elm))
def recipient_send_message_task(recipient_pk, body, group, sent_by): """Send a message asynchronously.""" from apostello.models import Recipient from site_config.models import SiteConfiguration recipient = Recipient.objects.get(pk=recipient_pk) if recipient.is_archived: # if recipient is not active, fail silently return from apostello.models import SmsOutbound, RecipientGroup # if %name% is present, replace: body = recipient.personalise(body) # send twilio message try: message = get_twilio_client().messages.create( body=body, to=str(recipient.number), from_=str(SiteConfiguration.get_solo().twilio_from_num)) # add to sms out table sms = SmsOutbound(sid=message.sid, content=body, time_sent=timezone.now(), recipient=recipient, sent_by=sent_by) if group is not None: sms.recipient_group = RecipientGroup.objects.filter(name=group)[0] sms.save() except TwilioRestException as e: if e.code == 21610: recipient.is_blocking = True recipient.save() async ('apostello.tasks.blacklist_notify', recipient.pk, '', 'stop') else: raise e
def test_import_outgoing_expiry_date(self): config = SiteConfiguration.get_solo() config.sms_expiration_date = today config.save() logs.check_outgoing_log() assert models.SmsOutbound.objects.filter( time_sent__lt=today).count() == 0
def test_import_incoming_expiry_date(self): config = SiteConfiguration.get_solo() config.sms_expiration_date = today config.save() logs.check_incoming_log() assert models.SmsInbound.objects.filter( time_received__lt=today).count() == 0
def test_cleanup(self): # setup config = SiteConfiguration.get_solo() config.sms_rolling_expiration_days = None config.sms_expiration_date = today - timedelta(days=100) config.save() sms = models.SmsInbound.objects.create( content="test message", time_received=timezone.now() - timedelta(50), sender_name="John Calvin", sender_num="+447927401749", matched_keyword="test", sid="12345", ) sms.save() assert models.SmsInbound.objects.count() == 1 # we have one sms logs.cleanup_expired_sms() assert models.SmsInbound.objects.count( ) == 1 # cleanup should leave it untouched config.sms_rolling_expiration_days = 50 config.save() logs.cleanup_expired_sms() assert models.SmsInbound.objects.count( ) == 1 # cleanup should leave it untouched config.sms_rolling_expiration_days = 49 config.save() logs.cleanup_expired_sms() assert models.SmsInbound.objects.count( ) == 0 # cleanup should remove sms
def test_cleanup(self): # setup config = SiteConfiguration.get_solo() config.sms_rolling_expiration_days = None config.sms_expiration_date = today - timedelta(days=100) config.save() sms = models.SmsInbound.objects.create( content='test message', time_received=timezone.now() - timedelta(50), sender_name="John Calvin", sender_num="+447927401749", matched_keyword="test", sid='12345' ) sms.save() assert models.SmsInbound.objects.count() == 1 # we have one sms logs.cleanup_expired_sms() assert models.SmsInbound.objects.count() == 1 # cleanup should leave it untouched config.sms_rolling_expiration_days = 50 config.save() logs.cleanup_expired_sms() assert models.SmsInbound.objects.count() == 1 # cleanup should leave it untouched config.sms_rolling_expiration_days = 49 config.save() logs.cleanup_expired_sms() assert models.SmsInbound.objects.count() == 0 # cleanup should remove sms
def test_no_name_raises(self): """Test shorter limit imposed with %name% present.""" s = SiteConfiguration.get_solo() with pytest.raises(ValidationError): less_than_sms_char_limit( 't %name%' * (s.sms_char_limit - settings.MAX_NAME_LENGTH + len('%name%')) )
def send_async_mail(subject, body, to): """Send email.""" # read email settings from DB, if they are empty, they will be read from # settings.py instead from site_config.models import SiteConfiguration s = SiteConfiguration.get_solo() from_ = s.email_from or settings.EMAIL_FROM send_mail(subject, body, from_, to)
def check_user_cost_limit(recipients, limit, msg): """Check the user has not exceeded their per SMS cost limit.""" cost = SiteConfiguration.get_twilio_settings()['sending_cost'] num_sms = ceil(len(msg) / 160) if limit == 0: return if limit < len(recipients) * cost * num_sms: raise ValidationError('Sorry, you can only send messages that cost no more than ${0}.'.format(limit))
def __init__(self, *args, **kwargs): super(ApostelloEmailBackend, self).__init__(*args, **kwargs) from site_config.models import SiteConfiguration s = SiteConfiguration.get_solo() self.host = s.email_host self.port = s.email_port self.username = s.email_username self.password = s.email_password
def fetch_generator(direction): """Fetch generator from twilio.""" twilio_num = str(SiteConfiguration.get_solo().twilio_from_num) if direction == "in": return get_twilio_client().messages.list(to=twilio_num) if direction == "out": return get_twilio_client().messages.list(from_=twilio_num) return []
def global_settings(request): """Expose TWILIO_FROM_NUM, DEBUG and site config in templates.""" return { 'TWILIO_FROM_NUM': settings.TWILIO_FROM_NUM, 'TWILIO_SENDING_COST': settings.TWILIO_SENDING_COST, 'DEBUG': settings.DEBUG, 'CONFIG': SiteConfiguration.get_solo(), }
def not_twilio_num(value): """Ensure value does not match the sending number.""" from site_config.models import SiteConfiguration twilio_num = str(SiteConfiguration.get_solo().twilio_from_num) if str(value) == twilio_num: raise ValidationError( "You cannot add the number from which we send messages. Inception!" )
def post_to_slack(attachments): """Post message to slack webhook.""" from site_config.models import SiteConfiguration config = SiteConfiguration.get_solo() url = config.slack_url if url: data = {'username': '******', 'icon_emoji': ':speech_balloon:', 'attachments': attachments} headers = {'Content-type': 'application/json', 'Accept': 'text/plain'} requests.post(url, data=json.dumps(data), headers=headers)
def global_settings(request): """Expose TWILIO_FROM_NUM, DEBUG and site config in templates.""" return { 'TWILIO_FROM_NUM': settings.TWILIO_FROM_NUM, 'TWILIO_SENDING_COST': settings.TWILIO_SENDING_COST, 'DEBUG': settings.DEBUG, 'CONFIG': SiteConfiguration.get_solo(), 'DISPLAY_GOOGLE_LOGIN': SocialApp.objects.filter(provider='google').count(), }
def post_to_slack(attachments): """Post message to slack webhook.""" from site_config.models import SiteConfiguration config = SiteConfiguration.get_solo() url = config.slack_url if url: data = {"username": "******", "icon_emoji": ":speech_balloon:", "attachments": attachments} headers = {"Content-type": "application/json", "Accept": "text/plain"} requests.post(url, data=json.dumps(data), headers=headers)
def check_user_cost_limit(recipients, limit, msg): """Check the user has not exceeded their per SMS cost limit.""" cost = SiteConfiguration.get_twilio_settings()['sending_cost'] num_sms = ceil(len(msg) / 160) if limit == 0: return if limit < len(recipients) * cost * num_sms: raise ValidationError( 'Sorry, you can only send messages that cost no more than ${0}.' .format(limit))
def setup_twilio(): config = SiteConfiguration.get_solo() config.twilio_account_sid = os.environ.get('TWILIO_ACCOUNT_SID') config.twilio_auth_token = os.environ.get('TWILIO_AUTH_TOKEN') config.twilio_from_num = os.environ.get('TWILIO_FROM_NUM') try: cost = float(os.environ.get('TWILIO_SENDING_COST')) except TypeError: cost = None config.twilio_sending_cost = cost config.save()
def add_new_contact_to_groups(contact_pk): """Add contact to any groups that are in "auto populate with new contacts.""" logger.info('Adding new person to designated groups') from apostello.models import Recipient from site_config.models import SiteConfiguration contact = Recipient.objects.get(pk=contact_pk) for grp in SiteConfiguration.get_solo().auto_add_new_groups.all(): logger.info('Adding %s to %s', contact.full_name, grp.name) contact.groups.add(grp) contact.save() logger.info('Added %s to %s', contact.full_name, grp.name)
def setup_twilio(): config = SiteConfiguration.get_solo() config.twilio_account_sid = os.environ.get("TWILIO_ACCOUNT_SID") config.twilio_auth_token = os.environ.get("TWILIO_AUTH_TOKEN") config.twilio_from_num = os.environ.get("TWILIO_FROM_NUM") try: cost = float(os.environ.get("TWILIO_SENDING_COST")) except TypeError: cost = None config.twilio_sending_cost = cost config.save()
def test_get_email(): """Test pulling email from settings and SiteConfiguration.""" s = SiteConfiguration.get_solo() a = ApostelloAccountAdapter() # test env var is migrated into db: assert s.email_from == '*****@*****.**' assert a.get_from_email() == '*****@*****.**' # test change in db s.email_from = '*****@*****.**' s.save() assert a.get_from_email() == '*****@*****.**'
def test_add_new_ppl_to_groups(self, groups): config = SiteConfiguration.get_solo() grp = groups["empty_group"] assert grp.recipient_set.count() == 0 config.auto_add_new_groups.add(grp) config.save() new_c = Recipient.objects.create(first_name="test", last_name="new", number="+44715620857") assert grp.recipient_set.count() == 1 assert new_c in grp.recipient_set.all()
def less_than_sms_char_limit(value): """Ensure message is less than the maximum character limit.""" from site_config.models import SiteConfiguration s = SiteConfiguration.get_solo() sms_char_lim = s.sms_char_limit - settings.MAX_NAME_LENGTH + len('%name%') if len(value) > sms_char_lim: raise ValidationError( 'You have exceeded the maximum char limit of {0}.'.format( sms_char_lim ) )
def less_than_sms_char_limit(value): """Ensure message is less than the maximum character limit.""" from site_config.models import SiteConfiguration s = SiteConfiguration.get_solo() sms_char_lim = s.sms_char_limit - settings.MAX_NAME_LENGTH + len('%name%') if len(value) > sms_char_lim: raise ValidationError( 'You have exceed the maximum char limit of {0}.'.format( sms_char_lim ) )
def test_apostello_mail_backend(): """Test email backend pulling from settings and db.""" # test migration pulled env var mail_backend = ApostelloEmailBackend() assert mail_backend.host == 'smtp.test.apostello' # test Siteconfiguration change s = SiteConfiguration.get_solo() s.get_solo() s.email_host = 'smtp.test2.apostello' s.save() mail_backend = ApostelloEmailBackend() assert mail_backend.host == 'smtp.test2.apostello'
def test_handle_outgoing_sms(self): config = SiteConfiguration.get_solo() config.sms_expiration_date = None config.save() msg = MockMsg('447932537999') cnp = logs.handle_outgoing_sms(msg) assert models.SmsOutbound.objects.all()[0].content == msg.body assert models.SmsOutbound.objects.count() == 1 logs.handle_outgoing_sms(msg) assert models.SmsOutbound.objects.count() == 1
def test_get_email(): """Test pulling email from settings and SiteConfiguration.""" s = SiteConfiguration.get_solo() a = ApostelloAccountAdapter() # test from db s.email_from = '*****@*****.**' s.save() assert a.get_from_email() == '*****@*****.**' # test from settings s.email_from = '' s.save() assert a.get_from_email() == '*****@*****.**'
def test_not_logged_in(self, keywords, recipients): # make sure replies are on: from site_config.models import SiteConfiguration config = SiteConfiguration.get_solo() config.disable_all_replies = False config.save() factory = TwilioRequestFactory(token=settings.TWILIO_AUTH_TOKEN) data = test_request_data_blocked() data['Body'] = u'Test' request = factory.post(uri, data=data) resp = sms(request) assert '<Message><Body /></Message>' in str(resp.content)
def less_than_sms_char_limit(value): """Ensure message is less than the maximum character limit.""" from site_config.models import SiteConfiguration s = SiteConfiguration.get_solo() sms_char_lim = s.sms_char_limit if "%name%" in value: # if `%name%` in value, then adjust limit to handle substitutions sms_char_lim = sms_char_lim - settings.MAX_NAME_LENGTH + len("%name%") if len(value) > sms_char_lim: raise ValidationError("You have exceeded the maximum char limit of {0}.".format(sms_char_lim))
def test_add_new_ppl_to_groups(self, groups): config = SiteConfiguration.get_solo() grp = groups['empty_group'] assert grp.recipient_set.count() == 0 config.auto_add_new_groups.add(grp) config.save() new_c = Recipient.objects.create( first_name='test', last_name='new', number='+44715620857', ) assert grp.recipient_set.count() == 1 assert new_c in grp.recipient_set.all()
def test_not_logged_in(self, msg, reply, keywords): factory = TwilioRequestFactory(token=settings.TWILIO_AUTH_TOKEN) data = test_request_data() data['Body'] = msg request = factory.post(uri, data=data) # turn off responses from site_config.models import SiteConfiguration config = SiteConfiguration.get_solo() config.disable_all_replies = True config.save() # run test resp = sms(request) assert reply not in str(resp.content)
def email_admin_on_signup(request, user, **kwargs): """Email office on new user sign up.""" body = ( "New User Signed Up: {}\n\n" "Please go to the admin page to approve their account.\n" "If you do not approve their account (and they are not using a " "whitelisted domain), they will be unable to access apostello." ) body = body.format(str(user)) from site_config.models import SiteConfiguration to_ = SiteConfiguration.get_solo().office_email if to_: send_async_mail("[apostello] New User", body, [to_], )